М.В. Сухарев
основы
Профессиональный подход
иТ Наука и Техника, Санкт-Петербург 2004
М.В. Сухарев Основы Delphi. П...
45 downloads
972 Views
67MB Size
Report
This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
Report copyright / DMCA form
М.В. Сухарев
основы
Профессиональный подход
иТ Наука и Техника, Санкт-Петербург 2004
М.В. Сухарев Основы Delphi. Профессиональный подход — СПб.: Наука и Техника, 2004. — 600 с.: ил. Под редакцией М.В. Финкова
ISBN 5-94387-129-2
Серия «Секреты мастерства» Эта книга является превосходным учебным пособием, дающим наиболее полное и системное понимание основополагающих механизмов Delphi. Цель книги — помочь программистам, начинающим работать с Delphi, сделать первый шаг в ее изучении так, чтобы не возвращаться к пройденному материалу в дальнейшем для углубления знаний. В то же время книга будет интересна и опытным программистам, благодаря своему обобщенному изложению. Основная часть материала описывает фундаментальные основы Delphi, структуру компонентов и правила их использования. Подробно рассмотрено объектноориентированное программирование в Delphi. Описываются технологии взаимодействия приложений с операционной системой. Книгу отличает глубокий, системный подход и, в то же время, доступное изложение материала. Используется большое количество наглядных примеров. Лучший выбор для всех, кто хочет получить серьезные знания и грамотно работать с Delphi.
Контактные телефоны издательства (812) 567-70-25, 567-70-26 (044) 516-38-66, 559-27-40
Официальный сайт www.nit.com.ru ||
11
9'785943 871290 ISBN 5-94387-129-2
© Сухарев М.В. © Наука и Техника (оригинал-макет), 2004
ООО «Наука и Техника». Лицензия №000350 от 23 декабря 1999 года. 198097, г. Санкт-Петербург, ул. Маршала Говорова, д. 29. Подписано в печать 16.08.04. Формат 70x100 1/16. Бумага газетная. Печать офсетная. Объем 37,5 п. л. Тираж 3000 экз. Заказ № 867 Отпечатано с готовых диапозитивов в ОАО «Техническая книга» 190005, Санкт-Петербург, Измайловский пр., 29
Содержание Введение Общая характеристика Delphi Место Delphi в современном программировании Задачи издания
:
13 13 14 15
ЧАСТЬ (.СТРУКТУРНОЕ ПРОГРАММИРОВАНИЕ В DELPHI
16
Глава 1. Структура программы 1.1. Основные элементы программы и алфавит языка 1.2. Основная часть программы 1.3. Модули 1.4. Проект
18 18 19 21 23
Глава 2. Структуры данных в Delphi 2.1. Стандартные типы данных , 2.1.1. Числовые типы 2.1.2. Строковые типы данных 2.1.3. Другие стандартные типы данных 2.2. Описание структур данных 2.2.1. Описание и использование переменных Описание переменных Оператор присваивания 2.2.2. Описание констант 2.2.3. Описание нестандартных типов данных 2.3. Сложные типы данных 2.3.1. Интервальные типы данных '. 2.3.2. Перечислимые типы данных 2.3.3. Множества 2.3.4. Записи 2.3.5. Массивы 2.4. Динамические структуры данных 2.4.1. Нетипизированные указатели 2.4.2. Типизированные указатели 2.4.3. Динамические массивы 2.5. Вариантные структуры данных 2.5.1. Общие понятия 2.5.2. Обращение к вариантным переменным 2.5.3. Определение типа вариантных переменных 2.5.4. Автоматическое приведение типов 2.5.5. Вариантные массивы 2.5.6. Разрушение вариантных переменных 2.6. Выражения в Object Pascal
24 24 24 26 27 27 27 27 28 28 29 30 30 31 32 34 35 39 39 39 41 43 43 44 45 48 50 52 53
.-
Глава 3. Структурные операторы 3.1. Организация ветвления .' 3.1.1. Условный оператор If..Then..Else 3.1.2. Условный оператор множественного выбора Case 3.2. Составной оператор 3!3. Зацикливание 3.3.1. Оператор зацикливания For Стандартный вариант Цикл с обратным отсчетом
56 56 56 57 60 60 60 60 62
3
3.3.2. Условные операторы зацикливания Оператор зацикливания с предусловием While..do Условный оператор зацикливания с постусловием Repeat..Until 3.3.3. Прерывание зацикленного фрагмента
62 62 64 65
Глава 4. Описание подпрограмм 4.1. Виды подпрограмм: процедуры и функции 4.2. Подпрограммы с параметрами 4.2.1. Описание параметров 4.2.2. Механизмы передачи параметров в подпрограммы Передача параметров по значению Передача параметров по ссылке. Параметры-переменные 4.2.3. Частные случаи передачи параметров подпрограммам Параметры по умолчанию Передача по значению параметров ссылочных типов данных Параметры-константы Параметры для заполнения Параметры без указания типа 4.3. Перегружаемые подпрограммы 4.4. Досрочный выход из подпрограммы 4.5. Процедурные типы данных Вопросы с ответами для повтррения по части 1
66 66 68 68 69 69 70 71 71 73 74 75 76 78 79 79 81
ЧАСТЬ II. ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В DELPHI
89
Глава 5. Основные механизмы и положения объектно-ориентированного программирования 5.1. Инкапсуляция 5.1.1. Понятие класса и объекта 5.1.2. Структура класса Описание класса. Свойства и методы , Методы класса Перегружаемые методы Области видимости элементов класса Property-свойства 5.2. Наследование 5.2.1. Основы наследования 5.2.2. Переопределение методов Механизм переопределения Переопределение методов с сохранением функциональности 5.3. Жизненный цикл экземпляра класса 5.3.1. Создание экземпляра класса. Конструктор Описание и вызов конструктора Переопределение конструктора 5.3.2. Использование экземпляра класса 5.3.3. Разрушение объекта: Деструктор 5.4. Полиморфизм : 5.4.1. Совместимость объектов 5.4.2. Определение принадлежности к классу и приведение типов объектов Оператор is Оператор as 5.4.3. Абстрактные методы 5.4.4. Полиморфизм и ргорег1у-свойства 5.5. Ключевое слово Self ...
90 90 90 91 91 93 94 95 100 104 104 105 105 107 108 108 108 109 111 112 114 114 116 116 117 118 120 .. 122
Глава 6. Особенности архитектуры программы в ОС Windows 6.1. Архитектура программы в ОС DOS 6.2. Архитектура программы в ОС Windows Многозадачность Многооконность Программа, управляемая событиями Взаимодействие программы с операционной системой
123 123 124 124 125 126 128
Глава 7. Объектно-ориентированный подход к обработке ошибок 7.1. Понятие исключительной ситуации 7.2. Принудительное создание исключительной ситуации «Тихие» исключения Эмуляция ошибок 7.3. Обработка исключений Блок обработки исключений Try..Except Блок обработки исключений Try..Finally Различия в работе блоков обработки исключений 7.4. Традиционный подход к обработке ошибок
129 129 130 130 130 131 131 133 133 135
'.
Глава 8. Объектно-ориентированный подход к хранению информации 8.1. Общие положения 8.2. Списки 8.2.1. Основы использования списков Класс TList : Добавление элементов в список Удаление элементов из списка Доступ к элементам списка 8.2.2. Управление размером списка : 8.2.3. Пример использования списков указателей 8.2.4. Списки объектов 8.2.5. Списки компонентов .8.2.6. Списки с заданным порядком элементов. Стеки и очереди Понятие стека и очереди Возможности использования класса TOrderedList Стек и очередь как наследники класса TOrderedList 8.3. Коллекции 8.3.1. Основы использования коллекций 8.3.2. Создание и разрушение коллекций 8.3.3. Управление хранимыми данными Добавление элементов в коллекцию Удаление элементов из коллекции Доступ к элементам коллекции Практическое использование коллекций 8.4. Массивы текстовой информации 8.4.1. Общие возможности строковых массивов в Delphi 8.4.2. Манипуляции со строковыми массивами Добавление элементов Удаление элементов Доступ к элементам Сохраняемость строковых массивов 8.4.3. Практическое использование строковых массивов 8.5. Хранение сложных двоичных данных Глава 9. Объектно-ориентированный подход к вводу/выводу информации 9.1. Общие сведения о потоках 9.2. Базовый класс для работы с потоками Информация о состоянии устройства ввода/вывода Создание и разрушение потока Чтение и запись информации в поток
/
..'.
138 138 140 140 140 140 141 141 142 142 144 145 146 146 147 147 148 148 149 150 150 150 150 150 152 152 153 153 154 154 155 156 157 158 158 161 161 161 162
9.3. Особенности реализации разных потоков 9.3.1. Файловые потоки 9.3.2. Потоки на основе оперативной памяти 9.3.3. Строковые потоки
163 163 164 165
Глава 10. Использование графической информации в Delphi 10.1. Общие принципы вывода информации 10.2. Параметры вывода информации 10.2.1. Параметры графической информации Общие положения Цветовые характеристики Характеристики обводки Характеристики заливки , 10.2.2. Характеристики текстовой информации 10.3. Области отображения и вывод на них Класс TCanvas , Координатная плоскость Установка характеристик графических объектов Вывод текстовой информации Методы вывода графических примитивов Копирование областей отображения Методы вывода изображений Прямой доступ к растровому представлению Синхронизация области отображения Разновидности областей отображения. Метафайловые области отображения 10.4. Использование графических изображений 10.4.1. Представление изображений в Delphi. Базовый класс TGraphic Общее описание Представление растровых изображений. Класс TBitmap Представление сжатых растровых изображений. Класс TJPEGImage Представление Windows-иконок. Класс Tlcon 10.4.2. Метафайлы. Класс TMetafile Метафайлы и их особенности Области отображения метафайлов. Класс TMetafileCanvas 10.4.3. Представление изображений вне зависимости от формата. Класс TPicture 10.4.4. Пример использования графических изображений Вопросы с ответами для повторения по части II
166 166 168 168 168 169 170 172 173 174 174 174 175 176 178 179 180 181 181
ЧАСТЬ III. МЕТОДИКА СОЗДАНИЯ ПРОГРАММ В DELPHI
212
Глава 11. Использование ИСР Delphi в разработке 11.1. Интегрированная среда разработчика Delphi 11.2. Главное меню Подменю File Подменю Edit Подменю Search Подменю View Понятие проекта и группы проектов. Подменю Project Подменю Run 11.3. Компоненты. Палитра компонентов и формы 11.4. Свойства и события компонентов. Инспектор объектов Основы использования компонентов Понятие активного компонента Инспектор объектов
213 213 215 215 216 217 218
182 183 183 183 185 186 187 187 187 188 189 191 199
218 220 221 223 223 224 224
11.5. Окно исходного текста программы Окно навигации по коду Автодополнение кода
227 227 228
Глава 12. Общие принципы программирования в Delphi 12.1. Визуальное построение приложения 12.2. Автоматическое создание программного кода 12.3. Функциональность приложения 12.4. Обработка исключительных ситуаций Вопросы с ответами для повторения по части III
230 230 233 235 238 242
ЧАСТЬ IV. КОМПОНЕНТЫ И ИХ ИСПОЛЬЗОВАНИЕ
245
Глава 13. Архитектура системы компонентов Delphi 13.1. Класс TObject 13.1.1. Поддержка жизнедеятельности объектов Создание объекта Разрушение объекта 13.1.2. Реакция на создание и разрушение объектов 13.1.3. Информация об объектах и классах 13.1.4. Обработка событий объектов 13.1.5. Объектные интерфейсы Понятие интерфейса в Delphi Базовый интерфейс (Interface Глобальный уникальный идентификатор GUID Поддержка интерфейсов классом TObject 13.2. Класс TPersistent 13.2.1. Переносимость информации между объектами 13.2.2. Сохраняемость свойств 13.3. Класс TComponent 13.3.1. Именование компонентов и доступ к ним из программного кода Именование компонентов во время разработки Именование компонентов во время выполнения программы 13.3.2. Принадлежность компонентов 13.3.3. Взаимосвязи между компонентами Механизм взаимосвязей Механизмы уведомлений 13.4. Визуальные и невизуальные компоненты 13.4.1. Визуальные компоненты Базовый класс визуальных компонентов Компоненты-оболочки. Класс TWinControl Легковесные компоненты. Класс TGraphicControl Свойство визуальной принадлежности 13.4.2. Невизуальные компоненты 13.4.3. Диалоговые компоненты Вопросы с ответами для повторения по главе 13 Глава 14. Визуальные компоненты 14.1. Общие свойства визуальных компонентов : 14.1.1. Положение и размеры элемента управления на экране 14.1.2. Автоматическое управление положением Привязка визуального компонента к одной из сторон контейнера Настройка автоматического изменения положения стороны компонента в соответствии с такой же стороной его контейнера Задание минимально и максимально возможных размеров компонентов ;..'.
246 246 247 247 249 251 252 253 253 253 257 257 258 258 .....258 259 260 260 260 262 264 266 266 266 268 268 268 269 270 271 271 272 272 277 277 278 279 279 281 283
7
\-
.
14.1.3. Управление видимостью и доступом пользователя к управлению 283 14.1.4. Дружественное поведение элементов управления 284 Изменение вида курсора мыши при наведении на компонент 284 Использование всплывающих подсказок 285 14.2. Компоненты-контейнеры 285 14.2.1. Общее описание 285 14.2.2. Контейнеры-панели 287 Виды панелей и общее описание ; 287 Панель ScrollBox — панель с полосами прокрутки 288 Панель RadioGroup — панель зависимых переключателей 289 14.2.3. Контейнеры-панели инструментов 291 14.2.4. Страничное представление групп компонентов 292 14.2.5. Пример использования контейнеров 295 14.3. Компоненты для отображения информации 300 14.3.1. Виды компонентов для отображения информации 300 14.3.2. Текстовые метки 300 14.3.3. Компоненты-индикаторы 303 Компонент ProgressBar 304 Компонент Chart — построение диаграмм и графиков 305 14.3.4. Изображения геометрических фигур 306 Отображение разделительных рамок. Компонент Bevel 306 Изображение графических примитивов. Компонент Shape 307 14.3.5. Вывод сложной графической информации 308 Вывод изображений из графических файлов 308 Вывод видеоклипов 310 Отображение созданных программно изображений , 312 Использование виртуального экрана 314 14.4. Компоненты-кнопки 317 14.4.1. Виды кнопок 317 14.4.2. Кнопки Button и BitBtn : 317 Простые кнопки Button 317 Кнопки BitBtn — кнопки с изображениями 318 Использование стилей для кнопок BitBtn 321 14.4.3. Кнопки SpeedButton 321 14.5. Компоненты для редактирования простых данных 326 14.5.1. Ввод строк и чисел 327 14.5.2. Редактирование логических значений. Переключатели 335 14.5.3. Изменение числового значения в заданном диапазоне 338 Компонент ScrollBar (полоса скроллинга) 338 Компонент TrackBar — выбора числового значения из интервала ... 339 14.5.4. Выбор даты 341 14.6. Выбор значения с помощью списков 345 14.6.1. Виды компонентов выбора из списка 345 14.6.2. Компонент ListBox 347 14.6.3. Компонент CheckListBox 348 14.6.4. Компонент ComboBox 349 14.6.5. Компонент ColorBox 350 14.7. Компоненты для редактирования многострочных данных 352 14.7.1. Многострочные редакторы Memo и RichEdit 352 14.7.2. Особенности компонента RichEdit. RTF-формат 356 14.7.3. Использование форматирования 358 14.8. Компоненты для редактирования данных в табличной форме 360 14.8.1. Вид и возможности компонент редактирования данных в табличной форме 360 , 14.8.2. Структура классов табличных компонентов 362 14.8.3. Особенности компонентов StringGrid и DrawGrid 364 14.8.4. Компонент ValueListEditor и его особенности 366
14.9. Пример использования визуальных компонентов. Пишем текстовый-редактор 369 Создание заготовки панели инструментов 370 Наполнение подпанели управления шрифтом 373 Наполнение подпанелей Numbering и Undo/Redo 375 Наполнение подпанели управления отступами 375 Создание области редактирования и панели управления файлом ... 376 Дружественный интерфейс 377 Наполнение функциональностью 378 Вопросы с ответами для повторения по главе 14 390 Глава 15. Невизуальные компоненты
394
Глава 16. Создание компонентов во время выполнения программы 16.1. Основные действия, выполняемые при создании компонентов 16.2. Создание визуальных компонентов 16.3. Назначение обработчиков событий 16.4. Использование массивов компонентов Вопросы с ответами для повторения по главе 16
396 396 398 402 403 404
Глава 17. Использование диалоговых компонентов 17.1. Общие методы и события диалоговых компонентов 17.2. Общие свойства диалоговых компонентов 17.3. Стандартные диалоговые компоненты 17.4. Диалоги для работы с файлами 17.4.1. Свойства файловых диалогов Свойство InitialDir Свойство Filter Настройка функциональности диалогов Результаты работы файловых диалогов 17.4.2. События файловых диалогов 17.4.3. Пример использования файловых диалогов 17.5. Диалоги выбора шрифта и цвета 17.5.1. Работа с компонентом ColorDialog 17.5.2. Работа с компонентом FontDialog Свойства FontDialog События FontDialog 17.5.3. Пример использования диалогов выбора цвета и шрифта 17.6. Диалоги текстового поиска и замены 17.6.1. Работа с компонентами поиска и замены 17.6.2. Настройка функциональности диалогов FindDialog и ReplaceDialog 17.6.3. Анализ данных компонента FindDialog 17.6.4. Анализ данных компонента ReplaceDialog 17.6.5. Пример использования диалога замены 17.7. Диалоги настройки параметров печати 17.7.1. Компонент PrintDialog События PrintDialog Свойства PrintDialog ._. Настройка функциональности 17.7.2. Компонент PrinterSetupDialog 17.7.3. Компонент PageSetupDialog Свойства PageSetupDialog Настройка функциональности PageSetupDialog События PageSetupDialog Вопросы с ответами для повторения по главе 17
405 405 406 406 407 409 409 409 410 412 413 414 416 :...416 419 419 421 421 423 424
'.
425 426 426 427 430 431 431 432 433 434 434 435 436 436 438
Глава 18. Формы 18.1. Понятие формы. Форма как часть проекта 18.1.1. Форма как Delphi-компонент 18.1.2. Представление формы в проекте 18.2. Использование форм 18.2.1. Жизненный цикл формы Добавление формы в проект Организация структуры форм в приложении Автоматическое создание формы во время выполнения программы Прямое создание формы во время выполнения программы 18.2.3. Отображение формы Немодальные формы Модальные формы 18.2.4. Управление доступом к форме Скрытие формы с экрана : Разрушение формы Закрытие формы 18.3. Организация многооконных приложений Виды приложений с точки зрения организации окон SDI-приложения MDI-приложения Приоритетные окна Организация форм в многооконных приложениях 18.4. Свойства и события класса TForm 18.4.1. События форм Создание, разрушение и активность Перемещение и изменение размеров Манипуляции с мышью в области формы Клавиатурные события 18.4.2. Свойства форм Расположение и размеры Управление атрибутами окна Прозрачность окна 18.5. Особенности визуального проектирования форм 18.5.1. Расположение компонентов на форме Организация контейнеров переменного размера. Компонент Splitter Открепление элементов управления 18.5.2. Использование фреймов •Понятие фрейма Создание фрейма Добавление фрейма на форму Связи экземпляров фреймов 18.5.3. Депозитарий форм Добавление-формы в депозитарий Построение формы на основе хранимой в депозитарии Вопросы с ответами для повторения по главе 18
441 441 441 442 445 445 445 445 446 448 450 451 451 453 453 454 455 457 .457 458 459 460 461 463 463 463 464 466 467 469 469 471 471 474 474 474 477 478 479 480 480 480 481 481 482 483
ЧАСТЬ V. ВЗАИМОДЕЙСТВИЕ ПРИЛОЖЕНИЯ С ОПЕРАЦИОННОЙ СИСТЕМОЙ
486
Глава 19. Гибкое управление окружением 19.1. Введение 19.2. Глобальная переменная Application Управление быстрыми подсказками Взаимодействие с пользователем через простейшие диалоги Идентификация приложения в системе Информация об исполняемом файле
487 487 488 488 489 490 491
10
Управление состоянием приложения Особенности обработки событий в Windows и Delphi Распределение вычислительной нагрузки на приложение Событие необработанного исключения 19.3. Глобальная переменная Screen 19.4. Глобальная переменная Mouse Положение указателя мыши на экране Захват мыши окном приложения . Настройки колеса мыши Глава 20.Вывод информации за пределы программы. Технология СОМ 20.1. Вывод информации на печать Управление структурой документа , Вывод информации Контроль за процессом формирования документа Пример вывода информации на принтер Функции печати стандартных компонентов 20.2. Вывод информации в другие приложения. Основы технологии СОМ 20.2.1. Общие положения 20.2.2. Основы СОМ-технологии Основные понятия СОМ. СОМ-объекты Вызов методов СОМ-объектов. Интерфейс IDispatch Создание и использование экземпляров серверов автоматизации 20.2.3. Экспорт информации в приложения Microsoft Office 20.2.4. Экспорт информации в Microsoft Word Запуск сервера Взаимодействие с сервером на уровне документа Непосредственный вывод информации. Объект Selection Форматирование текстовой информации ..; Использование закладок Управление приложением Microsoft Word Экспорт текстовой информации из компонента RichEdit 20.2.5. Экспорт информации в Microsoft Excel Управление сервером автоматизации Excel Управление документами Excel Адресация элементов документа на рабочем листе Вывод информации в ячейки рабочего листа Пример вывода информации в приложение Excel Глава 21. Управление выполнением приложения 21.1. Создание и использование DLL-библиотек 21.1.1. Понятие и назначение DLL-библиотек 21.1.2. Создание DLL-библиотеки 21.1.3. Особенности передачи строковых параметров в подпрограммы DLL-библиотек 21.2.4. Наполнение библиотек подпрограммами 21.1.5. Использование DLL-библиотек. Виды динамической компоновки Неявная компоновка Явное связывание 21.1.6. Соглашения о вызовах подпрограмм 21.2. Многопоточность 21.2.1. Понятие и назначение потоков 21.2.2. Описание потока 21.2.3. Создание потока и управление его выполнением 21.2.4. Разрушение потока 21.2.5. Получение результата работы потока Использование VCL
492 492 495 498 499 500 500 500 501 502 502 503 504 505 505 506 507 507 508 508 509 511 512 514 514 516 517 518 520 521 522 526 527 527 527 528 528 531 531 531 532 534 535 536 537 538 539 540 540 541 543 544 546 546
11
Использование обработчика OnTerminate Использование дополнительных событий Использование глобальных переменных 21.2.6. Приоритеты потоков Вопросы с ответами для повторения по части V
548 548 549 550 550
ЧАСТЬ VI. СОЗДАНИЕ КОМПОНЕНТОВ
554
Глава 22. Основы использования компонентов 22.1. Понятие компонента 22.2. Назначение компонентов 22.2.1. Повторная используемость 22.2.2. Функциональность 22.2.3. Унификация программных продуктов 22.3. Описание компонента 22.3.1. Реализация модуля компонента 22.3.2. Использование пакетов компонентов
:
555 555 555 555 556 556 556 556 ...558
Глава 23. Жизненный цикл компонента в среде разработки 23.1. Создание компонентов 23.1.1. Создание компонентов вручную 23.1.2. Создание компонентов средствами среды разработчика.. 23.2. Управление компонентами 23.3. Удаление компонентов из Палитры компонентов 23.3.1. Использование диалога управления проектом 23.3.2. Удаление компонентов с помощью диалога управления пакетами компонентов
560 560 561 561 562 563 563
Глава 24. Разработка компонентов 24.1. Соглашение об именовании компонентов и их элементов 24.1.1. Именование компонентов 24.2.2. Именование свойств 24.2. Жизненный цикл компонента в программе 24.2.1. Компоненты, созданные в визуальном построителе 24.2.2. Компоненты, созданные программно 24.3. Структура компонента 23.3.1. Конструктор и деструктор 24.3.2. Простые свойства 24.3.3. Property-свойства Модификаторы Read и Write Модификатор Index Property-массивы Модификаторы stored и default Переопределение property-свойств. Модификатор nodefault 24.3.4. Сообщения и события в компонентах Модель сообщений в компонентах Сообщения Windows События компонентов Делегирование событий 24.4. Основы отладки компонентов 24.5. Пример разработки компонента Постановка задачи Создание группы проекта , Реализация компонента Отладка компонента Инсталляция компонента Использование компонента в визуальной разработке Вопросы с ответами для повторения по части VI
566 566 566 567 567 568 568 568 569 572 572 573 574 575 578 580 581 581 582 582 584 585 587 587 587 590 593 594 595 595
565
Введение
Общая характеристика Delphi В настоящее время программирование бурно развивается, как с точки зрения расширения круга решаемых им задач, так и с точки зрения существенного усложнения используемых в программировании технологий. Причем особо необходимо отметить немалые размеры разрабатываемых программных продуктов. Все это требует максимального упрощения и ускорения процесса разработки приложений и использования ранее реализованных программных фрагментов. Такие требования к современному программированию привели к созданию многочисленных RAD-систем (от англ. RAD — Rapid Application Development — быстрая разработка приложений), представляющих собой интегрированные среды разработчика, включающие в себя: » средства быстрого и удобного построения программ, в том числе визуального; » встроенные компиляторы и отладчики; * системы коллективной разработки проектов и т.д. Одной из таких RAD-систем является Delphi. Итак, Delphi — это объектно-ориентированная среда для визуального проектирования Windowsприложений с развитыми механизмами повторного использования программного кода. Основным конкурентом Delphi является среда разработки Microsoft Visual C++, имеющая свои преимущества и недостатки, однако являющаяся более популярной, в основном, в силу того, что разработана именно фирмой Microsoft1. Но, по нашему мнению, никак не в силу более широких возможностей.
Введение
Существенной чертой Delphi является компонентная модель разработки программных продуктов. Суть модели заключается в поддержке системой постоянно расширяемого набора объектных компонентов, из которых и строится программа. Компоненты в Delphi просты для использования и развития, как результат сокрытия значительной части той структуры программы, которая близка к взаимодействию с операционной системой. Таким образом, для создания в Delphi несложных программных продуктов совершенно не обязательно понимать внутреннюю структуру Windowsприложения, получаемого после разработки в Delphi. Достаточно просто уметь работать с некоторыми компонентами, поставляемыми вместе со средой разработчика. При этом начать работу со средой можно практически без предварительного ознакомления, а написание первого приложения не потребует углубления в особенности системы. Этому отчасти способствует удобный интерфейс среды разработчика, не перегруженный излишними вопросами к разработчику. Однако такой подход совершенно неприемлем для серьезного программирования, и, рано или поздно, придется освоить и основы программирования под ОС Windows, и серьезно изучить саму среду разработки Delphi, а также возможности, которые она предоставляет.1 Кроме того, конечно же, для создания качественных программных продуктов необходимо глубокое понимание компонентной модели.
Место Delphi в современном программировании Наиболее существенный отрыв Delphi от ближайших аналогов состоит в действительно быстрой разработке приложений, обладающих сложным пользовательским интерфейсом, особенно имеющим сильные взаимосвязи между элементами управления, расположенными в окнах программы. Также Delphi предлагает довольно мощный набор компонентов для работы с базами данных. Причем иерархия компонентов для работы с БД организована таким образом, что практически неважно, какой именно базой данных пользуется приложение — это может быть и локальная БД и промышленный сервер, типа Oracle или MS SQL Server. Существенным преимуществом Delphi в этой области является возможность управления базами данных на логическом уровне, соответствующем понятиям самих баз данных, без использования низкоуровневых запросов к драйверам. Такие возможности Delphi обусловили ее широкую применяемость при разработке АСУП — автоматизированных систем управления предприятиями. Однако это не единственная область применения, так как возможности Delphi не ограничиваются вышеперечисленными. Delphi является языком программирования широкого назначения и позволяет разработать программный продукт любой сложности для любой области. 14
Введение
Даже если какие-либо возможности и не поддерживаются напрямую, то этот недостаток может быть исправлен добавлением соответствующих компонентов в систему. Такой подход касается, например, технологии DirectX, не поддерживаемой Delphi в ее исходной комплектации, но существуют компоненты для использования DirectX, которые легко интегрируются в среду разработки. В любом случае, подпрограммы, реализованные в других Windows-языках программирования, могут быть использованы в Delphi через механизм динамически компонуемых библиотек (от англ. Dynamic Link Library — DLL — Динамически компонуемая библиотека). Заметим, что многие системные библиотеки Windows изначально подключены к Delphi, а вызов функций из них ничем не отличается от использования обычных библиотек Pascal. С появлением среды разработки Kylix под операционную систему Linux, полностью соответствующую Delphi за исключением некоторых аспектов, связанных с различиями в технологиях, используемых в этих операционных системах, часть приложений, написанных в Delphi, стала переносимой под Linux, что открывает еще более широкие возможности этой среды разработки.
Задачи издания Задачей нашей книги является максимально полное описание основ тех возможностей, которые определяют облик Delphi, чтобы дать читателю ясное представление об устройстве приложения, разработанного в этой среде. Основной упор сделан на изучение компонентной модели Delphi и ее строения, а также возможностей стандартных компонентов, поставляемых вместе со средой разработки. Книга предназначена для читателей, знакомых с языком программирования Turbo Pascal или с каким-либо другим структурным языком программирования, например, С. Также предполагается наличие некоторых знаний по объектно-ориентированному подходу к программированию. Мы не ставим перед собой задачу описания всех возможностей среды программирования Delphi, так как это практически невозможно сделать в рамках одной книги. Наша задача — помочь программистам, начинающим работать с Delphi, сделать первый шаг в ее изучении таким образом, чтобы не возвращаться к пройденному материалу в дальнейшем для углубления знаний. Основная часть материала описывает такие фундаментальные основы Delphi, как структура компонентов и правила их использования. Также подробно рассмотрено объектно-ориентированное программирование в Delphi вообще.
15
СТРУКТУРНОЕ ПРОГРАММИРОВАНИЕ В DELPHI
СТРУКТУРА ПРОГРАММЫ 2
3
СТРУКТУРЫ ДАННЫХ В DELPHI
СТРУКТУРНЫЕ ОПЕРАТОРЫ
ОПИСАНИЕ ПОДПРОГРАММ
Delphi — это объектно-ориентированная среда для визуального построения программных продуктов, основанная на языке Object Pascal, который является переработанной и существенно дополненной версией Turbo Pascal фирмы Borland. Программирование в Delphi состоит из двух основных этапов: 1. Визуальное построение программы на основе объектных компонентов и настройка их свойств, в результате чего можно быстро сформировать пользовательский интерфейс и обеспечить значительную долю функциональности приложения. 2. Написание программного кода на языке Object Pascal для обеспечения особой функциональности приложения, которую невозможно достичь использованием визуального построения. С момента первой реализации языка Pascal технологии программирования сделали огромный шаг вперед. И, несмотря на то, что Pascal тоже развивался, его последней версии, выпущенной фирмой Borland в 1990 году, существенно недостает возможностей, присущих другим современным языкам программирования. В среде Delphi используется обновленный вариант этого популярного языка. Изменения коснулись, прежде всего, объектно-ориентированной части Pascal, однако, казалось бы, вполне законченные инструменты структурного и модульного программирования также приобрели некоторые новые особенности. Попробуем кратко рассмотреть основные элементы языка программирования Pascal, обращая внимание на изменения, произошедшие с ним в Delphi. '
17
Структура программы
1.1. Основные элементы программы и алфавит языка Основными элементами программы на языке Object Pascal являются: » операторы - - команды, определяющие структуру программы (например, операторы ветвления и зацикливания) или выполняющие какие-либо действия (например, арифметические операции или операции сравнения); * ключевые слова — команды, используемые обычно для отделения одних частей программы от других. Ключевые слова предназначаются для компилятора, теряются в процессе построения исполняемой программы, и не выполняются в прямом смысле этого слова; » директивы компилятору — аналогичны ключевым словам, но воздействуют не на программу и ее структуру, а на процесс компиляции и построения программы. Часть директив компилятору можно установить с помощью главного меню интегрированной среды, однако использование директив в тексте программы помогает использовать их избирательно — не для всей программы, а для отдельных ее частей. Все вышеперечисленные элементы программы имеют свои уникальные имена, называемые идентификаторами, по которым и используются при написании программы. Для формирования идентификаторов используются следующие символы: » латинские алфавитные строчные и прописные: от «а» до «z» и от «А» до «Z», а также символ подчеркивания «_» и цифры от «О» до «9»; * знаки арифметических операций: «+» (сложение), «—» (вычитание), «*» (умножение), «/» (деление); 18
Глава 1. Структура программы
* операторы сравнения «», «=», «=»; • символы логической и арифметической группировки: «(» и «)»; » все команды в Object Pascal заканчиваются точкой с запятой. В Object Pascal используются следующие правила именования идентификаторов: » Идентификаторы должны представлять собой одно слово, состоящее из строчных или прописных латинских символов, причем строчные и прописные символы не различаются между собой. Также допустимо использование символа «подчеркивание» («_») и цифр в любом месте имени, за исключением его начала. » Два разных элемента программы не могут иметь одинаковые имена, то есть идентификатор должен быть уникальным в пределах программы. В случае попытки описания, например, двух переменных с одинаковыми именами, компилятором будет выдана ошибка Duplicate Identifier (повторяющийся идентификатор). В скобках будет указано имя переменной, которая описывается повторно. В исходный текст программы может быть добавлена текстовая информация, игнорируемая при компиляции. Такая информация называется комментариями и заключается в пары фигурных скобок { и }. Вместо фигурных скобок могут также использоваться пары символов (* и *): { Комментарий } (* Комментарий *)
В Delphi введена возможность использования однострочных комментариев, характерных для множества языков программирования. Такие комментарии начинаются двумя символами «слеш» и занимают всю оставшуюся строку: // До конца строки — комментарий
1.2. Основная часть программы Программа на Object Pascal, как и в более ранних версиях Pascal, состоит из основной части, собственно и называемой программой (англ. Program — Программа), и нескольких модулей (англ. Unit — Модуль), подключаемых на этапе компиляции к основной части программы, наличие которых не обязательно. Рассмотрим структуру основной части программы. Program
Заголовок программы
Uses 19
Часть I. Структурное программирование в Delphi Раздел п о д к л ю ч е н и я модулей
(библиотек)
Label Раздел описания меток безусловного перехода Const Раздел описания констант Type Раздел описания нестандартных типов данных
Var Begin
End.
Раздел Начало Раздел Конец
описания переменных раздела описания логики программы описания логики программы раздела описания логики программы
Основная часть программы содержится в отдельном файле и состоит из одного или нескольких разделов, каждый из которых начинается с определенного ключевого слова (Program, Uses, Label и т.д.). Если в существовании раздела нет необходимости, то ключевое слово, открывающее его, не указывается. Основная часть программы в Delphi имеет расширение dpr (от англ. DPr — Delphi Project -- Проект Delphi) и, в большинстве случаев, не требует вмешательства программиста. Создание и обновление основной части программы берет на себя среда разработки. Из всех представленных разделов обязательным в Pascal является только раздел описания логики программы (начинается ключевым словом Begin, заканчивается ключевым словом End с точкой). В программе на Delphi обязательным является также заголовок программы, имеющий вид: Program
;
Требование к наличию заголовка программы связано со ссылками на файлы ресурсов, подключаемых к программе, и имеющих такие же названия, что и файл, в котором хранится основная часть программы. Данные ссылки добавляются средой автоматически, а исправление их вручную не рекомендуется, так как это может вызвать нарушение структуры проекта. Название программы выбирается по обычным правилам именования идентификаторов в Turbo Pascal, описанным выше. Исполняемый ехефайл, получаемый в результате компиляции программы, имеет имя, соответствующее названию файла с программой. Разделы описания констант (начинается ключевым словом Const), нестандартных типов данных (ключевое слово Туре) и переменных (ключевое слово Var) могут следовать друг за другом в любом порядке, однако приведенный порядок является наиболее предпочтительным. Это связано с тем, что константы могут использоваться при описании типов данных и переменных, а типы данных обычно используются при описании переменных. При этом идентификаторы, описанные в разделе описания переменных, ни при каких условиях не могут использоваться в
Глава 1. Структура программы
разделах описания типов и констант. Напомним, что область действия идентификатора начинается с момента его описания, то есть использование идентификатора возможно только ниже по тексту программы 2 .
1.3. Модули Модуль (библиотека), так же как и основная часть программы, содержится в отдельном файле и состоит из нескольких разделов, аналогичных разделам основной части программы. Рассмотрим структуру модуля.
Unit Interface Uses
Заголовок модуля Указание на начало интерфейсной секции Раздел подключения модулей (библиотек)
Const Раздел описания констант Туре Раздел описания типов данных
Var Implementation Uses
Раздел описания переменных Указание на начало описательной секции Раздел подключения модулей
Label Раздел описания' меток безусловного перехода Const Раздел описания констант Туре Раздел описания типов данных
Var initialization finalization End.
Раздел описания переменных Начало секции инициализации Раздел описания логики инициализации Начало секции деинициализации Раздел описания логики деинициализации Окончание модуля
' Это замечание не касается так называемых «форвардных» описаний подпрограмм и классов, когда заголовок описания выносится выше по тексту программы для обеспечения возможности использования таких подпрограмм и классов выше их реального описания. Аналогична ситуация с подпрограммами, заголовки которых указаны в интерфейсных частях библиотек. Такие подпрограммы могут быть использованы в описательных частях библиотек вне зависимости от взаимного расположения их реальных описаний. 21
Часть I. Структурное программирование в Delphi
Исходный текст модуля может содержать четыре секции: » Интерфейсная секция (начинается ключевым словом interface), в которой располагаются заголовки процедур и функций, а также описания констант (раздел описания констант начинается с ключевого слова Const), нестандартных типов данных (ключевое слово Туре) и переменных (ключевое слово Var). Все идентификаторы, описанные в интерфейсной секции, доступны для использования вызывающим (подключающим модуль) частям программы, наряду с их собственными описаниями. » Описательная секция (начинается ключевым словом Implementation), в которой располагаются описания процедур и функций, заголовки которых указаны в интерфейсной секции, а также другие процедуры и функции, используемые подпрограммами данного модуля, но не доступные фрагментам программ, подключающим модуль. Также в описательной части модуля могут располагаться описания меток безусловного перехода, констант, нестандартных типов данных и переменных (начинаются, соответственно, с ключевых слов Label, Const, Type и Var). Все эти описания также не доступны фрагментам программы, подключившим модуль, и используются только для внутренних целей библиотеки. « Секция инициализации (начинается ключевым словом initialization), содержащая команды, которые необходимо выполнить при подключении модуля к программе. Таким образом, перед выполнением какой-либо программы, к которой подключаются внешние модули, сначала выполняются инициализационные части модулей (в порядке подключения). Данная секция не является обязательной. » Секция деинициализации, используемая только при наличии секции инициализации, содержащая команды, которые необходимо выполнить при завершении приложения. Данная секция не является обязательной и может использоваться для освобождения ресурсов, занятых в секции инициализации. Наличие ключевого слова End с точкой является обязательным и означает окончание модуля. Наличие интерфейсной и описательных секций в библиотеке является обязательным, даже, если в этих секциях ничего не содержится3. Таким образом, ключевые слова Interface и Implementation должны присутствовать в тексте модуля всегда. Название модуля — идентификатор, указываемый после ключевого слова unit в заголовке модуля. Название модуля строится по обычным правилам именования идентификаторов и должно соответствовать имени Заметим, что отсутствие интерфейсной секции исключает какое-либо полезное использование библиотеки, так как подключающий модуль не может обратиться к элементам такой библиотеки. 22
Глава 1. Структура программы
файла, в котором находится описания модуля. Именно по названию осуществляется подключение модулей к основной части программы или к другим модулям. Как уже упоминалось ранее, подключение производится в разделе подключения модулей Uses по названию. Приведем пример простейшего модуля4 и программы, которая подключает к себе этот модуль. Код модуля приведен в листинге 1.1, а код подключающей его программы — в листинге 1.2. Листинг 1.1 .Простейший модуль Unit Unitl; Interface Implementation End.
•f-,^
{Заголовок модуля, название модуля — U n i t l } (Указание на начало интерфейсной секции} {Указание на начало описательной секции} {Окончание модуля}
Листинг 1.2. Простейшая программа, подключающая модуль '• . " • . • ' . . . " ' • . : . . ' .
•
....: ; {описание "'поля» записи} {окончание описания типа}
Таким образом, запись, как переменная, состоит из набора разнородных переменных, называемых полями записи, каждая из которых имеет свое уникальное имя в пределах записи. В качестве Типов переменных, описываемых внутри записи, могут использоваться любые типы данных, встроенные в Delphi или описанные в программе до описания данной записи, в том числе массивы и другие записи. Описание переменных-записей, или, как их принято называть, экземпляров записей, осуществляется по обычным правилам в разделе описания переменных Var. Обращаться к экземплярам записей в программе можно двумя способами — как к совокупностям полей, то есть к записям в целом, так и к отдельным полям конкретной записи. Для работы с записями в целом определена всего одна операция — присвоение. При этом поля одной записи присваиваются полям другой записи, то есть копируются в них. Для обращения к какому-либо конкретному полю записи используется имя этой записи и имя поля в его составе, разделенные точкой: .
С полями экземпляров записей можно производить любые операции, допустимые для типа данных этого поля, то есть указывать в качестве параметров процедур и функций, использовать как операнды для арифметических и логических операторов, и так далее. Приведем небольшой пример (листинг 2.5).
3
В языке С такие типы данных называют структурами.
34
Глава 2. Структуры данных в Delphi Листинг 2.5. Работа с записями Program UsingRecords; Туре {начало раздела,описания типов данных} Thuman = Record {заголовок описания записи} FirstName: String; {описание поля записи с именем FirstName} ( Lastame: String; {описание поля записи с именем LastName} Age: Integer; {описание поля записи с именем Age) end; {окончание описания типа} Var Humanl: Thuman; {Описание переменной -- записи типа THuman с именем Humanl} Human2: Thuman; {Описание переменной — записи типа THuman с именем Human2} Begin Human1.FirstName := 'Michael'; {Изменение поля FirstName переменной Humanl} Ншпап2 := Humanl; {Копирование значений всех полей записи Humanl в поля записи Human2. Таким образом поле FirstName записи Human2 станет иметь значение 'Michael'} End.
2.3.5. Массивы Массив — это поименованная область памяти, доступ к отдельным частям которой осуществляется по общему имени и индексу соответствующей части. Все части (элементы) массива имеют один и тот же тип, в качестве которого может выступать любой из стандартных или нестандартных типов, описанных к моменту объявления массива. В частности, элементы массива также могут являться массивами. Такие структуры называются многомерными. Описание массива производится в разделе описания типов данных (после ключевого слова Туре) и выглядит следующим образом: = Array [..] Of ;
35
Часть I. Структурное программирование в Delphi
Например: Program DeclareArrayTypesl ; Туре {Начало раздела описания типов данных} MyArray = Array [5..150] Of Integer; {Описание типа данных — массива с именем MyArray, состоящего из 146-ти элементов, пронумерованных от 5-ти до 150-ти, каждый из которых имеет тип Integer} Begin End.
С момента такого описания тип MyArray может использоваться наравне с остальными типами данных, известными компилятору, например, для описания двухмерного массива: Program DeclareArrayTypes2; Туре {Начало раздела описания типов данных} MyArray = Array [5..150] Of Integer; {Описание типа данных — массива с именем MyArray, состоящего из 146-ти элементов, пронумерованных от 5-ти до 150-ти, каждый из которых имеет тип Integer} MyArray2D = Array [1..10] Of MyArray; {Описание типа данных — массива с именем MyArray2D, состоящего из 10-ти элементов, пронумерованных от 1 до 10-ти, каждый из которых представляет собой массив типа MyArray} Begin End. • Многомерный массив можно также описать без использования вспомогательных типов, просто указав в квадратных скобках через запятую пределы изменения индексов для каждого измерения массива: Туре MyArray2D = Array [1..10, 5..150] Of Integer; {Описание двухмерного массива}
' Описание переменной, представляющей собой массив, происходит по обычным правилам описания переменных: Туре MyArray2D = Array [1..10, 5..150] Of Integer; {Описание двухмерного массива} Var Array2D: MyArray2D; {Описание переменной-массива) . 36
Глава 2. Структуры данных в Delphi Для обращения к элементу массива-переменной, ее индекс (индексы в случае многомерных массивов) указываются в квадратных скобках после имени переменной: Program UsingArrays; Type MyArray2D = Array [ 1 . . 1 0 , 5 . . 1 5 0 ] Of Integer; {Описание двухмерного массива}. Var
Array2D: MyArray2D; {Описание переменной-массива} Begin Array2D [1, 6] := 18; {Присвоение значение элементу массива с индексами 1, 6} End.
Обратите внимание, что максимально возможный размер массивов в Delphi увеличен по сравнению с Pascal, и может достигать 2 гигабайт вместо 64 килобайт. Заметим, что для массивов одного и того же типа, как и для записей, определена операция присвоения, выполняемая копированием значений элементов одного массива в другой. Также доступна операция присвоения для отдельных элементов массивов. Сравнение массивов в целом невозможно и вызывает ошибку компиляции. Однако это доступно динамическим массивам, описанным ниже. Переменную-массив можно описать и без предварительного создания соответствующего типа данных, прямо в разделе описания переменных:
Var MyArray2D: Array [ 1 . . 1 0 , 5 . . 1 5 0 ] Of Integer; {Описание переменной — двухмерного массива целочисленных элементов} Необходимо отметить, что при внешне эквивалентном описании два массива не будут считаться принадлежащими к одному типу данных, если это не указано явно: Туре ArrType = Array [1..10] Of Integer; {Описание типа данных — массива целочисленных элементов} Var Al
Array [1..10] Of Integer; {Описание переменной-массива целочисленных элементов} 37
Часть I. Структурное программирование в Delphi А2 = Array [ 1 . . 1 0 ] Of Integer; {Описание переменной-массива целочисленных элементов, полностью идентичной А 1 } A 3 : АггТуре; {Описание переменной-массива целочисленных элементов} А 4 : АггТуре; Begin
A3 := А 4 ;
А1
:= А2;
{Операция допустима, так как обе переменные имеют один и тот же тип — А г г Т у р е } {Операция не допустима, так как обе переменные имеют не один и тот же тип данных, а лишь похожие по структуре типы}
В рассмотренном примере все четыре используемые переменные являются одномерными массивами целочисленных элементов, проиндексированных от 1 до 10, однако переменные A3 и А4 совместимы между собой, так как при их описании явно указан один и тот же тип, а переменные А1 и А2 не совместимы ни между собой, ни с переменными A3 и А4. Соответственно при попытке компиляции такого фрагмента программы будет выдана ошибка "Incompatible types» - "Несовместимость типов». Однако если бы переменные А1 и А 2 были описаны в одной строке, то их типы считались бы идентичными: Var
Al, A2 = Array [1..10] Of Integer; {Описание двух переменных-массивов целочисленных элементов} Begin Al := А2;
38
{Операция допустима}
Глава 2. Структуры данных в Delphi
2.4. Динамические структуры данных Для работы с динамическими структурами данных Turbo Pascal предлагал два вида ссылочных типов — типизированные указатели и нетипизированные. Нетипизированный указатель представляет собой переменную, в которой хранится адрес некоторой области памяти некоторого размера, и предназначен для хранения произвольных данных. Типизированные ссылки указывают на место в памяти, где хранятся данные определенного типа. В Delphi сохранены все возможности работы с указателями, а также добавлены новые структуры данных на их основе.
2.4.1. Нетипизированные указатели Переменные -- нетипизированные указатели описываются с указанием типа Pointer, а выделение и освобождение памяти под них осуществляется, соответсвенно, командами GetMem и FreeMem (см. листинг 2.6). Листинг 2.6. Использование нетипизированных переменных-указателей Program UsingPointers , Var Point: Pointer;
Begin GetMem(Point, 1024) FreeMem(Point, 1024)
{Описание переменной-указателя, указан тип Pointer, то есть описывается нетипизированная ссылка] {Выделение памяти под переменнуюуказатель размером 1024 байт} {Освобождение памяти, занятой под переменную-указатель}
End.
2.4.2. Типизированные указатели Использование нетипизированных указателей ограничено стандартными функциями, принимающими такие переменные в качестве параметров, а также низкоуровневым программированием. Более интересными для рассмотрения являются типизированные указатели. Для описания типизированной ссылки не предусмотрен какой-либо специальный тип данных, в отличие от нетипизированных указателей, имеющих тип Pointer. Поскольку ссылочная переменная такого рода всегда указывает на данные конкретного типа, то ее описание и строится на основе этого типа. Для указания на ссылочную природу переменных используется оператор « л », и описание выглядит следующим образом: 39
Часть I. Структурное программирование в Delphi
Var : л;
Или в разделе описания типов данных: Туре =
Л
;
После описания переменной-указателя под нее выделяется память только для хранения адреса, а под сами данные, на которые переменная указывает, память не выделяется. Для инициализации переменной используется процедура N e w , отличием которой от аналогичной процедуры GetMem, используемой для работы с нетипизированными указателями, является отсутствие второго параметра, определяющего размер выделяемой памяти. Это связано с тем, что типизированная ссылка указывает на данные известного типа, соответственно, размер этих данных также известен компилятору. Итак, инициализация переменной выглядит следующим образом: Ыеуг() ;
Соответственно, при освобождении памяти, занятой под типизированный указатель, используется процедура Dispose — аналог процедуры FreeMem без второго параметра: Dispose( ) ;
Обращение к переменной-указателю происходит обычным образом — по ее имени, а для обращения к данным, на которые переменная указывает, после имени переменной указывается оператор разыменовывания «л»: А
Рассмотрим пример работы с типизированной ссылкой, указывающей на переменную типа Double (см. листинг 2.7).
Листинг 2.7. Пример работы с типизированной ссылкой Program UsingTypedPointers; Type pDouble = A Double; {Описание типа данных — указателя на переменную типа Double} Var MyPDouble: pDouble; {Описание переменной — типизированного указателя на переменную типа Double}
40
Глава 2. Структуры данных в Delphi Begin New(MyPDouble) ; {Выделение места в динамической памяти под одну переменную типа Double (размер необходимой памяти определяется автоматически) , адрес выделенной памяти заносится в переменную MyPDouble} MyPDoubleA = 12.8; {Присвоение переменной, на которую ссылается переменная-указатель значения 12.8} Dispose(MyPDouble) ; {Освобождение памяти, занятой под переменную, на которую указывает переменная MyPDouble. Адрес освобожденной памяти остается в переменной MyPDouble, но его использование недопустимо} End.
2.4.3. Динамические массивы В Delphi добавлена интересная возможность описания массивов без указания размерностей и, соответственно, пределов изменения индексов:
Var I n t A r r a y : Array Of Integer;
Такие массивы являются динамическими6 и изначально имеют нулевую длину. Установка размера массива и определение его во время выполнения программы производится так же как и для строк, с помощью функций SetLength и Length, соответственно. Элементы в данном случае нумеруются от нуля. Program UsingDynamicArraysl ; Var А, В: Array of Integer; {Описание двух переменных — динамических массивов целочисленных элементов} Begin SetLength(A, 5 ) ; {Установка размера массива А (5 э л е м е н т о в ) } А[0] := 1; {Присвоение значения 1 элементу массива А с номером 0 } End. Заметим, что в Turbo Pascal также существовала возможность использования динамических массивов через типизированные указатели, под которые ^память выделяется процедурой FreeMem, а не New. Однако о безопасности использования таких структур должен был заботиться программист. Также неудобства доставляла необходимость указания размерностей таких массивов и ограничение максимального числа элементов. В Object Pascal реализована более удобная схема работы с динамическими массивами. 41
Часть I. Структурное программирование в Delphi
Переменные-динамические массивы являются указателями и операции с ними производятся как с указателями. Например, при присвоении одного массива другому элементы одного массива не копируются во второй, а копируется адрес массива. Соответственно, сравнение двух массивов в логических выражениях типа «равно — не равно» производится сравнением адресов. Пример присвоения одного массива другому приведен в листинге 2.8. Листинг 2.8. Пример присвоения одного массива другому Program UsingDynamicArrays2; Var А, В: Array of Integer; {Описание двух переменных — динамических массивов целочисленных элементов} Begin SetLength(A, 5 ) ; { У с т а н о в к а размера массива А (5 э л е м е н т о в ) } А [ 0 ] := 14; {Присвоение значения 14 нулевому элементу массива А} В := А; {Присвоение массива А массиву В, теперь переменные А и В указывают на один и тот же массив} В[0] := 2; {Присвоение нулевому элементу массива В значения 2, теперь нулевой элемент массива А также имеет значение 2} End.
Отметим существенное отличие в работе со строками и динамическими массивами, имеющими одинаковое внутреннее представление на основе указателей, но разные методы работы. Две разные строки, состоящие из одинакового набора символов, считаются равными, а два разных массива, содержащие одинаковые элементы, не равны. Приведем пример: Var
SI, S2: AnsiString; {Описание двух строковых переменных типа AnsiString} Al, A2: Array Of Integer; {Описание двух переменных — динамических массивов целочисленных элементов}
42
Глава 2. Структуры данных в Delphi
Begin 51 := 'ABCDEF' ; {Присвоение строковой переменной SI значения} A 52 := ABCDEF'; (Присвоение строковой переменной S2 значения, аналогичного значению переменной S1. Теперь переменные S1 и S2 равны} SetLength(А1, 2); (Установка размера массиву А1} SetLength(А2, 2); (Установка массиву А2 размера, равного размеру массива А1} А1[0] := 0; А1[1] := 1; А2[0] := 0; А2[1] := 1; (Заполнение массивов А1 и А2 одинаковыми элементами. Теперь массивы имеют одинаковые длины и состоят из одинаковых элементов, однако не равны между собой, так как находятся в разных местах памяти} А2 := А1; (Присвоение ссылки на массив А1 в переменную А2. Теперь массивы А1 и А2 равны, так как переменные А1 и А2 указывают на одно и то же место в памяти}
2.5. Вариантные структуры данных •
2.5.1. Общие понятия
Структура данных программы на языке Pascal (то есть переменные) описывается в одном месте каждого модуля — в разделе описания переменных. Данный раздел начинается ключевым словом Var и специально предназначен для этого. Таким образом, переменные не могут быть введены во время выполнения программы. Еще одна существенная особенность представления информации в Pascal — это назначение типа переменной на этапе компиляции. Однако при программировании под Windows довольно часто встречаются задачи, в которых переменные должны изменять свой тип во время выполнения программы, соответственно, нельзя на этапе написания программы указать, какого именно типа должна быть та или иная переменная. В основном такие возможности требуются от программы, работающей с базами данных и использующей технологию СОМ. 43
Часть 1. Структурное программирование в Delphi
В Delphi введена поддержка переменных, которые не имеют конкретного типа данных на этапе компиляции, а в процессе выполнения программы могут хранить данные разных типов. Такие переменные называются вариантными (англ. Variant — вариантный, различный) и описываются по обычным правилам с указанием в качестве типа данных ключевого слова Variant: Var :
Variant;
Во время выполнения программы в вариантных переменных могут храниться данные любого типа, за исключением структурных (записей и статических массивов) и ссылочных (типизированные и нетипизирован7 ные указатели, метаклассы и указатели на экземпляры классов ). Также в вариантных переменных не могут находиться значения множественных типов данных. Неинициализированной переменной автоматически присваивается специальная константа Unassigned, описанная в модуле Variants8. Заметим, что для хранения одних и тех же данных вариантным переменным потребуется больше места в памяти, чем типизированным переменным. Кроме того, операции над вариантными переменными выполняются несколько дольше. Поэтому применение вариантов оправдано только в тех случаях, когда невозможно обойтись обычными переменными.
2.5.2. Обращение к вариантным переменным Обращение к переменным типа V a r i a n t ничем не отличается от работы с переменными других типов данных, за исключением того, что во время компиляции не выполняется проверка на совместимость типов. Кстати говоря, это может вызывать ошибки времени выполнения программы. Причем такие ошибки носят динамический характер, могут проявляться редко и при трудно определяемых условиях, что еще больше усугубляется оптимизатором компиляции Delphi и автоматическим приведением типов вариантных данных. Рассмотрим пример описания вариантной переменной и ее использования в программе (см. листинг 2.9).
Интересно, что значением вариантной переменной может являться указатель на объектный интерфейс. Интерфейсы подробно рассмотрены ниже при обсуждении структуры компонентов Delphi. В Delphi предусмотрено аналогичное значение для вариантных переменных, возвращаемое функцией Null, и показывающее, что в переменной нет данных. Однако некоторые операции с переменными, в которых находятся такие значения, будут приводить к ошибкам времени выполнения.
44
Глава 2. Структуры данных в Delphi Листинг 2.9. Пример описания и использования вариантной переменной Program UsingVariantsl ;
Var
V: Variant;{Описание вариантной переменной, тип переменной не определен} S: String; (Описание переменной типа S t r i n g } Begin V := 250; {Присвоение значения вариантной переменной V, переменной назначается целочисленный тип данных} V := 'It is a STRING' ; {Присвоение значения вариантной переменной V, переменной назначается строковый тип данных} S := V; {Строковой переменной присваивается строковое значение вариантной переменной} End.
2.5.3. Определение типа вариантных переменных Переменная типа Variant занимает в памяти шестнадцать байт, в которых находится информация о типе данных (код типа), хранящихся в данный момент в данной переменной и сами данные, либо указатель на них. Возможные коды типов данных для вариантных переменных, описанные в модуле System, представлены в табл. 2.4. Для определения типа данных, хранимых в вариантной переменной, используется функция VarType, описанная в модуле V a r i a n t s в следующем виде: Function V a r T y p e ( c o n s t V : V a r i a n t ) ;
TVarType;
Возвращаемое функцией VarType значение имеет тип аналогичный типу W o r d и содержит информацию о типе данных, а также о том, является ли значение, хранимое в переменной, массивом или указателем. Младшие двенадцать бит (три последние шестнадцатеричные цифры) этого значения содержат код типа, соответствующий константам, приведенным в табл. 2.4, а в старших четырех битах могут находиться следующие значения: * нулевое значение $0000 — указывает на то, что данные заявленного типа хранятся непосредственно в вариантной переменной; * значение $2000 (константа varArray модуля System) — указывает на то, что хранимые данные являются массивом элементов, имеющих тип, определяемый кодом типа данных; * значение $4000 (константа varByRef модуля System) — указывает на то, что хранимые данные являются ссылкой на соответствующий тип данных, определяемый кодом типа данных. 45
Часть I. Структурное программирование в Delphi Коды типов вариантных переменных Константа
Таблица 2.4 Тип хранимых данных
Значение
varEmpty
Значение не определено
$000
varNull
Пустое значение
$001
varSmallint
Smallint
$002
varlnteger
Integer
$003
varSingle
Single
$004
varDouble
Double
varCurrency
Currency
$006
varDate
TDateTime
$007
varQIeStr
Ссылка на AnsiString
$008
varDispalch
Ссылка на интерфейс IDispatch
$009
varBoolean
WordBool
$ООВ
varVariant
Variant
$ООС
varllnknown
Ссылка на интерфейс (Interface или lUnknown
$000
varShortlnt
Shortlnt
$010
varByte
Byte
$011
varWord
Word
$012
varLongWord
LongWord
$013
varlnt64
Int64
v
$014
varStrArg
Ссылка на строку, совместимую с СОМ
$048
varString
Ссылка на строку, не совместимую с СОМ
$100
' $005
Для выделения части значения, содержащей код типа данных, предусмотрена константа varTypeMask (значение $OFFF), с помощью которой можно отделить код типа данных от дополнительных признаков: and varTypeMask В качестве примера использования описанных выше методов приведем фрагмент программы, формирующий строку S, содержащую подробную информацию о типе вариантной переменной V9. S := 'Data element type: '; Case V a r T y p e ( V ) and varTypeMask of varEmpty: S := S + 'The variant is Unassigned' varNull: S := S + 'The v a r i a n t is N u l l ' ;
' Использование оператора множественного выбора Case..Of..End описано ниже, в разделе, посвященном СТРУКТУРНЫМ операторам ветвления. 46
Глава 2. Структуры данных в Delphi varSraallint:
S := S +
varlnteger:
S
:= S +
varSingle:
S
:= S +
varDouble:
S
:= S +
varCurrency:
S
:= S +
varDate:
:= S +
varOleStr:
:= S +
varDispatch:
S
:= S +
varBoolean: varVariant: varUnknown:
:= S + := S + := S +
varShortlnt:
:= S +
varByte: varWord: varLongWord:
S := S + S := S + S := S +
varlnt64: varStrArg: varString:
:= S + := S + := S +
'16-bit signed integer (type Smallint)'; '32-bit signed integer (type Integer)'; 'Single-precision floating-point value (type Single)'; 'Double-precision floating-point value (type Double)'; 'Currency floating-point value (type Currency)'; 'Date and time value (type TDateTime) ' ; 'Reference to a dynamically allocated UNICODE string'; 'Reference to an Automation object'; '16-bit boolean (type WordBool)'; 'A variant' ; 'Reference to an unknown OLE object'; '8-bit signed integer (type Shortlnt)'; 'A Byte'; 'unsigned 16-bit value (Word)'; 'unsigned 32-bit value (LongWord)'; '64-bit signed integer ( I n t 6 4 ) ' ; 'COM-compatible s t r i n g ' ; 'Reference to a dynamically . allocated s t r i n g ' ;
end; if (VarType(V) and v a r A r r a y ) = v a r A r r a y then S := S + '; It is an array'; if (VarType(V) and v a r B y R e f ) = varByRef then . S := S + '; It is a reference'
Таким образом, если в переменной v находится значение 200, то после выполнения данного фрагмента программы значением переменной s будет строка 'Data element type: A Byte', если переменной V задать значение 1221.66, то в переменной S будет строка 'Data element type: Currency f l o a t i n g - p o i n t value (type C u r r e n c y ) ' . Если же значение переменной V будет строковым, например, '1221.66', то значением переменной s будет такая строка: 'Data element type: Reference to a dynamically allocated s t r i n g ' . 47
Часть I. Структурное программирование в Delphi
2.5.4. Автоматическое приведение типов Интересной особенностью вариантных переменных является то, что во время выполнения программы их тип автоматически приводится для соответствия смыслу выражения. Естественно, это правило верно не для всех типов вариантных переменных, а только для тех, в которых хранятся числовые (целочисленные и вещественные), строковые, символьные и логические значения. Допустим, например, что в программе имеется вариантная переменная vi, которой присвоено значение 200. После присвоения переменная VI будет целочисленной (иметь тип с кодом varByte): Vi := 2 0 0 ;
Допустим также, что имеется переменная S типа string. Тогда в результате выполнения оператора присваивания S := vi, значением переменной S будет строка , Х 2 0 0 ' . Таким образом, во время выполнения оператора значение целочисленной переменной будет автоматически переведено в строковый вид. Более того, возможна и обратная ситуация, когда вариантная переменная строкового типа .автоматически переводится в численную форму. Однако именно в таких ситуациях могут возникать ошибки времени выполнения. Рассмотрим пример безошибочного использования вариантной переменной (листинг 2.10).
Листинг 2.10.
Пример безошибочного использования вариантной переменной
Program UsingVariants2;
Var V: Variant; I: Integer; Begin V := ' 2 8 0 ' ;
I
:= V;
{Описание вариантной переменной, тип переменной не определен} {Описание переменной типа String} {Присвоение значения вариантной переменной V, переменной н а з н а ч а е т с я строковый тип данных} {Целочисленной переменной I присваивается значение вариантной переменной V. Значение переменной I становится равным 2 8 0 }
End.
Но значением переменной V могла бы быть не строка '280', а, например, строка Л а 2 8 0 ' -- см. листинг 2.11. 48
Глава 2. Структуры данных в Delphi Листинг 2.11. Пример возникновения ошибочной ситуации Program UsingVariantsS; Var V : Variant; {Описание вариантной переменной, тип переменной не определен) I : Integer; (Описание переменной типа S t r i n g } Begin V := Л а 2 8 0 ' ; {Присвоение значения вариантной переменной V, переменной назначается строковый тип данных} I := V; {Ошибка времени выполнения — несоответствие типов, так как из строковой вариантной переменной невозможно выделить целочисленное значение} End.
Выполнение такой программы невозможно и будет прервано с сообщением об ошибке: "Could not convert variant of type (String) into type ( I n t e g e r ) " — "Невозможно привести тип String к типу integer". Эта ошибка появляется не из-за невозможности преобразовать строку в числовое значение вообще, а из-за невозможности выделить целое число именно из этой строки. Правила трансформации вариантных значений при использовании их в выражениях приведены в табл. 2.5. Правила изменения вариантных значений Тип переменной источника Целочисленные Вещественные
Таблица 2.5 Тип переменной-приемника
Целочисленные
Вещественные
Строковые
без изменения
без изменения
Перевод в строку
округление
без изменения
Перевод в строку
Логические 0 -> False, иначе True 0 -> False, иначе True "false" -> False "true" -> True
Строковые
выделение числа с округлением
выделение числа
без изменения
если можно выделить число: 0 -> False иначе True
Логические Значение Unassigned Значение Null
False -> 'О1
False -> '0'
False -> '0'
True -> '-Г
True ->'-1'
True -> '-Г
0
0
Пустая строка
без изменения False
Сообщение об ошибке
49
Часть I. Структурное программирование в Delphi
2.5.5. Вариантные массивы Как было отмечено выше, значением вариантной переменной не может являться массив, описанный по обычным правилам. Однако Delphi предлагает специальный вариантный массив, который можно создать с помощью функции VarArrayCreate, описанной в модуле V a r i a n t s : Function V a r A r r a y C r e a t e ( c o n s t B o u n d s : VarType: TVarType): Variant;
array of
Integer;
Параметр Bounds является массивом целочисленных значений, каждая пара которых определяет пределы изменения индексов. Количество измерений массива, соответственно, определяется количеством пар значений в этом массиве. Вместо массива, заданного переменной, можно использовать конструкцию следующего вида: [, < А 2 , В2>, ..., ]
Вторым параметром задается тип элементов массива. При этом могут использоваться любые константы, приведенные в табл. 2.4, за исключением varString, однако для формирования массива строковых элементов допустимо использование константы varOleStr.
-»~ -*-. -*-*~
1 65 П\4\ 50 80 0 14\3\ 55 11
-*~ -+-*-*-
7 |65|/2| 4\ Intel Pentium True \ 16800 \
Рис. 2.1. Непрямоугольный однородный массив (слева) и неоднородный массив (справа)
Заметим, что вне зависимости от количества измерений массива все его элементы имеют один и тот же тип, указываемый при вызове функции V a r A r r a y C r e a t e , но если в качестве типа элементов задать константу varVariant, то элементом вариантного массива будет также являться вариант. Следовательно, такому элементу можно присвоить вариантный массив, причем тип его элементов также может быть произвольным. Таким образом, в Delphi появилась возможность создания неоднородных (состоящих из элементов разного типа) массивов, или, например, однородных массивов непрямоугольной формы. Для получения информации о размерностях и индексации вариантных массивов в модуле Variants предусмотрены следующие функции: * VarArrayDimCount — возвращает размерность заданного вариантного масива. 50
Глава 2. Структуры данных в Delphi
» VarArrayLowBound — возвращает нижнюю границу индексов заданного вариантного массива для заданного измерения. » VarArrayHighBound — возвращает верхнюю границу индексов заданного вариантного массива для заданного измерения. Описание этих функций выглядит следующим образом: Function V a r A r r a y D i m C o u n t ( c o n s t A : V a r i a n t ) : Integer; Function V a r A r r a y L o w B o u n d ( c o n s t A : V a r i a n t ; D i m : I n t e g e r ) : Integer; Function VarArrayHighBound(const A: V a r i a n t ; D i m : I n t e g e r ) : Integer;
Пример использования и тех, и других приведен в листинге 2.12.
Листинг 2.12. Работа с вариантными массивами
Var V: Variant; {Описание вариантной переменной} LI, HI, L2, Н2: Integer; {Описание четырех целочисленных переменных} Begin V := VarArrayCreate([0, 5, 3, 8], varlnteger); {Создание двухмерного вариантного массива целочисленных элементов, аналогичного по описанию массиву Array[0..5, 3..8] Of Integer} LI := VarArrayLowBound(V, 1); {Определение минимально возможного индекса элемента для первого измерения вариантного массива V и занесение его в переменную L1 (значение 0) } HI := VarArrayHighBound(V, 1); {Определение максимально возможного индекса элемента для первого измерения вариантного массива V и занесение его в переменную HI (значение 5)} L2 := VarArrayLowBound(V, 2); {Определение минимально возможного индекса элемента для второго измерения вариантного массива V и занесение его в переменную L2 (значение 3)}
51
Часть I. Структурное программирование в Delphi Н2 := VarArrayHighBound(V, 2 ) ; {Определение максимально возможного индекса элемента для второго измерения вариантного массива V и занесение его в переменную Н2 (значение 8) }
2.5.6. Разрушение вариантных переменных При описании вариантной переменной, а также при создании вариантного массива выделяется некоторое количество оперативной памяти. В случае использования локальных вариантных переменных память возвращается системе при достижении программой конца области видимости переменной. В случае глобальных переменных разрушение структур данных происходит при присвоении новых значений вариантным переменным или при завершении программы. Это не всегда правильно, так как неиспользуемые глобальные переменные могут неоправданно занимать большой объем памяти, замедляя работу системы в целом. Для немедленного разрушения вариантной переменной предназначена процедура VarClear, по своему действию эквивалентная присвоению переменной значения Unassigned: Procedure V a r C l e a r ( V : Variant); Например:
Var V: Variant;
{Описание вариантной переменной}
Begin V := VarArrayCreate([0, 5, 3, 8], varlnteger); {Создание двухмерного вариантного массива} VarClear(V);
52
{Разрушение вариантного массива V}
Глава 2. Структуры данных в Delphi
2.6. Выражения в Object Pascal Выражения в Object Pascal можно разделить на арифметические, логические и строковые. К арифметическим выражениям относятся операции сложения (используется оператор +), вычитания (используется оператор -), умножения (используетсй оператор *) и деления (используется оператор /), производимые над вещественными и целочисленными значениями. При использовании в одном выражении значений разных типов, например при умножении целого числа типа integer на вещественное значение типа Double, тип результата выражения устанавливается по типу одного из операндов, имеющего самую широкую область значений. Var X, Rd: Double; I, Ri : Integer;
{Описание вещественных переменных X и Rd} {Описание целочисленных переменных I и Ri}
Begin Rd := X * I;
Ri
:= X *
( П о п ы т к а присвоения переменной Rd значения арифметического выражения — произведения целочисленной и вещественной переменных I и X. Операция корректна, так как выражение имеет тип Double, как наиболее широкий из типов операндов. Следовательно, р е з у л ь т а т может быть занесен в переменную Rd, имеющую тип D o u b l e } I; {Попытка присвоения переменной Ri значения арифметического выражения — произведения целочисленной и вещественной переменных I и X. Операция я в л я е т с я ошибочной, так как выражение имеет тип Double, как наиболее широкий из типов' операндов. Следовательно, результат не может быть занесен в переменную Ri, имеющую тип Integer)
В выражениях, использующих ко операция конкатенации — лучается строка, состоящая из ледовательности, указанной в
строковые значения, поддерживается тольсложения строк, в результате которой посимволов каждой строки-операнда в посвыражении:
Var N,
F:
Double;
{Описание двух строковых переменных — N и F}
53
Часть I. Структурное программирование в Delphi
Begin N := 'Ольга'; {Присвоение переменной N значения 'Ольга'} F := 'Тихонова'; {Присвоение переменной F значения 'Тихонова' } N := N + ' ' + F; {Присвоение переменной N результата конкатенации трех строк — значения переменной N, строки, состоящей ив символа пробел, и значения переменной F. В переменной N, таким образом, будет содержаться строка 'Ольга Тихонова' }
Логические операции представляют собой набор логических операндов (переменных или констант типа Boolean), соединенных логическими операторами And, Or и Not. Оператор And возвращает значение True, если оба операнда имеют значение True. Оператор Or возвращает значение True, если хотя бы один из операторов имеет такое значение. Оператор Not предназначен для работы с одним операндом и инвертирует его значение. Var А, В, С: Boolean;
{Описание переменных А, В и С типа Boolean
Begin А := True; В := True; А := Not A; I
С
:= A Or В;
С := A And В;
{Присвоение переменной А значения True} {Присвоение переменной В значения True} {В переменную А заносится инвертированное значение этой же переменной (False)} (В переменную С заносится результат логического выражения A or В. Так как один из операндов (В) имеет значение True, то результатом выражения также .является значение True} {В переменную С заносится результат логического выражения A and В. Так как один из операндов (А) имеет значение False, то результатом выражения также является значение False}
В выражениях любого вида могут использоваться более сложные конструкции, например: 54
Глава 2. Структуры данных в Delphi Var А, В, С: Double;
{Описание вещественных переменных А, В и С}
Begin С : = А + В * А + В ; {В переменную С заносится значение выражения А + В * А + В, вычисляемого с использованием обычных математических правил: А + (В * А) + В}
Во всех видах выражений могут применяться не только переменные и константы, но также и функции, возвращающие значения, подходящие по типу для данного выражения, например, можно стандартную функцию Sin, возвращающую синус заданного значения, использовать следующим образом: Var X, Y: Double;
{Описание вещественных переменных X и Y}
Begin У := Sin(X);
{Присвоение переменной Y значения, возвращаемого функцией Sin}
.
'
~
•. _ . •
55
3
Структурные операторы
3.1. Организация ветвления 3.1.1. Условный оператор If..Then..Else Условный оператор предназначен для ветвления программы в зависимости от некоторого условия (логического выражения или логической переменной) и выглядит следующим образом: If Then {Else ); Вторая часть условного оператора, начинающаяся с ключевого слова Else, необязательна, и ее можно не использовать, например: If X > 0 Then X
:=
10;
В качестве примера использования условного оператора приведем фрагмент программы, определяющий сигнатуру некоторой целочисленной переменной х. Сигнатурой числового значения является: 0, если значение равно нулю; -1, если значение отрицательно; 1, если значение положительно. Код примера приведен в листинге 3.1. Обратите внимание, что используются два условных оператора, один из которых вложен в другой. В обоих операторах присутствует секция Else. 56
Глава 3. Структурные операторы Листинг 3.1. Использование условного оператора
Var X: Integer; Sign: Integer;
(Описание анализируемой переменной} {Описание переменной для хранения результата анализа (сигнатуры)}
Begin If X > 0 then Sign := 1 (Если значение переменной X положительно, переменной Sign будет присвоено значение 1} else If X < 0 then Sign := -1 {Если значение переменной X отрицательно, переменной Sign будет присвоено значение -1} else Sign := 0; {Если не выполнены оба предыдущих условия (значение переменной X нулевое), следовательно, переменной .iSign также следует присвоить значение 0}
Вместо Условия в операторе If. .Then. .Else допускается применение переменной типа Boolean: Var В: Boolean; Begin If В Then . . . Else •
.
• . . . . ' •
- '
3.1.2. Условный оператор множественного выбора Case Для повышения читабельности исходных текстов программ предусмотрен условный оператор множественного выбора Case: Case Of Оначение 1>: ;•
,
57
Часть I. Структурное программирование в Delphi Оначение N>: ; else ; end;
В качестве возможных Значений анализируемой переменной могут использоваться подходящие по типу константы, а также непрерывные интервалы значений, задаваемые парами констант, разделенными двоеточием. В качестве примера приведем фрагмент программы, определяющий сигнатуру переменной, реализованный с помощью оператора множественного выбора (листинг 3.2). Такой подход поможет избежать большой вложенности условных операторов. Листинг 3.2. Использование оператора множественного выбора
Var X: Integer; {Описание анализируемой переменной} Sign: Integer;(Описание переменной для хранения результата анализа (сигнатуры) }
Begin Case X Of (Анализируем параметр X } -2147483648. .-1: Sign := -1; (Если значение переменной X отрицательно (от минимально допустимого значения переменной типа Integer до -1), то переменной Sign будет присвоено значение 1} 1. .2147483647: Sign := 1 ; (Если значение переменной X положительно (от 1 до максимально допустимого значения переменной типа I n t e g e r ) , то переменной Sign будет присвоено значение -1} 0: Sign := 0; {Если значение переменной X нулевое, то переменной Sign будет присвоено значение 0} end;
Заметим, что первый вариант определения сигнатуры с помощью вложенных операторов i f . .Then. .Else является более корректным, так как при любом значении переменной х переменная sign также получит какое-либо значение в данном фрагменте программы. Логика же второго примера подразумевает ограничение возможных значений анализируемой переменной интервалом - 2 1 4 7 4 8 3 6 4 8 . . 2 1 4 7 4 8 3 6 4 7 . В таких случаях целесообразно использовать секцию Else оператора мно58
Глава 3. Структурные операторы
жественного выбора для отработки неподдерживаемого значения параметра х. В этой секции могут выполняться различные действия, такие, как установка переменной sign значения, отличного от О, 1 и -1, или, например, возбуждение исключительной ситуации10. Изменим предыдущий пример таким образом, чтобы в случае обнаружения значения переменной х, выходящего за пределы допустимого интервала, переменной Sign устанавливалось значение -2. Измененный код приведен в листинге 3.3. Листинг 3.3. Модифицированный код Var
X: Integer; (Описание анализируемой переменной} Sign: Integer;{Описание переменной для хранения результата анализа (-сигнатуры) } Begin Case X Of -2147483648
{Анализируем параметр X} -1: Sign := -1; {Если значение переменной X отрицательно (от минимально допустимого значения переменной типа Integer до -1), переменной Sign будет присвоено значение 1} 1. .2147483647: Sign := 1; {Если значение переменной X положительно (от 1 до максимально допустимого значения переменной типа Integer), переменной Sign будет присвоено значение -1} 0: Sign := 0; {Если значение переменной X нулевое, значение переменной Sign также нулевое} Else: Sign := -2; {Если значение переменной X не удовлетворяет ни одному из трех условий, переменной Sign присваивается значение -2} end;
Понятие исключительной ситуации рассмотрено ниже в соответствующем разделе. 59
Часть I. Структурное программирование в Delphi
3.2. Составной оператор Так как одной команды, выполняемой в зависимости от некоторого условия, может быть недостаточно, в Delphi, как и в Pascal, предусмотрена возможность использования составных операторов — нескольких команд, заключенных в так называемые программные скобки — ключевые слова begin и end: begin ; ; end; Пример составного оператора приведен ниже при обсуждении условных операторов цикла.
з.з. Зацикливание Операторы зацикливания предназначены для многократного выполнения (повторения) некоторого фрагмента программы и представлены в Delphi в трех вариантах:
* оператором For; * оператором while, .do; » оператором repeat, .until.
3.3.1. Оператор зацикливания For Стандартный вариант Оператор For изменяет некоторую переменную (счетчик цикла) в заданных пределах (от минимального значения до максимального) с единичным шагом и при этом на каждый шаг может выполнять какую-либо команду или набор команд. В качестве счетчика цикла может использоваться любая целочисленная переменная. For := То Do ; В качестве Команды может использоваться любая конструкция языка, в том числе условные операторы или операторы зацикливания, а также составной оператор. В качестве Минимального значения и Максималь60
Глава 3. Структурные операторы
лого значения могут быть использованы целочисленные переменные или константы. Обратите внимание, что использование переменной-счетчика цикла после оператора зацикливания не рекомендуется, так как значение этой переменной может быть не определено. Это связано с работой оптимизатора компиляции в Delphi, подставляющего вместо некоторых счетчиков цикла регистры процессора для ускорения выполнения программы. При компиляции программы, в которой переменная-счетчик цикла используется после оператора цикла, выдается предупреждение " [Warning] FOR-LOOP variable 'Имя переменной' may be undefined after loop» — " [ПреЛ дупреждение] Значение переменной-счетчика цикла Имя переменной' может быть не определено после завершения оператора цикла». Приведем пример использования оператора зацикливания For при вычислении факториала некоторого числа (см. листинг 3.4).
Листинг 3.4. Вычисление факториала с использованием оператора зацикливания For
Var I: Integer; F: Integer; X: Integer;
{Описание целочисленной переменной — счетчика цикла} {Описание целочисленной переменной — результата вычислений) {Описание целочисленной переменной — значения, от которого вычисляется факториал}
Begin {Будем вычислять факториал числа 10} X := 10; {Значение факториала по умолчанию 1} F := 1; For i := 2 To X {Счетчик цикла последовательно изменяется от 2 до X (10) }
Do F
F * i; {На каждом шаге цикла предыдущий результат умножается на значение счетчика цикла}
Заметим, что, если значение переменной х, установленное перед вычислением, меньше 2, то оператор зацикливания не выполнится ни разу, и результирующее значение факториала (значение переменной F) будет равно 1.
61
Часть I. Структурное программирование в Delphi Цикл с обратным отсчетом Для уменьшения счетчика цикла от Максимального значения до Минимального значения (также с единичным шагом) предусмотрена разновидность оператора зацикливания — так называемый оператор зацикливания с обратным отсчетом. В этом операторе используюется ключевое слово DownTo вместо То: For := DownTo Do ; Приведем фрагмент программы вычисления факториала на основе цикла с обратным отсчетом (листинг 3.5). Листинг 3.5. Вычисление факториала с использованием оператора зацикливания For с обрантым отсчетом Var I: Integer;
F: Integer; X: Integer;
{Описание целочисленной переменной — счетчика цикла} {Описание целочисленной переменной — результата вычислений} {Описание целочисленной переменной — значения, от которого вычисляется факториал}
Begin X := 10; {Будем вычислять факториал числа 10} F := 1; {Значение факториала по умолчанию 1} For i := X DownTo 2 {Счетчик цикла последовательно изменяется от X (10) до 2 в сторону уменьшения} Do F : = F * i; , i {На каждом шаге цикла предыдущий результат умножается на значение счетчика цикла}
3.3.2. Условные операторы зацикливания Оператор зацикливания с предусловием While..do В случаях, когда на этапе написания программы неизвестно количество шагов в цикле, используются условные операторы зацикливания, выполняющие фрагмент программы, в зависимости от истинности или ложно-
62
Глава 3. Структурные операторы
сти некоторого условия. Таких операторов в Delphi, как и в Pascal, два условный оператор цикла с предусловием и с постусловием. Отличие этих операторов друг от друга состоит в том, что первый проверяет необходимость продолжения работы перед выполнением зацикливаемого фрагмента программы, а второй — после. Таким образом, в операторе цикла с постусловием зацикливаемый фрагмент программы выполняется как минимум один раз, а при использовании цикла с предусловием он может не выполняться ни разу. Условный оператор зацикливания с предусловием While. . do выглядит следующим образом: While Do ; Команда, представленная Любой структурой языка, выполняется до тех пор, пока Условие истинно. В качестве примера использования оператор зацикливания While, .do рассмотрим вычисление факториала (листинг 3.6). Листинг 3.6. Вычисление факториала на основе условного оператора цикла While..do Var F: Integer;
X: Integer;
{Описание целочисленной переменной — результата вычислений} {Описание целочисленной переменной — значения, от которого вычисляется факториал}
Begin
X := 10; F := 1; While X > 1 do begin *^ '
* X;
X := : end;
- 1;
U*
{Будем вычислять факториал числа 10} {Значение факториала по умолчанию 1} {Зацикленный фрагмент будет выполняться, пока истинно выражение X > 1} {Начало составного оператора — зацикленного фрагмента} {Умножаем текущее значение факториала на X} {Уменьшаем X на 1} {Окончание составного оператора — зацикленного фрагмента}
63
Часть I. Структурное программирование в Delphi
Условный оператор зацикливания с постусловием Repeat..Until Условный оператор зацикливания с постусловием Repeat. .Until является заодно еще и составным оператором, и выглядит следующим образом: Repeat
•
; ; Until ; Зацикленный фрагмент программы выполняется оператором Repeat. .Until до тех пор, пока не станет истинным Условие. Закончим обсуждение условных операторов цикла описанием двух фрагментов программы, содержащих бесконечные циклы. Первый вариант бесконечного цикла реализуем с помощью оператора while: While 2=2 Do; {Так как условие 2=2 истинно и не изменится в процессе выполнения программы, то зацикленный фрагмент программы (пустой оператор ;) будет выполняться бесконечно} И аналогичный пример с использованием оператора Repeat. .Until: Repeat
Until 22;
{Так как условие 22 ложно и не изменится в процессе выполнения программы, то зацикленный фрагмент- программы (команды в этом фрагменте отсутствуют) будет выполняться бесконечно}
Заметим, что использование бесконечных циклов при программировании под операционную систему Windows крайне не рекомендуется, так как приводит к «подвисанию» системы (снижению ее производительности), и обычно приостанавливает выполнение других приложений. Особенно это касается Windows 98 и более ранних версий.
64
Глава 3. Структурные операторы
з.з.з. Прерывание зацикленного фрагмента Для досрочного завершения какого-либо оператора зацикливания (For, While или Repeat. .Until) предусмотрена команда Break. Наиболее часто применение такой команды требуется в подпрограммах поиска с помощью перебора в ситуациях, когда искомый элемент найден, и в дальнейшем переборе остальных элементов нет необходимости. Проиллюстрируем использование команды Break на примере выхода из бесконечного цикла на основе анализа значения, возвращаемого генератором случайных чисел (функция Random возвращает случайное число в интервале от нуля включительно до единицы). Repeat {Начало оператора цикла} if Random < 0 . 0 1 then break; {Выход, в зависимости от значения функции Random} Until False; {Так как условие завершения цикла ложно, то зацикленный фрагмент может п о в т о р я т ь с я бесконечно}
Условие Random < 0.01, используемое в условном операторе, представляющем собой фрагмент программы, зацикленный с помощью оператора Repeat. .Until, изменяется в процессе выполнения программы, так как при каждом вызове функция Random возвращает разные значения. Таким образом, программа завершится с высокой вероятностью, но сколько раз будет выполнена проверка условия, заранее неизвестно. В тот момент, когда условие в операторе if станет истинным, будет выполнен оператор break, и выполнение оператора зацикливания прервется. Соответственно, завершится и программа.
•
3 Зак. 867
65
Описание подпрограмм
4.1. Виды подпрограмм: процедуры и функции Подпрограммы в Delphi, как и в Pascal, представлены процедурами и функциями. Процедура — это логически законченная часть программы, описываемая отдельно от раздела описания логики программы и вызываемая по ее уникальному имени. Процедуры, так же как и программа, имеют сложную структуру, включающую в себя разделы описания переменных, констант, типов, и так далее (все они называются локальными, так как их область видимости ограничена процедурой). Единственное, чем отличается по структуре процедура от программы — в ней нет раздела подключения модулей. Procedure ; Label
Заголовок процедуры Раздел описания локальных меток безусловного перехода
Const Раздел описания локальных констант Type Раздел описания локальных типов данных Var
Раздел описания локальных переменных Procedure Function Раздел описания локальных подпрограмм — процедур и функций, содержащих некоторые элементы логики процедуры 66
Глава 4. Описание подпрограмм begin
Начало раздела описания логики процедуры Раздел описания логики процедуры Конец раздела описания логики процедуры
end;
Функции — это процедуры, которые возвращают значения. Благодаря этому их можно использовать в арифметических или логических выражениях, наряду с переменными или константами. Структура функции аналогична структуре процедуры, за исключением заголовка, в котором указывается, значение какого типа возвращает функция. Вместо ключевого слова Procedure в описании функций используется ключевое слово Function: Function :
;
Для указания компилятору, какое именно значение должна возвращать функция, в разделе описания логики функции должно использоваться присвоение следующего вида: := ;
В Delphi предусмотрен еще одцн механизм возврата значения функции в вызывающую подпрограмму — ключевое слово Result: Result
:= ;
Приведем пример модуля, в котором описана одна процедура и одна функция (листинг 4.1), а также программу, которая подключает этот модуль и вызывает подпрограммы, описанные в нем (листинг 4.2). Листинг 4,1. Модуль с описанием подпрограмм Unit Subprograms;
Interface Procedure RunMe;
Function GetValue:
Implementation Procedure RunMe; begin end;
{Заголовок модуля содержит название, по которому модуль подключается к программе} {Начало интерфейсной секции} (Заголовок процедуры RunMe, которая должна быть реализована в описательной секции и будет доступна в подключающем модуле} Integer; {Заголовок функции GetValue, к о т о р а я должна быть реализована в описательной секции и будет доступна в подключающем модуле} {Начало описательной секции}
{Реализация процедуры RunMe}
Часть I. Структурное программирование в Delphi Function GetValue: Integer; begin Result := 158; end; {Реализация функции GetValue, функция возвращает значение 158} end.
Листинг 4.2. Программа, подключающая модуль и вызывающая его подпрограммы Uses Subprograms; Var V: Integer;
{Подключение модуля Subprograms) {Описание целочисленной переменной}
Begin RunMe; GetValue; V := GetValue;
{Вызов процедуры RunMe} {Вызов функции GetValue без анализа возвращаемого значения} {Вызов функции GetValue с занесением возвращаемого значения в переменную V}
4.2. Подпрограммы с параметрами Для повышения гибкости подпрограмм и изменения их функциональности в некоторых пределах можно использовать параметры — некоторые значения, передаваемые в процедуры и функции для внутреннего использования. С помощью параметров вызывающая программа имеет возможность изменять ход выполнения подпрограмм тем или иным образом, заложенным в них разработчиком, что существенно расширяет круг их применения. Object Pascal поддерживает множество видов параметров. Далее рассмотрены основы использования некоторых из них, а также механизмы их передачи в подпрограммы.
4.2.1. Описание параметров Описание параметров подпрограмм происходит в их заголовках после имени и имеет следующий вид: Procedure (: ; . . . ; : ); 68
Глава 4. Описание подпрограмм
или для функций: Function
() : ;
Параметры, переданные в подпрограмму, могут использоваться в ней аналогично локальным переменным, а значение, заданное в качестве параметра при вызове подпрограммы, является, соответственно, начальным значением такой переменной. '...-г,'..'.
'
4.2.2. Механизмы передачи параметров в подпрограммы Delphi поддерживает два механизма передачи параметров в подпрограммы: 1.По значению (от англ. Value parameter — параметр, передаваемый по значению); 2. По ссылке. Передача параметров по значению При передаче по значению параметр рассматривается как локальная переменная, значение которой устанавливается при вызове подпрограммы, может быть использовано и изменено подпрограммой, но не может повлиять на вызывающий фрагмент. В качестве параметра может указываться переменная или константа, подходящая параметру по типу. При передаче параметра по значению значение параметра, заданное при вызове подпрограммы, заносится в стек (см. рис. 4.1). Затем подпрограмма выделяет собственную память для локальной переменной, переносит в нее значение параметра, переданное через стек, и обеспечивает доступ к этой локальной переменной через имя параметра. После завершения подпрограммы измененное значение параметра теряется и никак не влияет на данные вызывающего фрагмента. Рассмотрим пример, в котором подпрограмма изменяет значение параметра, переданного ей по значению (см. листинг 4.3).
Г\
Вызывающий фрагмент
Значение 1
гСтек —'1/
/
Локальная переменная
t
iг Подпрограмма
Рис. 4.1. Механизм передачи параметров по значению 69
Часть I. Структурное программирование в Delphi Листинг 4.3. Передача параметров по значению Program ChangingParamsl; Var I: Integer; {Описание целочисленной переменной} Procedure Proc (A: Integer); {Описание процедуры Proc с одним параметром, передаваемым по значению} Begin А := 154; {Изменение значения параметра, переданного в процедуру. Никаких изменений данных вызывающего фрагмента не происходит} end; Begin {Начало раздела описания логики программы} I := 200; {Изменение значение переменной 1} Proc (I) ; {Вызов процедуры Proc, в качестве параметра передается значение переменной I, после окончания работы процедуры значение переменной сохраняется} Proc(80) ; {Вызов процедуры Proc, в качестве параметра передается целочисленная константа 80, что допустимо при передаче параметров по значению} End. Передача параметров по ссылке. Параметры-переменные Второй механизм передачи параметров — по ссылке — подразумевает возможность изменения подпрограммой данных вызывающего фрагмента программы. Для этого в качестве параметра вызывающий фрагмент должен использовать переменную, адрес которой будет передан в подпрограмму через стек (см. рис. 4.2). Далее подпрограмма обеспечивает доступ к переданному адресу по имени параметра. Соответственно, изменения, производимые с параметром в подпрограмме, влияют на ту переменную, которая указана в качестве параметра. Адрес
Подпрограмма
Рис. 4.2. Механизм передачи параметров по ссылке 70
Глава 4. Описание подпрограмм Для указания компилятору на необходимость передачи параметра по ссылке перед описанием соответствующего параметра указывается специальный модификатор параметра — ключевое слово Var (от англ. Variable — переменный): Procedure ( . . . , V a r : , . . . ) ; Так как при использовании параметров-переменных в подпрограмму должен быть передан некоторый адрес, то в качестве параметра, передаваемого по ссылке, должны использоваться только переменные. Изменим предыдущий пример таким образом, чтобы параметр передавался в процедуру по ссылке (см. листинг 4.4). Листинг 4.4. Передача параметров по ссылке Program ChangingParams2;
Var I: Integer; {Описание целочисленной переменной} Procedure Proc (Var A: Integer); {Описание процедуры Proc с одним параметром, передаваемым по ссылке}
Begin А := 154;
{Изменение значения параметра, переданного в процедуру. Одновременно с этим изменяется значение переменной, переданной в качестве параметра}
end; Begin I := 200; , Ргос(1);
{Начало раздела описания логики программы} {Изменение значение переменной 1} {Вызов процедуры Proc, в качестве параметра передается адрес переменной I, после окончания работы процедуры, значение переменной изменится и станет равным 154}
End. Параметры, передаваемые по ссылке, называют параметрами-переменными (от англ. Variable parameter — параметр-переменная).
4.2.3. Частные случаи передачи параметров подпрограммам Параметры по умолчанию Особенностью Delphi относительно Pascal является возможность использования параметров по умолчанию. Такие параметры находятся в конце списка параметров и их описания имеют следующий вид: : = Оначение по умолчанию>
71
Часть I. Структурное программирование в Delphi
Подпрограммы, для которых указаны параметры по умолчанию, вызываются либо с указанием полного набора параметров, либо с указанием набора обязательных параметров и одним или несколькими параметрами, имеющими значения по умолчанию. При этом для того, чтобы передать некоторый параметр, задаваемый умолчанием, следует передать все параметры, находящиеся в списке параметров перед ним. Пример использования параметров по умолчанию приведен в листинге 4.5. Листинг 4.5. Использование параметров по умолчанию Program UsingFunctions;
Var S: String; {Описание строковой переменной 5} Function GetFullName (SurNarae: String; FirstName: String = 'имя не введено'; SecondName: String = 'отчество не в в е д е н о ' ) : String; {Описание функции G e t F u l l a m e с одним обязательным параметром и двумя параметрами, заданными по умолчанию} Begin Result := SurName + л ' +
FirstName + ' ' + SecondName; {В качестве результата функция возвращает сумму параметров. Если при вызове не задан последний параметр, то вместо него подставляется строка 'отчество не введено' , если не задан еще и предпоследний параметр, то вместо него п о д с т а в л я е т с я 'имя не в в е д е н о ' } end; Begin
{Начало раздела описания логики программы)
S := GetFullName('Иванов'); {В переменной S будет значение 'Иванов имя не введено отчество не в в е д е н о ' } S := GetFullName('Иванов' , ' И в а н ' ) ; {В переменной S будет значение 'Иванов Иван отчество не введено' } S := GetFullName('Иванов' , 'Иван', ' И в а н о в и ч ' ) ; {В переменной S будет значение 'Иванов Иван И в а н о в и ч ' } End.
72
Глава 4. Описание подпрограмм
Передача по значению параметров ссылочных типов данных При передаче параметров по значению все содержимое заданной структуры данных копируется в локальную память подпрограммы. Однако для ссылочных типов данных их значением, фактически, является адрес памяти. Таким образом, при передаче в подпрограмму ссылки, ее значение нельзя изменить, но можно изменить информацию в памяти, на которую эта ссылка указывает. Для начала продемонстрируем невозможность изменения самой ссылки: Procedure ChangeParam ( P : Begin New(Р) ;
Л
ПоиЫе) ;
End;
В такой реализации процедуры в качестве параметра передается не адрес переменной, использованной в качестве параметра, а ее содержимое, то есть адрес, который является значением. Этот адрес заносится в локальную копию переменной, и в подпрограмме используется копия, начальное значение которой содержит переданный в качестве параметра адрес. Поэтому подпрограмма, выделяя память под параметр р и занося адрес выделенной памяти в этот параметр, не влияет на переменную, которая была задана в качестве параметра. Теперь продемонстрируем возможность изменения информации, на которую ссылается переданный параметр: Procedure ChangeParam ( P : "Double); Begin PA := 150; {Изменение информации в памяти, на которую указывает параметр, влияет на данные вызывающего фрагмента} New(P); {Выделение памяти под параметр Р не приводит к изменению адреса в памяти, который является значением переменной, переданной в качестве параметра. Память выделяется под локальную копию параметра} {Изменение информации в памяти, на которую PA := 320; указывает параметр, не влияет на данные вызывающего фрагмента; так как значение параметра уже изменено} {Освобождение памяти, выделенной под Dispose(P); локальную копию параметра} End;
Изменим процедуру, сделав параметр передаваемым по ссылке (листинг 4.6). 73
Часть I. Структурное программирование в Delphi
Листинг 4.6. Передача по значению параметров ссылочных типов данных A
Procedure ChangeParam (Var P: Double); Begin л Р := 150; {Изменение информации в памяти, на которую указывает параметр, влияет на данные вызывающего фрагмента} New(P); {Выделение памяти под параметр Р приводит к потере ссылки на область памяти, находящейся в параметре при его передаче. Память выделяется под переменную, заданную в качестве параметра} А Р := 320; {Изменение информации в памяти, на которую указывает параметр, влияет на данные вызывающего фрагмента} Dispose(P); {Освобождение памяти, выделенной под переменную, заданную в качестве параметра} End; Аналогична ситуация с параметрами — динамическими массивами, значения элементов которых также может изменить подпрограмма, так как 11 ей передается не копия массива, а ссылка на него , даже если параметр не помечен в описании подпрограммы модификатором Var. Параметры-константы Для параметров, передаваемых по значению, предусмотрен модификатор Const, запрещающий изменять значение даже локальной копии данного параметра и передавать его как параметр-переменную в другие подпрограммы. Модификатор предназначен для указания компилятору Delphi на неизменяемый характер параметра и позволяет ему создавать более оптимальный программный код при использовании строковых параметров и параметров сложных типов данных. Procedure ( . . . , Const : ,
...);
Таким образом, указание в процедуре Ргос из предыдущего примера модификатора Const вместо модификатора Var приведет к ошибке компиляции с сообщением " L e f t side cannot be assigned to" -- "Левая часть выражения не может быть изменена". Параметры, описанные с модификатором Const, называются параметрами-константами. Пример использования таких параметров приведен в листинге 4.7. 11
Заметим, что, несмотря на множество общих черт в реализации динамических массивов и строковых переменных, подпрограмма не может изменить значение строкового параметра.
74
Глава 4. Описание подпрограмм
Листинг 4.7. Использование параметров-констант Procedure OpenFile(Const: FileName: AnsiString);
{Заголовок процедуры OpenFile, строковый параметр FileName передается как константа}
Var sLocal: AnsiString; {Описание локальной переменной} Begin FileName := 'TheFile.txt';
{Операция недопустима, так как параметр FileName помечен модификатором Const} sLocal := FileName; {Операция возможна} end; Параметры для заполнения Параметры, предаваемые по значению (в том числе и параметры-константы), используются для настройки подпрограммы, тогда как в параметрах-переменных информация может возвращаться обратно в вызывающий фрагмент программы, в измененном или первоначальном (установленном при вызове подпрограммы) виде. Существует еще один вид параметров — параметры для заполнения, которые предусмотрены только для обратной передачи данных из подпрограммы в вызывающий фрагмент программы. Такие параметры аналогичны параметрам-переменным, однако использование начальных значений этих параметров в подпрограмме недопустимо. Delphi автоматически освобождает память, занятую под переменные, передаваемые в качестве параметров для заполнения, и доступ к ним может вызвать ошибку времени выполнения. Заметим, что обращение к параметрам для заполнения до присвоения им значений в подпрограмме является классическим примером динамической ошибки. Delphi, освобождая память, занятую под ссылочную переменную, задаваемую в качестве параметра, не обнуляет ее адрес. В результате чего параметр указывает на некоторую область памяти, в которой находятся некоторые данные, причем доступ к ним возможен. Однако при интенсивном использовании системы эта память уже может использоваться другой подпрограммой, и доступ к ней вызовет ее сбой. Для описания параметров для заполнения используется модификатор Out: Procedure ( . . . , Out : , . . . ) ;
Пример использования параметров для заполнения приведен в листинге 4.8. 75
Часть I. Структурное программирование в Delphi Листинг 4.8. Использование параметров для заполнения Procedure ReadFile(Const: FileName: AnsiString; Out B u f f e r : Pointer) ; {Заголовок процедуры O p e n F i l e . Строковый параметр F i l e N a m e передается как к о н с т а н т а , параметруказатель B u f f e r предназначен для заполнения в подпрограмме} Var
sLocal: AnsiString; {Описание локальной переменной} Begin GetMem(Buffer, 1 0 2 4 ) ; {Выделение памяти под параметр B u f f e r } {Занесение•информации в память, на которую указывает переменная Buffer} end; Параметры без указания типа Передача параметров ссылочного типа в подпрограммы является распространенным явлением, причем типы данных, на которые указывают передаваемые ссылки, могут быть самые разнообразные. В качестве примера можно привести процедуру, записывающую информацию в файл. Для такой процедуры не имеет значения, что именно хранится в памяти, на которую указывает параметр-ссылка, а необходимо знать только адрес, где находится информация и ее объем. Допустим, в программе описана процедура SaveToFile, принимающая в качестве параметров указатель на данные и целочисленное значение, содержащее размер данных: Procedure S a v e T o F i l e ( B u f f e r :
Pointer;
Size:
Integer);
*i
Для использования такой процедуры, фрагменты программы, которые сохраняют информацию в файл, должны получить адрес сохраняемой структуры и передать его в качестве параметра B u f f e r . Для этого может быть применен оператор @, возвращающий адрес заданной переменной, и функция s i z e O f , возвращающая размер заданной структуры:
Туре TRec = Record S: String; I: Integer; end; (Описание типа данных, используемого для хранения информации} Var Rec: Tree;{Описание переменной, в которой будет храниться структура, предназначенная для сохранения} ,
76
Глава 4. Описание подпрограмм Begin SaveToFile(@Rec, S i z e O f ( R e c ) ) ; {Вызов процедуры SaveToFile с передачей ей адреса, возвращаемого оператором @, и размера экземпляра записи, возвращаемого функцией SizeOf} Однако в Delphi предусмотрен специальный тип параметров, который не требует дополнительной операции получения адреса. Это так называемые параметры без указания типа. Они передаются по значению 1 2 , не требуют указания типа при описании, и рассматриваются в подпрограмме как нетипизированные указатели. В качестве таких параметров могут использоваться переменные любого типа. Перед» описанием нетипизированного параметра должно быть указано ключевое слово Var или const. Так, описание рассматриваемой нами процедуры SaveToFile может быть сделано таким образом: Procedure SaveToFile ( V a r B u f f e r ; ' S i z e : I n t e g e r ) ; Использование нетипизированных параметров может выглядеть следующим образом: Туре TRec = Record S: String; I: Integer; end; {Описание типа данных, используемого для хранения информации} Var Rec: Tree; {Описание переменной, в которой будет храниться структура, предназначенная для сохранения} Begin SaveToFile(Rec, SizeOf(Rec)); {Вызов процедуры SaveToFile с автоматической передачей ей адреса, без использования оператора @, и размера экземпляра записи, возвращаемого функцией SizeOf} •
12
—'4
На самом деле передается адрес переменной, заданной в качестве параметра.
77
Часть I. Структурное программирование в Delphi
4.3. Перегружаемые подпрограммы В Delphi предусмотрена возможность описания нескольких подпрограмм с одинаковыми именами в одной и той же области видимости. Такие подпрограммы называются перегружаемыми. Аналогичные механизмы присутствуют в большинстве современных языков программирования. Наличие механизма перегружаемых подпрограмм позволяет реализовывать подпрограммы, выполняющие одинаковые действия на основе параметров разных типов, что упрощает их вызов, так как отпадает необходимость приведения параметров к конкретным типам данных. Кроме того, перегружаемые подпрограммы могут создаваться не только с параметрами разных типов, но и вообще с разным количеством параметров. Для того, чтобы компилятор мог выбрать правильную подпрограмму из нескольких перегруженных, они должны отличаться так называемой сигнатурой — последовательностью типов данных в списке параметров. Также перегружаемые подпрограммы должны быть помечены ключевым словом overload. Пример описания и использования перегружаемой подпрограммы приведен в листинге 4.9. Листинг 4.9. Использование перегружаемой подпрограммы Unit DeclaringOverloads; Interface
{Заголовок модуля} {Указание на начало интерфейсной секции} Function GetName(S: String): String; overload; {Заголовок функции GetName с одним строковым параметром} Function GetName(A: Integer): String; overload; {Заголовок функции GetName с одним числовым параметром} Implementation {Указание на начало описательной секции} Uses SysUtils; {Подключение модуля S y s U t i l s для доступа к функции I n t T o S t r } Function GetName(S: String): String; overload; begin Result := S; end; {Описание функции GetName с одним строковым параметром, в качестве результата возвращается параметр} Function GetName(A: Integer): String; overload; begin Result := IntToStr(A); {Описание функции GetName с одним 78
Глава 4. Описание подпрограмм числовым параметром, в качестве результата возвращается строковое представление параметра) end; End.
{Окончание модуля} "7
'
При использовании идентификатора GetName в какой-либо подпрограмме на этапе компиляции будет подставлен такой вариант функции, который соответствует типу задаваемого параметра.
4.4. Досрочный выход из подпрограммы Подпрограммы являются довольно крупными логическими единицами программы и выполняют обычно довольно сложные действия. Необходимый результат, ожидаемый от процедуры или функции — выполнение каких-либо действий или расчет возвращаемого значения — может быть получен при выполнении не всего кода подпрограммы, а только его части. Поэтому подпрограммы снабжены механизмом досрочного завершения, реализуемым процедурой Exit: Procedure E x i t ; Результатом выполнения процедуры Exit будет передача управления от подпрограммы к фрагменту программы, вызвавшему ее. Если Exit вызывается в основной части программы, а не в процедуре или функции, то завершится программа. Необходимо отметить, что при использовании процедуры Exit в функциях следует внимательно следить за инициализацией возвращаемого значения, то есть выход из функции должен быть произведен только после его расчета, иначе вызывающий фрагмент может получить некорректные данные..
4.5. Процедурные типы данных И, в заключение, мы вернемся к информационной структуре программы и разберем такое важное для программирования на Delphi понятие, как процедурные типы данных. Переменные процедурного типа представляют собой ссылки на подпрограммы — процедуры и функции. Описание типа строится на основе сигнатуры подпрограммы без указания имени: Туре = Procedure(); 79
Часть I. Структурное программирование в Delphi
Или для функций: Туре = Function(): ;
Допускается описание переменной в разделе Var без введения в программу нового процедурного типа, например: Var f S i n = Function(X: D o u b l e ) : D o u b l e ;
Как и переменные любого другого типа, процедурные переменные после описания не определены. Для инициализации процедурной переменной необходимо присвоить ей адрес подпрограммы, доступной текущему фрагменту программы, в котором выполняется присвоение. Для получения адреса подпрограммы используется ее имя: = ;
После инициализации переменная процедурного типа используется так же, как и вызов обычной подпрограммы, то есть вместо названия подпрограммы указывается название процедурной переменной: (;
Например, можно воспользоваться стандартным классом исключения EdivByZero (от англ. Division By Zero Exception — Исключительная ситуация деления на ноль), возникающим автоматически при попытке программы произвести деление на ноль. Создание объекта исключения может располагаться прямо в вызове оператора Raise. Конструктор этого класса должен быть вызван с параметром-строкой, которая будет выдана пользователю: Raise E D i v B y Z e r o . C r e a t e ( ' Д е л е н и е на н о л ь ! Введите другой д е л и т е л ь . ' ) ;
130
Глава 7. Объектно-ориентированный подход к обработке ошибок
После вызова оператора Raise начинается процедура поиска обработчика исключения, описанная выше. Диалог, выдаваемый пользователю, представлен на рис. 7.1.
7.3. Обработка исключений Когда некоторый фрагмент программного кода необходимо защитить от досрочного завершения по ошибке, его следует заключить в блок обработки исключения («защищенный блок»). Таких блоков в Delphi предусмотрено два, а отличаются они характером действий, выполняемых в случае ошибки. . Блок обработки исключений Try..Except Блок обработки исключений T r y . .Except (от англ. Try — Пробовать и Except — кроме) пытается выполнить заданный фрагмент программы, а в случае появления исключения — передает управление специальному обработчику, расположенному в секции Except: Try Except On do «Эбработчик исключения>; On do ; Else «Эбработчик по умолчанию>; End;
Таким образом, если в контролируемом Фрагменте программы, расположенном между ключевыми словами Try и Except, произошло исключение, то управление передается в секцию Except. В секции Except производится поиск Обработчика, который соответствует классу выброшенного исключения. Если такой обработчик не найден, то вызывается Обработчик по умолчанию, находящийся после ключевого слова Else. Если же ключевое слово Else не используется и при этом не найден обработчик исключения, то исключение передается в подпрограмму, вызвавшую данную, и так далее, в соответствии с механизмом, описанным выше. Обратим внимание на некоторые особенности работы блока Try. .Except. 1. Если в секции Except не используется ни одной структуры On. . Do, a, соответственно, и ключевого слова Else, то есть секция Except представляет собой простую последовательность каких-либо команд, то содержание этой секции считается обработчиком любого исключения. 131
Часть II. Объектно-ориентированное программирование в Delphi
2. При определении обработчика исключения используются правила совместимости типов, поэтому для обработки исключений разных типов, классы которых являются наследниками от одного и того же класса, может быть использован родительский класс. Таким образом, обработчик On Exception Do. . . будет являться обработчиком любого исключения, так как все классы исключений являются наследниками класса Exception. 3. Если Обработчик исключения в результате своей работы вызовет еще одно исключение, оно будет обрабатываться по общим правилам. Такое исключение может быть обработано и программно, то есть текст Обработчик исключения может быть в свою очередь заключен в блок обработки. 4. Если для возникшего исключения подобран обработчик, то сообщение пользователю не выдается. Приведем пример функции, возвращающей частное двух своих параметров (листинг 7.1). Если во время расчета частного происходит ошибка, то в качестве значения функции будет возвращено значение константы NaN (от англ. NaN — Not a Number — Не число), описанной в стандартном модуле Math. Для реакции на исключительные ситуации функции использован простейший вариант блока обработки, не содержащий конструкции On. .Do и ключевого слова Else. .
-
•
.
•
•
•
,
•
•
•
•
•
.
, ••
• •••••••
•
-;.
•
• •
,•. Free ; \
Чтение и запись информации в поток Чтение информации из потока производится на уровне двоичной информации с помощью методов Read и ReadBuffer, а также на уровне экземпляров классов-наследников T C o m p o n e n t с помощью метода ReadComponent: Function Read(var B u f f e r ; Count: L o n g i n t ) : L o n g i n t ; Procedure R e a d B u f f e r ( v a r B u f f e r ; C o u n t : L o n g i n t ) ; Function R e a d C o m p o n e n t ( I n s t a n c e : T C o m p o n e n t ) : TComponent;
Для записи информации в поток используются аналогичные методы: Function W r i t e ( v a r B u f f e r ; C o u n t : L o n g i n t ) : L o n g i n t ; Procedure W r i t e B u f f e r ( v a r B u f f e r ; C o u n t : L o n g i n t ) ; Procedure W r i t e C o m p o n e n t ( I n s t a n c e : T C o m p o n e n t ) ;
Методы Read и W r i t e при описании в классе TStream являются абстрактными, а методы ReadBuffer и W r i t e B u f f e r их вызывают. Таким образом, создание и использование экземпляра класса TStream недопустимо. Классы, реализующие потоки, связанные с реальными устройствами, переопределяют данные методы для достижения соответствующей функциональности. Для записи в поток информации, хранимой в другом потоке, вне зависимости от того, экземпляром какого класса он является, используется метод CopyFrom: Function C o p y F r o m ( S o u r c e : T S t r e a m ; Count:
Int64):
Int64;
Для изменения текущей позиции в потоке предусмотрено свойство Position, однако в некоторых случаях более удобно использовать метод Seek, изменяющий текущую позицию относительно настоящего значения: Function Seek(const O f f s e t : Int64; Origin: TSeekOrigin) :
Int64;
Метод Seek перемещает позицию текущего указателя на количество байтов, заданное параметром O f f s e t относительно начала потока, конца потока, или текущего положения указателя. Возможные значения данного параметра приведены в табл. 9.2.
162
Глава 9. Объектно-ориентированный подход к вводу/выводу информации Возможные значения параметра Origin метода Seek класса TStream Значение
Таблица 9.2
Описание
soFromBeginning
Текущий указатель устанавливается на Offset байт от начала потока. Значение Offset должно быть положительным
soFromCurrent
Текущий указатель смещается на Offset байт от текущего положения. Значение Offset может быть как положительным, так и отрицательным
soFromEnd
Текущий указатель устанавливается на Offset байт от конца потока в сторону его начала. Значение Offset должно быть отрицательным
9.3. Особенности реализации разных потоков 9.3.1. Файловые потоки Файловые потоки предназначены для ввода/вывода информации с помощью файлов, реализованы в виде класса TFileStream, и поддерживают все возможности операционной системы, предусмотренные для работы с файлами. Для создания файлового потока TFileStream предусмотрено два конструктора: Constructor Create(const FileName: s t r i n g ; Mode: W o r d ) ; overload; Constructor Create(const FileName: s t r i n g ; Mode: W o r d ; R i g h t s : Cardinal); overload;
Вторая версия конструктора имеет дополнительный параметр R i g h t s (англ. Right — Права), использование которого имеет смысл только в операционной системе Linux. В Windows параметр Rights игнорируется и его рассмотрение нами не имеет смысла. Параметры FileName и Mode (англ. Mode — Режим) определяют соответственно название файла, с которым связывается поток, и набор флагов доступа к файлу. Флаги доступа разделяются на две группы: направления движения информации и разделения доступа. По направлению движения информации файлы могут открываться (см. табл. 9.3): • Только для чтения; « Только для записи; » Для чтения и записи одновременно. 163
Часть II. Объектно-ориентированное программирование в Delphi
Разделение доступа определяет, какие операции могут выполняться над файлом другими приложениями во время работы с ним создаваемого потока (см. табл. 9.4): » » » »
защита файла от записи в него информации другими приложениями; защита файла от чтения другими приложениями; защита от записи и чтения другими приложениями; отсутствие защиты от записи и чтения другими приложениями.
Флаги, используемые при создании файлового потока для определения направления движения информации Флаг
Таблица 9,3
Доступ к файлу
fmCreate
Создание файла для записи. Если файл с заданным именем уже существует, то он открывается для записи, что эквивалентно использованию флага fmOpenWrite. Если файл с заданным именем уже существует, то информация, находящаяся в нем, будет утеряна
fmOpenRead
Открытие файла для чтения
fmOpenWrite
Открытие файла для записи. Если файл с заданным именем уже существует, то информация, находящаяся в нем, будет утеряна
fmOpenReadWrrte
Открытие файла для чтения и записи одновременно
Флаги, используемые при создании файлового потока для определения типа разделения доступа к файлу Флаг
Таблица 9.4
Разделение файла между приложениями
fmShareExclusive
Файл открывается в монопольном режиме, во время работы с файлом ни одно приложение не может записывать в этот файл информацию и считывать ее из него
fmShareDenyWrite
Другие приложения могут считывать информацию из файла, но не могут ее записывать в него
fmShareDenyRead
Другие приложения могут записывать информацию в файл, но не могут считывать ее из него
fmShareDenyNone
Другие приложения могут использовать файл для любых операций
9.3.2. Потоки на основе оперативной памяти Для временного хранения информации в оперативной памяти предусмотрен потоковый класс TMemoryStream. Этот класс поддерживает все свойства и методы, описанные в TStream, и реализует дополнительные методы для сохранения в файл записанной в него информации, а также и чтения информации из файла. Поток TMemoryStream построен на основе нетипизированного указателя, который может быть доступен для чтения через свойство Memory типа Pointer, если есть необходимость прямого доступа к данным. 164
Глава 9. Объектно-ориентированный подход к вводу/выводу информации Для создания потока TMemoryStream используется конструктор Create без параметров: Constructor Create; :
Для чтения информации из файла с одновременным занесением ее в поток используется метод LoadFromFile: Procedure LoadFromFile(const FileName: s t r i n g ) ; И аналогичный метод для записи содержимого потока в файл: Procedure SaveToFile(const FileName: s t r i n g ) ; Кроме того, предусмотрены метод для чтения информации из другого потока вне зависимости от его типа, а также метод записи хранимой информации в другой поток: Procedure Procedure
LoadFromStream(Stream: T S t r e a m ) ; SaveToStream(Stream: T S t r e a m ) ;
Также, для потоков, работающих с оперативной памятью, определена операция очистки содержимого с помощью метода Clear: ' Procedure Clear;
9.3.3. Строковые потоки Строковые потоки используются для доступа к строкам, хранимым в памяти. Реализованы строковые потоки в виде класса TStringStream. В каждом экземпляре такого класса хранится одна строка, доступ к которой возможен с помощью обычных методов, характерных для потоков (то есть Read, W r i t e , и другими). Если программе, использующей такой поток, необходим доступ к хранимой информации, как к строке в целом, то ссылка на строку может быть получена с помощью свойства DataString: property D a t a S t r i n g : s t r i n g ; Помимо методов записи информации, унаследованных от класса TStream, в строковых потоках реализована возможность записи и чтения строк: Function ReadString(Count: L o n g i n t ) : s t r i n g ; Procedure W r i t e S t r i n g ( c o n s t A S t r i n g : s t r i n g ) ; При чтении и записи строк в строчные потоки учитывается положение текущего указателя, то есть значение свойства Position. .
.Л
165
fO
Использование графической информации в Delphi
10.1. Общие принципы вывода информации Операционная система Windows построена таким образом, что для прикладной программы не важно, на какое именно устройство производится вывод графической информации. Данный подход вполне логичен, так как все устройства, предназначенные для вывода информации, например, монитор или принтер, поддерживают примерно одинаковый набор возможностей. Прямой доступ к устройствам, который использовался в ОС DOS, запрещен в Windows, а вместо непосредственного взаимодействия с аппаратной частью предназначен программный интерфейс, представляемый операционной системой в виде набора системных API-функций. Для того, чтобы идентифицировать графическое устройство, введено понятие графического контекста (от англ. DC — Device Context — Контекст устройства), представляющего собой описание параметров графического устройства и используемого по ссылке (от англ. HDC — Handle of Device Context — Ссылка на контекст устройства). Ссылка на графический контекст передается API-функциям в качестве одного из параметров, в результате чего выводимая информация попадает на необходимое устройство (см. рис. 10.1). Контекст устройства имеет набор характеристик, таких как стиль обводки и заливки, и использует их при выводе графических объектов. Характеристики контекста являются ресурсами системного модуля GDI, и для их создания, назначения контексту и разрушения предусмотрены различные системные подпрограммы. Каждая характеристика является системным ресурсом Windows и используется, как и сам контекст, по ссылке.
166
Глава 10. Использование графической информации в Delphi Подпрограмма \ НОС
Системные функции Драйвер \
Устройство Рис. 10.1. Вывод графической информации в Windows
В Delphi с помощью механизмов инкапсуляции скрыты не только подробности взаимодействия с физическими устройствами вывода графической информации, но и вызов системных функций. Для представления и использования графического контекста и его характеристик предусмотрены специальные типы данных и классы. Графический контекст представлен в Delphi классом TCanvas (англ. Canvas — Полотно), описанном в модуле Graphics, а характеристики контекста — классами-оболочками системных графических ресурсов трех видов: \ * шаблоны обводки (класс ТРеп); » шаблоны заливки (класс TBrush); » шрифты (класс TFont). Таким образом, в программах Delphi процесс вывода графической информации становится длиннее на один шаг (см. рис. 10.2). Однако это в некоторой степени компенсируется снижением сложности программирования за счет более удобного представления данных в виде экземпляров классов и автоматического выполнения объектами Delphi некоторых необходимых действий. | Подпрограмма Delphi | Экземпляр TCanvas Системные функции HDC
Драйвер ± Устройство
Рис.
10.2. Вывод графической информации в Delphi
167
Часть II. Объектно-ориентированное программирование в Delphi
10.2. Параметры вывода информации ю.2.1. Параметры графической информации Общие положения В Windows и, соответственно, в Delphi используется механизм текущих параметров графической информации, когда некоторые настройки вывода действуют на все выводимые объекты с момента назначения их контексту. При изменении каких-либо текущих характеристик, например, шаблона обводки, выведенные к текущему моменту времени объекты не изменяются, а все объекты, выводимые позднее, будут использовать новые параметры. Классы-оболочки графических ресурсов ТРеп (англ. Реп — Карандаш), TBrush (англ. Brush — Кисть) и TFont (англ. Font — Шрифт) имеют общий родительский класс TGraphicsObject (англ. Graphics Object — Графический объект). В классе TGraphicsObject реализованы основные методы, необходимые для поддержки работы с такими системными ссылками. Эти методы в основном предназначены для обеспечения корректности при взаимодействии с механизмами вывода графической информации. Иерархия классов, предназначеных для хранения параметров графической информации, представлена на рис. 10.3. TGraphicsObject
Рис. 10.3. Иерархия классов-параметров ' графической информации
Все приведенные классы имеют свойство Handle (англ. Handle — Ссылка), описанное, однако, в каждом классе отдельно, а не в родительском классе. Такой подход вызван тем, что для каждого вида графических ресурсов в Delphi предусмотрен собственный тип данных. Соответственно, в классе ТРел свойство Handle описано с типом НРеп, в классе TBrush — с типом HBrush, а в классе TFont — с типом HFont. Экземпляры классов ТРеп, TBrush и TFont создаются вызовом конструктора Create без передачи ему параметров. Все свойства графических объектов устанавливаются для экземпляра класса с помощью соответствующих свойств, рассматриваемых нами ниже.
168
Глава 10. Использование графической информации в Delphi
Для обеспечения реакции программы на изменения графических объектов предназначено событие OnChange. Данное событие описано в классе TGraphicsObject, а возникает оно при изменении каких-либо свойств объектов TPen, TBrush и TFont. Отметим, что во всех стандартных классах-наследниках TGraphicsObject переопределен, метод Assign класса TPersistent, что позволяет существенно упростить процедуру создания одного объекта на основе другого. Далее подробно рассмотрены предусмотренные в Delphi характеристики графической информации. Цветовые характеристики Практически все операции по выводу изображения на экран используют какие-либо цветовые характеристики, а указание цвета является довольно распространенной процедурой. Цветовые характеристики графических объектов не являются сложными структурами, поэтому для их хранения не используются системные ресурсы Windows. Соответственно для хранения цвета не предусмотрено и собственного класса. Для хранения цвета в Windows используется механизм представления RGB, согласно которому любой цвет строится на основе трех составляющих — красной (R, Red), зеленой (G, Green) и синей (В, Blue). Интенсивность каждой составляющей может быть представлен целочисленным значением от 0 до 255, или, в шестнадцатеричной системе от $00 до $FF. Тип данных TColor (англ. Color — Цвет) является поддиапазоном типа integer, используется для указания цвета некоторого объекта и описан в модуле Graphics следующим образом: Type TColor = -$7FFFFFFF-1..$7FFFFFFF; Цвет в переменных типа TColor записывается справа налево, то есть в двух младших шестнадцатеричных разрядах хранится красная составляющая, в следующих двух разрядах находится зеленая, а затем синяя: $BBGGRR
При нулевой интенсивности всех составляющих ($000000) цвет является черным, а при максимальной ($FFFFFF) — белым. Старшие два шестнадцатеричных разряда предназначены для более качественного преобразования цвета системой, если такое преобразование необходимо, и могут иметь следующие значения: » $00 — указанный цвет приводится к системной палитре; * $01 — указанный цвет приводится к текущей палитре; * $02 — указанный цвет приводится к палитре устройства вывода. .
169
Часть II. Объектно-ориентированное программирование в Delphi В модуле Graphics описан набор констант, который облегчает указание цвета. Названия таких констант построены с указанием названия цвета. Например, константа clBlack соответсвует черному цвету, а константа clActiveBorder представляет цвет, используемый системой для вывода рамки окна. Характеристики обводки Экземпляры класса ТРеп используются для указания характеристик начертания обводки при выводе линий и геометрических фигур. Начертание линий определяется следующими параметрами: * * * »
цвет; стиль; толщина; режим взаимодействия с фоном, на который выводится изображение.
Цвет обводки содержится в свойстве Color типа TColor: property Color: TColor;
.
Толщина линии, которая применяется для обводки графических объектов хранится в целочисленном свойстве width и задается в точках. property W i d t h :
Integer;
Параметры Style и Mode, определяющие стиль и режим взаимодействия выводимой информации с фоном, более сложны, и хранятся соответственно в перечислимых свойствах TPenStyle и TPenMode: property Style: T P e n S t y l e ; property Mode: TPenMode; Возможные значения свойств Style и Mode приведены соответственно в табл. 10.1 и 10.2.
Возможные значения свойства Style экземпляров класса ТРеп Значение
Стиль линии
psSolid
Сплошная
psDash
Штриховая
psDot
Пунктирная
psDashDot
Штрих- пунктирная
psDashDotDot
Один штрих на две пунктирных точки
psClear
Линия не рисуется
psInsideFrame
Сплошная
170
Таблица 10.1
Глава 10. Использование графической информации в Delphi Возможные значения свойства Mode экземпляров класса ТРеп Значение pmBlack
Таблица 10.2
Режим вывода Линия черная, вне зависимости от ее цвета
pmWhite
Линия белая, вне зависимости от ее цвета
pmNop
Линия не выводится
pmNot
Вместо вывода линии инвертируется фон
pmCopy
Простой вывод
pmNotCopy
Линия выводится инверсным цветом
pmMerge
Цвет линии накладывается на цвет фона по методу OR
pmNotMerge
pmMerge, затем результат инвертируется
pmMergeNotPen
pmMerge, но цвет линии инвертируется
pmMergePenNot
pmMerge, но цвет фона инвертируется
pmMask
Цвет линии накладывается на цвет фона по методу AND
pmNotMask
pmMask, затем результат инвертируется
pmMaskNotPen
pmMask, но цвет линии инвертируется
pmMaskPenNot
pmMask, но цвет фона инвертируется
pmXor
Цвет линии накладывается на цвет фона по методу XOR
pmNotXor
pmXor, затем результат инвертируется
Приведем пример создания объекта шаблона обводки:
Var Pen: TPen;
{Описание ссылки на экземпляр класса TPen}
Begin Pen := TPen.Create; Pen.Color := $FFOOOO; Pen.Style := psDashDot; Pen.Mode := pmMerge;
{Создание- экземпляра класса ТРеп} {Назначение цвета обводки} {Назначение стиля обводки) (Назначение режима вывода обводки}
Pen.Free;
{Разрушение экземпляра класса ТРеп}
После назначения такого объекта графическому контексту все выводимые на данный контекст объекты будут иметь штрих-пунктирную обводку синего цвета, которая накладывается на фоновое изображение по методу OR, то есть добавляет синюю составляющую соответствующим точкам фона.
171
Часть II. Объектно-ориентированное программирование в Delphi
Характеристики заливки Шаблон заливки определяется всего двумя параметрами: цветом (содержится в свойстве Color) и стилем (свойство Style перечислимого типа TBrushStyle, возможные значения которого приведены в табл. 10.3).
Возможные значения свойства Style экземпляров класса TBrush
Таблица 10.3
Стиль заливки
Значение bsSolid
Сплошная
bsClear
Заливка не выводится
bsBDiagonal
Наклонные полосы из левого нижнего угла в правый верхний
bsFDiagonal
Наклонные полосы из левого верхнего угла в правый нижний
bsCross
Вертикальная сетка
bsDiagCross
Наклонная сетка
bsHorizontal
Горизонтальные линии
bsVertical
Вертикальные линии
Приведем пример создания объекта шаблона заливки: Var Brush: TBrush;
{Описание ссылки на экземпляр класса TBrush}
Begin
Brush := TBrush.Create;
{Создание экземпляра класса TBrush}
rush.Color := $OOFFOO; Brush.Style := bsSolid;
{Назначение цвета заливки} {Назначение стиля заливки}
Brush.Free;
{Разрушение экземпляра класса T B r u s h }
После назначения такого объекта графическому контексту все выводимые на данный контекст объекты будут иметь сплошную заливку зеленого цвета. Шаблон заливки представляет собой квадратное изображение шириной восемь точек, формируемое в памяти на основе параметров, заданных свойствами Color и s t y l e . Однако существует возможность использования растрового изображения (экземпляр класса TBitMap) в качестве шаблона. Данное изображение будет использоваться вместо 172
Глава 10. Использование графической информации в Delphi простых настроек, если свойству Bitmap объекта заливки присвоено какое-либо значение: property Bitmap: TBitmap; Подробнее использование классов TBitMap рассмотрено ниже.
Ю.2.2. Характеристики текстовой информации Класс TFont инкапсулирует свойства, необходимые для описания шрифта, которым может выводиться какая-либо текстовая информация. Список свойств данного класса приведен в табл. 10.4. Свойства класса TFont Название свойства
Таблица 10.4 Описание
Тип
Name
TComponentName (аналог типа String)
В качестве значения свойства Name может использоваться название любого шрифта, доступного в данный момент системе, например, «Arial»
Charset
TFontCharset
Набор символов, который используется при выводе информации. Для вывода русских символов используется значение RUSSIAN_CHARSET
Color
TColor
Определяет цвет выводимого текста
Height
Integer
Определяет высоту шрифта в пунктах (1/72 дюйма). Используется, когда имеет положительное значение. При установке данного свойства свойство Size становится отрицательным
Size
Integer
Определяет высоту шрифта в точках. Используется, когда имеет положительное значение. При установке данного свойства свойство Height становится отрицательным
PixelsPerlnch
Integer
Коэффициент перевода высоты шрифта, заданной свойством Size в высоту, задаваемую свойством Height. Size = - Height * 72 / PixelsPerlnch
Style
set of TFontStyle;
Определяет набор атрибутов шрифта. В множество могут включаться элементы fsBold (полужирное начертание шрифта), fsltalic (наклон символов), fsUnderline (подчеркивание) и fsStrikeOut (перечеркнутые символы)
Pitch
TFontPitch
Определяет тип шрифта и может иметь одно из трех значений: fpDefault (использовать настройки шрифта), fpVariable (использовать символы переменной ширины), fpFixed (использовать моноширинный шрифт);
Приведем пример создания объекта шрифта: Var Font: TFont;
{Описание ссылки на экземпляр класса TFont]
Begin
173
Часть II. Объектно-ориентированное программирование в Delphi Font := TFont.Create; (Создание экземпляра класса TFont} Font.CharSet := RUSSIAN_CHARSET; {Назначение кодовой страницы} Font.Color := $OOFFFF; {Назначение цвета i h b a n f } Font.Name := ' A r i a l ' ; {Название шрифта} Font.Pitch := .fpDefault; ( У с т а н о в к а набора символов} Font.Size := 16; {Установка размера} Font.Style := [fsBold, fsltalic]; {Установка стиля} Font.Free;
(Разрушение экземпляра класса T F o n t }
После назначения такого объекта графическому контексту вся текстовая информация будет выводиться шрифтом A r i a l размера 16 пунктов, символы будут иметь наклонное полужирное начертание, а цвет символов будет желтым.
ю.з. Области отображения и вывод на них Класс TCanvas Класс TCanvas, (от англ. Canvas — полотно) представляет собой область отображения и инкапсулирует свойства и поведение некоторого абстрактного устройства, предназначенного для вывода графической информации. Таким устройством может являться окно приложения на экране или его часть, а также печатающее устройство. Ссылка на устройство, то есть графический контекст, содержится в свойстве Handle. Таким образом, класс TCanvas представляет собой оболочку вокруг графического контекста и системных функций, предназначенных для работы с ним. Заметим, что экземпляр класса TCanvas может выводить какую-либо информацию, только если определено значение свойства Handle, так как именно оно указывает на реальное устройство вывода. Координатная плоскость При работе с областями отображения типа TCanvas используются локальные координаты, то есть левый верхний угол области отображения имеет координаты (0, 0 ) . При этом вся выводимая на TCanvas информация автоматически перенаправляется на реальное устройство и располагается в нужном месте. 174
Глава 10. Использование графической информации в Delphi
Несмотря на то, что при выводе графического объекта можно указать любые координаты, область отображения все-таки имеет некоторые размеры, которые хранятся в свойстве ClipRect: property C l i p R e c t :
TRect;
В полях L e f t и Тор данной записи находятся нулевые значения, а поля Right и Bottom представляют собой соответственно ширину и высоту области отображения. '
Установка характеристик графических объектов Для установки текущих характеристик графических объектов, выводимых на область отображения, используются свойства Pen, Brush и Font, имеющие типы TPen, TBrush и TFont, рассмотренные нами выше: property P e n : T P e n ; property Brush: TBrush;' property F o n t : T F o n t ;
Поскольку характеристики обводки, заливки и начертания символов являются системными ресурсами, то при работе с ними следует соблюдать осторожность. Каждый созданный программой объект должен быть разрушен, как только перестанет быть необходимым. Если экземпляры классов TPen, TBrush и TFont создаются, но не разрушаются, то может возникнуть недостаток ресурсов в системе, что в некоторых случаях приводит к ее краху. Свойства Pen, Brush и Font класса TCanvas реализованы таким образом, что при присвоении им некоторого объекта происходит не замена текущей ссылки на новую, а перенос характеристик заданного объекта в текущий. При создании экземпляра класса TCanvas свойства Pen, Brush и Font заполняются ссылками на соответствующие объекты, имеющие характеристики по умолчанию. Таким образом, для работы с данными свойствами можно применять два подхода. Первый подход заключается в создании экземпляра класса TPen, TBrush и TFont, заполнении его свойств, присвоении ссылки соответствующему свойству экземпляра класса TCanvas, и последующем разрушении объекта: Var
Canvas: TCanvas; Pen: TPen; Begin 175
Часть II, Объектно-ориентированное программирование в Delphi Pen := ТРеп.Create; {Создание экземпляра ТРеп} Pen.Color := $ F F O O O O ; Pen.Style := psDashDot; Pen.Mode := pmMerge; {Настройка экземпляра Т Р е п } Canvas. Pen := Pen; {Изменение свойства Pen экземпляра T C a n v a s . Характеристики объекта Pen копируются в объект Canvas.Pen. Ответственность за разрушение объекта Реп остается у данного фрагмента программы} Pen.Free; {Разрушение созданного ранее экземпляра ТРеп}
Второй подход заключается в возможности получения ссылки на объекты шаблонов и шрифта с помощью свойств Pen, Brush и Font с последующим изменений свойств полученных объектов:
var Canvas: TCanvas; Begin
Canvas.Pen.Color Canvas.Pen.Style Canvas.Pen.Mode
{Настройка экземпляра = $FFOOOO; {Изменение psDashDot;{Изменение pmMerge; {Изменение
свойства Pen класса T C a n v a s } цвета} стиля} способа вывода}
Вывод текстовой информации Для вывода текста на объекты TCanvas используется метод TextOut данного класса: Procedure T e x t O u t ( X , Y: Integer; const Text: s t r i n g ) ;
Данный метод выводит строку, заданную параметром Text, в координаты х и Y. В случае, когда текстовая информация должна быть выведена в некоторую прямоугольную область, используется метод TextRect: Procedure TextRect(Rect: TRect; X, Y: Integer; const Text: string);
Заданная строка текста выводится в заданные координаты шрифтом, определяемым свойством Font. Для более гибкого управления выводом текстовой информации предусмотрено целочисленное свойство TextFlags класса TCanvas, представляющее собой набор битовых флагов, некоторые из которых представленных в табл. 10.5: property T e x t F l a g s : 176
Longlnt;
Глава 10. Использование графической информации в Delphi Флаги, используемые для формирования свойства TextFlags класса TCanvas Флаг
Таблица 10.5
Описание
ETO_OPAQUE
Перед выводом символов область, занимаемая текстом, заливается фоновым цветом. Наличие данного флага повышает отображения текста. При выводе фоновой области используются текущие настройки кисти, причем заливка выводится только при использовании стилей bsClear или bsSolid, установленных для текущей заливки области отображения (см. пример ниже)
ETO_RTLREADING
Текст выводится справа налево
ETO_GLYPHJNDEX
Текст рассматривается как массив кодов, анализируемых непосредственно графическим модулем Windows. Применяется только для ТгуеТуре шрифтов
ETO_NUMERICSLOCAL
Все числа в заданном тексте отображаются с использованием национальных настроек операционной системы
ETO_NUMERICSLATIN-
Все числа в заданном тексте отображаются без использования национальных настроек операционной системы
Приведем пример использования флага ETO_OPAQUE для заливки прямоугольной области, на которую выводится текстовая строка:
Var Canvas: TCanvas; Begin Canvas.Brush.Style := bsSolid; { У с т а н о в к а стиля заливки} Canvas.Brush.Color := clWhite; { У с т а н о в к а цвета заливки} Canvas.TextFlags := Canvas.TextFlags OR ETO_OPAQUE; {Добавление флага ETO_OPAQUE к свойству TextFlags с помощью операции логического сложения} Canvas.TextOut(10, 10, 'Text in the r e c t a n g l e ' ) ; ' { В ы в о д строки с автоматической заливкой фоновой области}
Широко распространенная операция в приложениях, выводящих текстовую информацию — вычисление размеров охватывающих прямоугольников для строк при текущих установках шрифтовых характеристик. Для выполнения таких действий в классе TCanvas предусмотрены следующие методы: Function T e x t E x t e n t ( c o n s t T e x t : s t r i n g ) : T S i z e ; Function-TextWidth(const T e x t : s t r i n g ) : I n t e g e r ; Function T e x t H e i g h t ( c o n s t T e x t : s t r i n g ) : I n t e g e r ;
Метод TextExtent возвращает запись, состоящую из двух полей — сх и су, в которых находятся соответственно ширина и высота прямоуголь-
177
Часть II. Объектно-ориентированное программирование в Delphi
ника, который будет занимать заданная параметром Text строка текста при использовании текущего шрифта. Методы Textwidth и TextHeight используются для получения информации о ширине и высоте строки текста по отдельности и эквивалентны следующим выражениям: TextExtent(Text) . сх и TextExtent(Text) .су. Необходимо заметить, что возможности настройки выводимого текста в Windows намного более широки, чем реализованные в классе TCanvas. Для доступа ,к расширенным возможностям вывода текстовой информации следует использовать API-функции Windows, передавая им в качестве ссылки на графический контекст значение свойства Handle области отображения. Приведем пример вывода строки с помощью API-функции Windows на графический контекст, инкапсулированный в область отображения Delphi: Var Canvas: TCanvas; Begin TextOut(
{Вызов API-функции TextOut, на основе которой построен метод TextOut класса TCanvas} Canvas.Handle, {Ссылка на графический к о н т е к с т } 10, {Горизонтальная координата вывода т е к с т а } 10, {Вертикальная координата вывода текста} 'Text in the rectangle', {Выводимая строка} 21 {Длина строки}
Заметим, что использование API-функции в данном случае не оправдано, так как проще использовать аналогичный метод самой области представления Canvas. Однако, как было указано выше, класс TCanvas поддерживает не все функции, реализованные в графической библиотеке Windows. Методы вывода графических примитивов В классе TCanvas предусмотрены возможности вывода различных графических примитивов, которые можно разбить на следующие логические группы: » отрезки и ломаные линии; » прямоугольники, в том числе со скругленными краями; » окружности, эллипсы и дуги; » кривые Безье. Также поддерживается заливка любой замкнутой области с помощью метода FloodFill. #
178
Глава 10. Использование графической информации в Delphi
Копирование областей отображения Помимо вывода примитивов в классе TCanvas реализованы методы для поддержки работы со сложными изображениями. Такие методы предназначены для копирования в область отображения части другой области отображения или графического изображения, заданного экземпляром 22 класса типа TGraphics . Для копирования некоторой прямоугольной области объекта TCanvas в заданную прямоугольную область другого объекта TCanvas используется метод CopyRect: Procedure CopyRect(const Best: TRect; C a n v a s : const Source: T R e c t ) ;
TCanvas;
Заметим, что в качестве объекта-назначения Dest может быть указана область отображения, из которой вызывается этот метод. Такой подход может использоваться, например, для смещения (скроллинга) области отображения:
Var Canvas: TCanvas; Begin Canvas.CopyRect( Rect(0, 1, 150, 5 1 ) , Canvas, Rect(0, 0, 150, 50)
{Вызов метода CopyRect} (Результирующая прямоугольная область} (Область отображения — источник} (Исходная прямоугольная область сдвинута относительно результирующей на одну точку вверх}
Поскольку результирующая прямоугольная область (в которую копируется изображение) сдвинута относительно исходной (из которой копируется изображение) на одну точку на одну точку вниз, то при каждом выполнении такого фрагмента программы изображение в прямоугольнике (О, О, 150, 51) будет сдвигаться соответственно на одну точку вниз. При несоответствии размеров области-источника Source и области-приемника Dest изображение автоматически масштабируется. Для управления взаимодействием выводимого и фонового изображений используется свойство CopyMode перечислимого типа T C o p y M o d e , возможные значения которого представлены в табл. 10.6. 22
Различные классы, предназначенные для хранения графических изображений, описаны ниже.
179
Часть II. Объектно-ориентированное программирование в Delphi Возможные значения свойства CopyMode экземпляров класса TCanvas Возможное значение
Таблица 10.6
Описание
cmBlackness
Заполняет область назначения черным цветом. Источник изображения игнорируется
cmWhiteness
Заполняет область назначения белым цветом. Источник изображения игнорируется
cmDestlnvert
Инвертирует область назначения. Источник изображения игнорируется
cmSrcCopy
Простое копирование
cmSrcAnd
Копирование с использованием логической операции AND
cmMergeCopy
Копирование с использованием логической операции AND
cmSrcPaint
Копирование с использованием логической операции OR
cmSrclnvert
Копирование с использованием логической операции XOR
cmNotSrcCopy
Копирование инвертированной копии изображения-источника
cmSrcErase
Инвертирует изображение-приемник и копирует на него изображение-источник с использованием логической операции XOR
cmMergePaint
Выводит инвертированную копию изображения-источника с использованием I логической операции OR
Выводит изображение с использованием логической операции OR, затем cmNotSrcErase инвертирует полученное изображение cmPatCopy
Заполняет область-приемник текущей заливкой
cmPatlnvert
Заполняет область-приемник текущей заливкой, используя при выводе операцию XOR
cmPatPaint
Заполняет инвертированную область-приемник текущей заливкой, используя при выводе операцию Or, затем выводит результат в область приемник, используя операцию OR
Методы вывода изображений Для копирования изображения, хранимого в каком-либо наследнике класса TGraphic, на область отображения используются следующие методы классы TCanvas: Procedure D r a w ( X , Y : Integer; G r a p h i c : Procedure StretchDraw(const Rect: TRect;
TGraphic); Graphic: T G r a p h i c ) ;
Метод Draw используется для копирования изображения в заданные координаты области отображения без изменения его размеров, а метод StretchDraw позволяет задать область, которую будет занимать изображение на области отображения. Соответственно, если размеры изображения отличаются от размеров этой области, изображение будет автоматически отмасштабированно. Еще один метод вывода изображения на объект TCanvas позволяет скопировать часть изображения типа TBitmap в заданный прямоугольник области отображения, заменив при этом цвет изображения, заданный параметром Color на текущую заливку: Procedure BrushCopy(const Dest: TRect; Bitmap: T B i t m a p ; const Source: TRect; C o l o r : T C o l o r ) ;
180
Глава 10. Использование графической информации в Delphi
Прямой доступ к растровому представлению Для прямого доступа к растровому представлению области отображения предусмотрено свойство Pixels, представляющее собой двумерный массив цветов соответствующих точек области отображения: property P i x e l s [ X , Y :
Integer]:
TColor;
Для изменения цвета точки, имеющей координаты (х, Y ) , следует воспользоваться следующей конструкцией: «Эбъект T C a n v a s > . P i x e l s [ X , Y] := ; Синхронизация области отображения В случаях, когда для построения изображения на области отображения используется множество параметров, связанных между собой, часто бывает необходимо предотвратить вывод информации на время приведения каких-либо параметров в соответствие друг другу. Для обеспечения такой возможности предусмотрен механизм блокировки области отображения с помощью методов Lock и UnLock: Procedure Lock; Procedure U n l o c k ;
•
При вызове метода Lock увеличивается значение целочисленного свойства LockCount и любая подпрограмма, которая обращается к объекту TCanvas будет автоматически приостановлена, пока свойство LockCount не будет иметь нулевое значение. Уменьшение значения данного свойства происходит при вызове метода UnLock. Заметим, что каждому вызову метода Lock должен соответствовать вызов метода UnLock, поэтому работа с экземплярами класса TCanvas при использовании блокировок обычно производится в блоке T r y . .Finally. .End: Var Canvas:
.TCanvas;
Begin
Try Canvas.Lock; {Блокировка T C a n v a s } {Выполнение необходимых операций} Finally Canvas.UnLock;
{Разблокировка TCanvas}
End
181
Часть II. Объектно-ориентированное программирование в Delphi
Разновидности областей отображения. Метафайловые области отображения В стандартной поставке Delphi присутствуют два наследника класса TCanvas: * TControlCanvas, адаптированный для использования при прорисовке визуальных компонентов, » TMetafileCanvas, предназначенный для работы с метафайлами — специальными ресурсами Windows, позволяющими хранить последовательность операций вывода на некоторое устройство, сохранять такую последовательность в файле, и считывать ее. Класс TControlCanvas практически не имеет отличий от TCanvas, тогда как метафайловые области отображения требуют более подробного рассмотрения. Область отображения TMetafileCanvas сопоставляется специальному графическому ресурсу Windows, инкапсулированному в Delphi в класс TMetafile, описываемый ниже. В конструкторе данной области отображения указывается метафайл, в который будет сохраняться информация о командах, а также контекст устройства, с которым совместим данный метафайл: constructor Create(AMetafile:
TMetafile;
ReferenceDevice:
HOC);
После разрушения такой области отображения, выведенная на него информация, остается в экземпляре класса TMetaFile, переданном через параметр AMetafile. В качестве параметра ReferenceDevice можно указывать нулевое значение, если создаваемый контекст должен быть совместим с экраном или принтером.
var MetafileCanvas: TMetafileCanvas; Metafile: TMetafile; begin {Создание экземпляра класса TMetafile} Metafile := TMetafile.Create; {Создание экземпляра класса TMetafileCanvas} MetafileCanvas := TMetafileCanvas.Create(Metafile, 0); {Вывод информации на MetafileCanvas} MetafileCanvas.MoveTo(0, 0); MetafileCanvas.LineTo(100, 100); {Разрушение области отображения} MetafileCanvas.Free; {Использование объекта Metafile, в котором сохранены команды вывода} 182
Глава 10. Использование графической информации в Delphi
{Разрушение объекта M e t a f i l e }
Metafile.Free; end;
В приведенном примере создается метафайловая область отображения M e t a f i l e C a n v a s , выводящая графическую информацию к метафайл Metafile. После вывода информации область отображения разрушается, но в метафайле M e t a f i l e остается запись последовательности команд, выполняемых над областью отображения MetafileCanvas. Далее с данным метафайлом можно производить различные действия, например, выводить на какую-либо область отображения (в том числе и на метафайловую) с помощью метода D r a w , так как класс T M e t a f i l e является наследником TGraphics (см. описание метафайлов ниже).
10.4. Использование графических изображений .
.
.
~ ;•
i
ю.4.1. Представление изображений в Delphi. Базовый класс TGraphic Общее описание Программа, работающая в среде Windows, имеет доступ к достаточно развитым графическим возможностям этой операционной системы, что накладывает на саму программу повышенные требования к качеству пользовательского интерфейса. Для создания дружественного программного интерфейса нередко применяются различные графические изображения, создаваемые в специализированных программных продуктах, сохраняемые в файлы и выводимые на экран во время выполнения программы. Самостоятельное использование таких файлов трудоемко и требует от разработчика некоторых дополнительных знаний. Однако в Delphi реализована поддержка следующих видов графических изображений: 1. Растровые изображения, хранимые 2. Растровые изображения, хранимые 3. Растровые изображения, хранимые «иконки»). 4. Векторные изображения, хранимые называемые «метафайлы»).
в формате BMP. в формате JPEG. в формате ICO (так называемые в форматах WMF или EMF (так 183
Часть II. Объектно-ориентированное программирование в Delphi
Все остальные форматы напрямую Delphi не поддерживаются в Delphi напрямую и требут модулей сторонних разработчиков. Под поддержкой работы с графическими изображениями подразумеваются возможности их непосредственного создания программой, записи в файл или поток, а также чтения из файла или потока. Также реализована работа с буфером обмена Windows (Clipboard). Для хранения во время выполнения программы каждого из приведенных типов изображений в Delphi предусмотрены собственные классы, общим родителем которых является класс TGraphic, заявляющий основные свойства (см. табл. 10.7) и методы, необходимые для описания абстрактного изображения. Таблица 10.7
Некоторые свойства класса TGraphic Свойство
Тип
Описание
Boolean
Определяет, хранится ли в данном объекте изображение (значение False), или нет (значение True)
Integer
Ширина и высота изображения, хранимого в данный момент времени. Устанавливаются при чтении изображения из файла или потока, но могут быть изменены в дальнейшем путем усечения изображения либо добавления фоновой области
Transparent
Boolean
Определяет, является ли фон изображния прозрачным. Для векторных изображений и иконок имеет значение True, остальные типы изображений могут иметь различные значения
Palette
HPalette
Ссылка на палитру (набор цветов), используемых в изображении. Если в изображении не используется палитра, то свойство имеет нулевое значение
PaletteModified
Boolean
Определяет, изменена ли палитра с момента прочтения изображения. Содержит True, если изменение производилось
Empty Width Height
Методы, описанные в классе TGraphic (см. табл. 10.8) являются либо абстрактными, либо просто не имеют реализации, и предназначены для переопределения в классах-наследниках. Все они заявляют возможности чтения изображений из некоторых структур данных, а именно файлов, потоков или буфера обмена Windows, или их записи в эти структуры. Методы класса TGraphic Метод
Таблица 10.8 Описание
LoadFromClipboardFormat
Чтение изображения из буфера обмена
LoadFromFile
Чтение изображения из файла
LoadFromStream
Чтение изображения из потока
SaveToClipboardFormat
Запись изображения, хранимого в объекте, в буфер обмена
SaveToFile
Запись изображения', хранимого в объекте, в файл
SaveToStream
Запись изображения, хранимого в объекте, в поток
184
Глава 10. Использование графической информации в Delphi
Наиболее простыми в использовании являются методы LoadFromFile и SaveToFile, а также их аналоги LoadFromStream и SaveToStream, которым в качестве параметров передается имя файла с изображением или, соответственно, ссылка на поток, из которого следует считать изображение: Procedure L o a d F r o m F i l e ( c o n s t F i l e N a m e : s t r i n g ) ; Procedure SaveToFile(const FileName: s t r i n g ) ; Procedure L o a d F r o m S t r e a m ( S t r e a m : T S t r e a m ) ; Procedure S a v e T o S t r e a m ( S t r e a m : T S t r e a m ) ;
Напомним, что TGraphic не предназначен для создания его экземпляров, а присутствует только в качестве возможного родительского класса ' для других, поддерживающих работу с одним из типов графических изображений. Особенности таких классов описаны далее, однако все они используют описанные свойства TGraphic и реализуют заявленные в нем методы, в том числе методы LoadFromStream и SaveToStream, заявленные в TGraphic как абстрактные. Представление растровых изображений. Класс TBitmap Для хранения растровых изображений, поддерживаемых Windows 23 , предназначен класс TBitmap, содержащий в качестве одного из своих свойств ссылку на структуру данных типа HBitmap: property Handle:
HBitmap;
Создание экземпляров класса TBitmap производится с помощью вызова конструктора Create без параметров, после чего объект готов к работе и в него может быть загружено изображение, например, с помощью метода LoadFromFile. Далее программа может определить и при необходимости изменить размеры изображения с помощью свойств width и Height. Такого рода действия характерны для экземпляров любого класса, унаследованного от TGraphic. Однако объекты класса TBitmap поддерживают множество других полезных свойств и методов, ориентированных на работу именно с растровыми изображениями. Так, например, после создания экземпляра класса TBitmap имеется доступ к его свойству Canvas типа TCanvas, работа с которым была описана выше. В области отображения, определяемой этим свойством, находится изображение, хранимое в данном объекте. Свойство Canvas описано только для чтения, что не запрещает его использование как объекта, то есть вызов его методов разрешен. В результате этого программа имеет возможность изменения графического изображения, хранимого в объекте. 13
Речь идет об изображениях «Windows bitmap», которые сохраняются в файлах с расширением BMP.
185
Часть II. Объектно-ориентированное программирование в Delphi
Также заслуживают внимания два свойства, управляющие прозрачностью фона изображения: property TransparentColor: i T C o l o r ; property TransparentMode: TTransparentMode;
Тип TTransparentMode описан следующим образом: Type TTransparentMode = (tmAuto, tmFixed); Свойство TransparentColor (от англ. Transparent Color — Прозрачный цвет) определяет цвет, который не следует выводить на экран при выводе всего изображения. Данное свойство используется только тогда, когда значением свойства TransparentMode (от англ. Transparent Mode — Режим прозрачности) является константа tmFixed, в противном случае (значение tmAuto) в качестве прозрачного цвета по умолчанию используется цвет левой нижней точки изображения. Представление сжатых растровых изображений. Класс TJPEGImage Класс TBitmap, рассмотренный в предыдущем разделе, предназначен для хранения растровых изображений, представленных в самом простом виде — растровой картой без возможностей сжатия. Однако такой формат не всегда удобен в силу большого объема, занимаемого файлами с такими изображениями на диске. Одним из самых распространенных форматов, использующих сжатие, является JPEG, для поддержки которого в Delphi предназначен класс TJPEGImage. -Поскольку формат JPEG существенно сложнее, чем BMP, существуют некоторые особенности работы с ним, в основном касающиеся настройки качества изображения и процесса его распаковки из сжатого вида. При этом класс TGPEGimage не имеет свойства Canvas, вследствие чего изменение прочитанного с диска изображения невозможно. Также в данном классе отсутствует свойство Handle, так как формат JPEG не поддерживается напрямую операционной системой. Для использования изображения такого рода в системных функциях Windows его сначала необходимо перевести в обычный растровый формат, например, с помощью вывода на область отображения какого-либо экземпляра класса TBitmap. Сделать это можно с помощью метода Assign: MyJPEGImage.Assign(MyBitmap). Возможно и обратное: MyBitmap.Assign(MyJPEGImage). Наиболее полезные свойства класса TJPEGImage представлены в табл. 10.9. • 186
Глава 10. Использование графической информации в Delphi Некоторые свойства класса TJPEG/mage Тип
Свойство
Таблица 10.9 Описание
Grayscale
Boolean
Определяет, является ли хранимое изображение чернобелым (значение True), или цветным (значение False). Может изменяться во время выполнения программы для вывода цветного изображения в черно-белой гамме
Performance
TJPEGPerformance
Определяет соотношение качества изображения и скорости его вывода. Данное свойство может иметь одно из двух значений: jpBestSpeed (повышенная скорость вывода) и jpBestQuality (лучшее качество)
Integer
Определяет соотношение качества изображения и размера файла, в котором изображение будет сохранено. Данное свойство может иметь значение от 0 (минимальный объем файла, наихудшее качество изображения) до 100 (максимальный объем файла, наилучшее качество изображения). По умолчанию установлено значение 75
CompressionQuality
Представление Windows-иконок. Класс Tlcon Для использования Windows-иконок, то есть стандартных растровых изображений Windows, хранимых в файлах с расширением ICO, предназначен класс Tlcon, возможности которого ограничены чтением таких изображений из файлов, выводом их на области отображения без изменения размеров (то есть использование метода StretchDraw класса TCanvas недопустимо), а также сохранением.
ю.4.2. Метафайлы. Класс TMetafile Метафайлы и их особенности Метафайл — это последовательность команд вывода графической информации, записанная в специальном формате, поддерживаемом операционной системой Windows. Для хранения метафайлов используются файлы с расширением WMF (от англ. WMF — Windows MetaFile — Метафайлы Windows) или EMF (от англ. EMF — Enhanced MetaFile — Улучшенный метафайл). Формат WMF был разработан для Windows 3.1 и на данный момент является устаревшим. Часть информации, сохраняемой в таком формате, может быть искажена или утеряна. Таким образом, для хранения метафайлов рекомендуется использование формата EMF. Особенностью метафайлов по отношению к растровым изображениям является подход к представлению в них графической информации, который заключается в хранении последовательности команд вывода, а не двухмерного массива точек. Соответственно, объем файлов, в которых хранятся такие изображения, зависит только от количества элементов в 187
Часть II. Объектно-ориентированное программирование в Delphi
этих изображениях, а не от их размеров. Дополнительным преимуществом метафайлов является существенное повышение качества изображений при масштабировании в связи с векторным представлением изображения в метафайле. Поскольку метафайлы поддерживаются операционной системой, в классе TMetafile описано свойство Handle, содержащее ссылку на структуру данных, которую можно использовать для передачи в качестве параметра системным функциям Windows: property Handle:
HMetafile;
Остальные свойства класса TMetafile, за исключением унаследованных от TGraphics, представлены в табл. 10.10. Некоторые свойства класса TMetafile Свойство
Таблица 10.10 Описание
Тип
CreatedBy
String
Содержит строку, указывающую на автора изображения или программу, в которой оно создано. Предназначено только для чтения и устанавливается автоматически при создании области отображения для метафайла
Description
String
Содержит текстовое примечание, хранимое вместе с изображением. Предназначено только для чтения и устанавливается автоматически при создании области отображения для метафайла
Enhanced
Boolean
Определяет формат файла, в котором хранится метафайл. Если данное свойство имеет значение True, то файл сохраняется в формате EMF, иначе используется формат WMF
Inch
Word
Содержит разрешение изображения (количество точек на дюйм). Используется только в формате WMF
MMMeight
Integer
Содержит высоту изображения в сотых долях миллиметра
MM Width
Integer
Содержит ширину изображения в сотых долях миллиметра
Области отображения метафайлов. Класс TMetafileCanvas Для программного создания метафайла предназначен специальный класс области отображения TMetafileCanvas, являющийся наследником TCanvas. Так как метафайл содержит несколько больше информации, чем растровые изображения, в классе TMetaFileCanvas переопределен конструктор Create: Constructor Create(AMetafile:
TMetafile;
ReferenceDevice:
HOC);
В параметре A M e t a f i l e передается ссылка на экземпляр метафайла, в который будет записана последовательность команд вывода информации на данную область отображения. Указание контекста ReferenceDevice используется для создания метафайлового контекста, совместимого с некоторым устройством. При использовании метафайлов, совместимых с экраном и печатающими устройствами, в качестве этого параметра можно указывать нулевое значение. 188
Глава 10. Использование графической информации в Delphi
Информация, выводимая на метафайловую область отображения, записывается в метафайл не в процессе вывода, а только после разрушения экземпляра класса TMetaf ileCanvas. В этот же момент в метафайле будут установлены свойства CreatedBy (авторство) и Description (примечание), если при создании области отображения использовался конструктор следующего вида: Constructor C r e a t e W i t h C o m m e n t ( A M e t a f i l e : T M e t a f i l e ; R e f e r e n c e D e v i c e : HOC; const C r e a t e d B y , D e s c r i p t i o n : S t r i n g
);
Таким образом, схема создания метафайла выглядит следующим образом: 1. Создание метафайла — экземпляра класса TMetaf ile с помощью конструктора Create. 2. Создание метафайловой области отображения — экземпляра класса TMetaf ileCanvas С ПОМОЩЬЮ конструктора Create ИЛИ CreateWithConment.
3. Использование области отображения обычным образом, например настройка характеристик, использование свойств, и вызов методов вывода графической информации. 4. Разрушение метафайловой области отображения с помощью вызова метода Free. При этом последовательность команд, вызванных при использовании области отображения, переносится в метафайл. 5. Использование метафайла, например, сохранение его в файл с помощью метода SaveToFile. 6. Разрушение метафайла с помощью вызова метода Free.
ю.4.3. Представление изображений вне зависимости от формата. Класс TPicture Элементы управления Windows используют изображения для создания своего визуального представления (вида кнопок и т.д. и т.п). Причем в большинстве случаев не играет роли, в каком именно формате такие изображения хранятся. Однако наличие собственных классов для представления изображений существенно затрудняет разработку компонентов, так как для одного ресурса (изображения, выводимого элементом управления) необходимо наличие нескольких свойств разных типов. В качестве решения этой проблемы в Delphi предусмотрен класс TPicture, являющийся оболочкой для всех описанных выше классов (растровых изображений, иконок и метафайлов), и имеющий возможность использовать другие форматы изображений, если в программе существует такая необходимость. 189
Часть II. Объектно-ориентированное программирование в Delphi
Для хранения растровых изображений, иконок и метафайлов в классе T P i c t u r e предусмотрены соответственно свойства Bitmap, Icon и Metafile. При попытке присвоения этим свойствам ссылок на экземпляры классов вызывается метод Assign, в результате чего объект TPicture не становится владельцем присваиваемых объектов, а создает их копии. Одновременно с инициализацией свойств Bitmap, icon и M e t a f i l e заполняется значение свойства Graphic, которое можно передавать различным методам областей отображения в качестве ссылки на изображение, вне зависимости от формата. Для хранения изображений, представленных в других форматах, например в формате JPEG, используется непосредственно свойство Graphic (см. рис. 10.4 ) TGraphic TBitmap TJPEGImage Tlcon
TPicture Graphics Bitmap Icon Metafile
TMetaflle •
Рис. 10.4. Иерархия классов Graphic и свойства класса TPicture
Методы, реализованные в классе TPicture, также отвечают его основному назначению — универсализации работы с изображениями различных форматов. Так, методы LoadFromFile и SaveToFile получают в качестве параметра имя файла, анализируют его расширение, а также корректно считывают и записывают изображение, помещая ссылку на него в соответствующее свойство: Procedure LoadFromFile(const F i l e n a m e : s t r i n g ) ; Procedure SaveToFile(const F i l e n a m e : s t r i n g ) ;
Изначально классу TPicture известны форматы BMP, JPEG, ICO, WMF 24 и EMF . Информация о них хранится в специальной структуре, создаваемой при первом обращении к классу TPicture. Данная структура может быть расширена, если программный продукт описывает наследни-
24
Для использования графических изображений в формате JPEG следует подключить модуль JPEG, добавив его название в раздел Uses секции Interface или Implementation.
190
]_^
Глава 10. Использование графической информации в Delphi
ков класса TGraphic, поддерживающих иные форматы данных. Для этого предназначен метод RegisterFileFormat класса TPicture: class Procedure R e g i s t e r F i l e F o r m a t ( c o n s t A E x t e n s i o n , ADescription: string; AGraphicClass: TGraphicClass) ;
В качестве параметров данному методу передаются расширение файлов AExtension, текстовое описание формата ADescription, а также имя класса AGraphicClass, который реализует чтение, хранение и запись изображений нового типа. Более подробное описание работы с классом TPicture выходит за рамки нашей книги.
Ю.4.4. Пример использования графических изображений В качестве примера использования графических возможностей Delphi приведем программу, выводящую на экран изображение и сохраняющую его в файлы различных форматов: BMP, JPEG и EMF. Также реализуем считывание изображений из файлов, причем изображение, полученное из файла, будет автоматически растягиваться на всю форму (см. рис. 10.5).
JPEG. Создать и сохранить | JPEG. Считать и вывести |
Рис. 10.5. Использование графических файлов
Для создания изображения на экране будем использовать область отображения главной формы приложения, ссылка на которую находится в свойстве Canvas формы. Запустим среду разработчика Delphi и создадим новый проект, выбрав пункт главного меню File->New->Application. Добавим на главную форму в столбик шесть компонентов-кнопок типа TButton с закладки Standard. Визуальным построителем автоматически будут назначены следующие названия данным компонентам: Buttonl, Button2, Buttons, Button4, Buttons, Button6. Форма в процессе разработки представлена на рис. 10.6. 191
Часть II. Объектно-ориентированное программирование в Delphi
| Button2
|
Bulton3
J
Button4 Buttons Bullon6
Рис. 10.6. Форма в процессе разработки Изменим подписи кнопок (изменяя значение свойства Caption для каждой кнопки в Инспекторе объектов) на следующие: » * * » * »
BMP. Создать и сохранить BMP. Считать и вывести JPEG. Создать и сохранить JPEG. Считать и вывести EMF. Создать и сохранить EMF. Считать и вывести
4 Для удобства реализуем два вспомогательных метода: Procedure Procedure
CreatePicture(Canvas: TCanvas); DrawPicture(Graphic: TGraphic);
Метод CreatePicture создает изображение на заданной в раметра области отображения, a DrawPicture выводит Graphic на области отображения Canvas формы с учётом мой формы, которые можно получить из ее свойств width
качестве паизображение размеров саи Height.
Для каждой из шести кнопок реализуем обработчики события нажатия Onclick, выполняющие необходимые действия. Для этого будем выделять соответствующие компоненты по очереди и на странице Events Инспектора объектов дважды щелкать мышью напротив названия события Onclick. При выполнении этих действий в класс формы будут добавляться методы-обработчики соответствующий событий. Модуль формы выглядит как показано в листинге 10.1. Листинг 10.1. Модуль формы unit GrFormats; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; (Заголовок модуля формируется автоматически} 192
Глава 10. Использование графической информации в Delphi
type TForml = class(TForm) Buttonl: TButton; Button2: TButton; Buttons: TButton; Button4: TButton; Buttons: TButton; Button6: TButton; procedure ButtonlClick(Sender: TObject) procedure Button2Click(Sender: TObject) procedure ButtonSClick(Sender: TObject); procedure Button4Click(Sender: TObject); procedure ButtonSClick(Sender: TObject); procedure Button6Click(Sender: TObject); {Ссылки на интерфейсные элементы и заголовки обработчиков нажатия на кнопки Buttonl-ButtonG} private { Private declarations } Procedure CreatePicture(Canvas: TCanvas); Procedure DrawPicture(Graphic: TGraphic); {В секции private описаны заголовки вспомога1тельных методов CreatePicture и DrawPicture} public { Public declarations } end;
Var Forml: TForml; {В раздел описания переменных автоматически добавляется ссылка на главную форму) implementation {$R *.dfm} Uses JPEG;
(В описательной секции модуля подключается модуль JPEG для поддержки файлов этого типа} Procedure TForml.CreatePicture(Canvas : TCanvas); begin {Метод создает изображение на заданной области отображения} Canvas.Pen.Width := 10; Canvas.Pen.Color := clYellow; {Настройка характеристик обводки} Canvas.MoveTo(10, 10); Canvas.LineTo(80, 100); {Вывод линии} Canvas.Pen.Width : = 5 ; 7 Заж. 867
193
Часть II. Объектно-ориентированное программирование в Delphi Canvas.Pen.Color := clRed;
{Настройка характеристик обводки} Canvas.Arc(10, 10, 80, 100, 30, 30, 50, 5 0 ) ; {Вывод дуги) end;
Procedure TForml.DrawPicture(Graphic: TGraphic) ; begin {Метод выводит изображение, заданное параметром Graphics на области отображения главной формы с автоматическим масштабированием по размерам формы} Canvas.StretchDraw(Classes.Rect(0, 0, Width - 10, Height - 25) , Graphic); end; procedure TForml.ButtonlClick(Sender : TObject); {Обработчик нажатия на кнопку "BMP. Создать и сохранить"} Var Bitmap: TBitmap; {Описание ссылки на класс растрового изображения} begin Bitmap := TBitmap.Create; {Создание экземпляра TBitmap} Bitmap.Width := 88; Bitmap.Height := 107; {Установка размеров изображения} CreatePicture(Bitmap.Canvas); {Создание изображения с помощью вызова вспомогательного метода} Bitmap.SaveToFile('example.bmp'); {Сохранение изображения в файл} Bitmap.Free; {Разрушение экземпляра TBitmap} end; procedure TForml.Button2Click(Sender: TObject); {Обработчик,нажатия на кнопку "BMP. Считать и вывести"} Var Bi tmap: TBi tmap; {Описание ссылки на класс растрового изображения} begin Bitmap := TBitmap.Create; (Создание экземпляра TBitmap} Bitmap.LoadFromFile('example.bmp'); {Загрузка изображения из файла}
194
Глава 10. Использование графической информации в Delphi DrawPicture(Bitmap); Bitmap.Free; end; -•
{Вывод изображения из объекта Bitmap (наследника TGraphic) с помощью вспомогательного метода) {Разрушение экземпляра TBitmap}
' . i
procedure TForml.ButtonSClick(Sender: TObject); {Обработчик нажатия на кнопку "JPEG. Создать и сохранить"} Var Bi tmap: TBitmap; JPEGImage: TJPEGImage; {Описание ссылок на экземпляры TBitmap и TJPEGImage} begin Bitmap := TBitmap.Create;{Создание экземпляра TBitmap} Bitmap.Width := 88; Bitmap.Height := 107; {Установка размеров изображения} CreatePicture(Bitmap.Canvas); {Создание изображения с помощью вызова вспомогательного метода} JPEGImage := ТJPEGImage.Create; {Создание экземпляра JPEGImage} JPEGImage.Assign(Bitmap); {Копирование изображения из объекта Bitmap в объект JPEGImage} JPEGImage.CompressionQuality := 100; {Настройка качества изображения (максимальное)} JPEGImage.SaveToFile('example.jpg'); {Сохранение изображения в файл} JPEGImage.Free; {Разрушение экземпляра JPEGImage} Bitmap.Free; {Разрушение экземпляра TBitmap} end; procedure TForml.Button4Click(Sender: TObject); {Обработчик нажатия на кнопку "JPE-G. Считать и вывести"} , Var JPEGImage: TJPEGImage; {Описание ссылки на экземпляр сжатого.растрового изображения формата JPEG} begin .JPEGImage := ТJPEGImage.Create; {Создание экземпляра TJPEGImage} JPEGImage.LoadFromFile('example.jpg') ; {Загрузка изображения из файла} 195
Часть I I . Объектно-ориентированное программирование в Delphi DrawPicture(JPEGImage);
JPEGImage.Free; end;
(Вывод изображения из объекта JPEGImage (наследника TGraphic) с помощью вспомогательного метода} {Разрушение экземпляра JPEGImage}
procedure TForml.ButtonSClick(Sender: TObject); {Обработчик нажатия на кнопку "EMF. Создать и сохранить"} Var Metafile: TMetafile; MetafileCanvas: TMetafileCanvas; {Описание ссылок на экземпляры метафайла Metafile и его области отображения MetafileCanvas} begin Metafile := TMetafile.Create; {Создание экземпляра Metafile} Metafile.Enhanced := True; Metafile.Width := 88; Metafile.Height := 107; {Установка размеров изображения} {Создание экземпляра метафайловой области отображения на основе метафайла Metafile. В качестве совместимого контекста указывается контекст области отображения формы} MetafileCanvas : = TMetafileCanvas.Create(Metafile, Canvas.Handle); CreatePicture(MetafileCanvas); {Создание изображения с помощью вызова вспомогательного метода} MetafileCanvas.Free; {Разрушение экземпляра MetafileCanvas. Сохраненная последовательность команд вывода записывается в объект Metafile, указанный в конструкторе MetafileCanvas} Metafile.SaveToFile('example.emf'); {Сохранение изображения в файл} Metafile.Free; {Разрушение экземпляра Metafile} end; procedure TForml.Button6Click(Sender: TObject); {Обработчик нажатия на кнопку "EMF. Считать и вывести"} Var Metafile: TMetafile; {Описание ссылки на метафайл Metafile}
196
Глава 10. Использование графической информации в Delphi
begin Metafile := TMetafile.Create; {Создание экземпляра Metafile} Metafile.LoadFromFile(Лexample.emf'); {Загрузка изображения из файла} DrawPicture(Metafile); {Вывод изображения из объекта Metafile (наследника TGraphic) с помощью вспомогательного метода} Metafile.Free; {Разрушение экземпляра Metafile} end; end.
Заметим, что приведенный пример можно несколько упростить, используя класс оболочку абстрактного графического изображения T P i c t u r e . С помощью такого класса можно реализовать метод, который считывает с диска файл, заданный, например, параметром, и выводящий его на область отображения формы. Метод этот приведен в листинге 10.2. Листинг 10.2. Метод, считывающий с диска файл Procedure TForml.LoadAndDrawPicture(const FileName: String); Var Picture:
TPicture;
{Описание ссылки на экземпляр класса TPicture}
begin Picture := TPicture.Create; {Создание экземпляра класса-оболочки} Picture.LoadFromFile(FileName); {Считывание изображения из файла, заданного именем. Тип файла определяется автоматически по расширению, ссылка на изображения помещается в свойство Graphic} DrawPicture(Picture.Graphic); {Вывод изображения с помощью вспомогательного метода} Picture.Free; {Разрушение объекта TPicture} end; Использование такого метода позволяет не создавать уникальных обработчиков для каждого типа графических изображений, а вызывать его в обработчиках, просто передавая соответствующее имя файла:
197
Часть II. Объектно-ориентированное программирование в Delphi procedure TForml.Button2Click(Sender: T o b j e c t ) ; Обработчик нажатия на кнопку "BMP. Считать и вывести" begin LoadAndDrawPicture( л example.bmp'); {Чтение и вывод на экран файла формата BMP} end; procedure
TForml.Button4Click(Sender: T o b j e c t ) ; {Обработчик нажатия на кнопку "JPEG. Считать и вывести"}
begin
LoadAndDrawPicture('example.jpg'); {Чтение и вывод на экран файла формата JPEG}
end; procedure
TForml.ButtonSClick(Sender: T o b j e c t ) ; {Обработчик нажатия на кнопку "EMF. Считать и вывести"}
Begin
LoadAndDrawPicture('example.emf'); {Чтение и вывод на экран файла формата EMF}
end;
Проведем небольшой эксперимент для выяснения наиболее удобного графического формата, который мог бы использоваться в такой программе. Будем изменять размеры окна приложения и выводить в него изображения из разных файлов. Видно, что при размере окна, соответствующем размерам изображений, лучшее качество достигается при использовании форматов BMP и EMF, тогда как JPEG-изображение сильно размыто и искажает изначальные цвета. При увеличении или уменьшении окна относительно нормального размера сильно искажается изображение, выводимое из BMP-файла, несколько искажается JPEG-изображение, а метафайл выводится без потери качества. ' По размеру файлов с изображениями можно отметить следующие особенности. Форматы BMP и JPEG подразумевают увеличение размеров файлов, пропорциональное размерам изображения. Это в меньшей степени касается формата JPEG, так как на размер файла будут существенно влиять еще и другие характеристики, например, количество использованных цветов. Формат метафайла является векторным, поэтому размер его файлов зависит не от размеров изображения, а от количества выводимых элементов.
198
Вопросы с ответами для повторения по части II Таким образом, EMF представляется наиболее удачным форматом для хранения изображения, притом, что, так же как и остальные два, может быть подготовлено с помощью специализированных графических редакторов. Однако применение сложных EMF-изображений ограничено неприятными визуальными эффектами, так как пользователь будет видеть процесс их построения. Впрочем, эта проблема может быть решена созданием виртуального экрана — растрового изображения в памяти, на которое будет выведен метафайл перед отображением на экране25. Однако основного недостатка метафайлов — длительного построения изображения — избежать невозможно.
Вопросы с ответами для повторения по части II Опишите суть инкапсуляции и структуру описания класса в Object Pascal. Объясните понятие объекта Ответ: Классом называется описание некоторой структуры программы, обладающей набором внутренних переменных — свойств, и функций (процедур), имеющих доступ к свойствам — методов. Процесс объединения переменных и методов, в результате которого и получается класс, называется инкапсуляцией. Описание классов разделено на две части — интерфейсную («заголовочную») и описательную. В интерфейсной части располагается описание заголовка класса, в котором указывается название класса, по которому будут создаваться его экземпляры, описания свойств и заголовков методов. В описательной части располагается реализация методов, заголовки которых указаны в интерфейсной части описания класса. Интерфейсная часть описания класса располагается в разделах описания нестандартных типов данных модулей и основных частей программ. Для доступа к свойствам и методам класса (за исключением статических методов) необходимо создать переменную-экземпляр класса — объект. Перечислите области видимости элементов классов, применяемые в Object Pascal Ответ: Для разграничения доступа к свойствам и методам объектов классов между различными частями программы предусмотрены модификаторы доступа («видимости»). Модификатор доступа относится не к конкретному свойству или методу класса, а ко всем элементам класса, описание которых располагается после указания модификатора. Один и тот же модификатор может указываться в описании класса более одного раза. Создание и использование виртуального экрана подробно рассмотрено в разделе, посвященном визуальным компонентам вывода сложной графической информации. 199
Часть II. Объектно-ориентированное программирование в Delphi В Object Pascal применяется четыре области видимости: 1
» закрытая для всех фрагментов программ, не находящихся в одном модуле с описываемым классом. Такая область видимости начинается ключевым СЛОВОМ private; • видимая только классам, наследуемым отданного, или расположенным в одном модуле с описанием данного класса. Такая область видимости начинается ключевым словом protected; » доступная любым фрагментам программы. Такая область видимости начинается ключевым словом public; » доступная любым фрагментам программы, а также выдающая информацию о своих описаниях во время выполнения программы. Описания, расположенные в такой области видимости, доступны для изменения в режиме визуальной разработки программы. Область начинается ключевым СЛОВОМ published. Расскажите о правилах описания методов в классах и их особенностях Ответ: Описательная часть класса находится в разделе описания локальных подпрограмм. Методы, заявленные в интерфейсной части реализуются по обычным правилам описания процедур и функций. Для связи функций с классом, методами которого они являются, название класса указывается перед именем самой функции: Procedure .() ; ormi
. Э Buttodl Э Button2
Activetof*^ ""ASon'""''''" 1 " ' ' аШ«* : AlpheBtend
^
Глава 11. Использование ИСР Delphi в разработке
11.2. Главное меню Главное меню IDE Delphi находится в главном окне и содержит все необходимые команды для управления разработкой приложения. Рассмотрим кратко назначение основных пунктов меню, останавливаясь подробно на сопутствующих понятиях и методах программирования в Delphi. Подменю File
Подменю File содержит команды, предназначенные для работы с проектом на уровне файлов, и состоит из следующих подпунктов: New
27
содержит список программных объектов , которые можно создать в открытом проекте (новый модуль, новую форму и т.д.), а также новый проект. Open вызывает диалог открытия любого программного объекта, доступного Delphi (файлов модулей, форм, проектов, библиотек компонентов, и т.д.). Open Project.... открывает ранее сохраненный проект. Reopen содержит список ранее открывавшихся программных объектов и позволяет открыть один из них. Save сохраняет текущий программный объект, например, модуль, который в данный момент открыт в текстовом редакторе IDE. Save as сохраняет текущий программный объект в файле с другим именем. Save Project as .. сохраняет текущий проект в файле с другим именем. Save All сохраняет все измененные на данный момент программные объекты. Close закрывает текущий программный объект, например форму, находящуюся в визуальном построителе. Close All закрывает все программные объекты, открытые в данный момент. Use Unit вызывает диалог выбора активного модуля для отображения его в текстовом редакторе IDE. Print выводит на печать текущий программный объект; Exit завершает работу IDE.
27
Программным объектом (элементом) мы будем называть логическую часть программы любого уровня, в зависимости от контекста. Например, это может быть целый проект, а может быть одна форма, входящая в состав проекта.
215
Часть III. Методика создания программ в Delphi
Подменю Edit Подменю Edit предлагает команды, предназначенные для работы с исходным текстом программы и объектами в визуальном построителе, и состоит из следующих подпунктов: Undelete / Undo
Redo Cut
Сору Paste
Delete
Select All Align to Grid Bring to Front
Send to Back
Align
216
восстанавливает компонент, удаленный с формы, или отменяет последние изменения, произведенный с исходным текстом программы в редакторе IDE. восстанавливает исходный текст программы до того состояния, в котором он находился до вызова команды Undo. удаляет компонент из окна формы (в режиме работы с формой) с копированием его в буфер обмена или вырезает выделенный фрагмент исходного текста из редактора. помещает в буфер обмена компонент из окна формы (в режиме работы с формой) или выделенный фрагмент исходного текста из редактора. вставляет из буфера обмена фрагмент текста в редактор исходного текста или компонент в окно формы (в зависимости от того, какое окно является активным). удаляет компонент из окна формы (в режиме работы с формой) или выделенный фрагмент исходного текста из редактора без копирования его в буфер обмена. выделяет все компоненты, размещенные на форме или текст модуля, открытого в редакторе. выравнивает выделенный компонент относительно сетки, нанесенной на форму. изменяет расположение компонента относительно других компонентов таким образом, чтобы он был «ближе» к пользователю (визуально перекрывал другие компоненты). изменяет расположение компонента относительно других компонентов таким образом, чтобы он был «дальше» от пользователя (визуально перекрывался другими компонентами). вызывает диалог выравнивания нескольких компонентов друг относительно друга по горизонтали и вертикали (левый край компонентов на однбй линии, верхние края компонентов на одной линии и т.д.).
Глава 11. Использование ИСР Delphi в разработке
Size
вызывает диалог подбора размеров нескольких компонентов (установить всем минимальный размер, установить всем заданный размер, и т.д.). Scale вызывает диалог пропорционального изменения размеров всех компонентов, находящихся на форме (изменение задается в процентах). Tab order вызывает диалог указания порядка переключения фокуса ввода между компонентами во время выполнения программы, которое производит пользователь с помощью клавиши Tab. Creation order вызывает диалог указания порядка создания компонентов формы в процессе ее инициализации во время работы приложения. Flip Children изменяет положение визуальных элементов управления на форме, отражая их по горизонтали относительно центра формы. Содержит два подпункта: All - отразить все элементы формы, и Selected отразить только выделенные элементы. Lock controls....... запрещает изменение положения всех элементов управления с помощью визуального построителя для всех форм, открытых в IDE. Подменю Search
.
Подменю Search содержит общепринятые команды текстового поиска, такие как: Find Find in Files
поиск подстроки в тексте активного модуля. поиск заданной подстроки во нескольких файлах. В каких именно файлах следует искать подстроку, можно указать в диалоге, который появляется при выборе данной команды. Replace замена подстроки в тексте активного модуля на другую подстроку. Search again повторение последнего поиска или последней замены подстроки. Incremental Search .. перевод текстового редактора IDE в режим поиска по вводимым символам. Разработчик имеет возможность вводить символы, а в редакторе выделяется строка, содержащая введенную на данный момент последовательность. Go to Line Number .. переход к строке, по ее номеру для быстрого перемещения по тексту текущего модуля. • 217
Часть III. Методика создания программ в Delphi
Find Error
Browse Symbol
переход к строке программы, соответствующей введенному адресу. Используется для локализации ошибок после сообщений типа «Инструкция по адресу . . . обратилась . . . ». вызов диалога просмотра информации о заданном программном идентификаторе (переменной, классе, и т.д.).
Подменю View Подменю View содержит команды вызова на экран окон и диалогов IDE, необходимых для разработки и отладки программы, таких как: * » » « * * * » *
менеджер проектов (View->Project Manager); инспектор объектов (view-X)bject inspector); дерево объектов (View-X>bject TreeView); список заданий разработчику (view->To-Do List); палитра выравнивания компонентов на форме (view->Alignment Palette); дерево элементов программы (view->Browser); дерево элементов активного модуля (View->Code Explorer); список компонентов палитры IDE (View->Component List); диалог выбора активного окна среди открытых в IDE (View->'Window List).
Также подпункт View главного меню управляет отображением панелей инструментов (view->Toolbars), окнами, обеспечивающими более продуктивную работу с отладчиком (View->Debug Windows), содержит команды управления сохранением и восстановлением внешнего вида среды (view->Desktops), и позволяет выбрать активную форму или модуль (View->Onits и view->Forms), а также переключаться между формой и ее модулем (View->Toggle Form/Unit). Понятие проекта и группы проектов. Подменю Project В подменю Project собраны команды, предназначенные для управления проектом и группой проектов. Поясним эти понятия. Проектом называется некоторый логически законченный набор файлов (на усмотрение разработчика). Группой проектов называется набор проектов. Например, при создании нового приложения (пункт главного меню File->New->Application) автоматически создается новый проект и новая группа проектов, содержащая этот проект. Далее, с помощью пункта меню Project->Add to Project можно добавить к проекту любой файл, выбрав его в стандартном диалоге, а с помощью пункта меню Project>Remove from Project удалить из проекта любой файл. 218
Глава 11. Использование ИСР Delphi в разработке
Понятие проекта изначально введено в Delphi для более удобного управления группой файлов, относящихся к одному приложению. Группа проектов появилась значительно позднее, так как не оправдал себя подход SDI, заключающийся в принципе «Одна среда разработчика — одно приложение». Часто возникают ситуации, когда один и тот же модуль или ресурсный файл участвуют в нескольких проектах, и удобно их все иметь в одной среде и отлаживать несколько проектов одновременно. Для работы с группами проектов предназначен немодальный диалог Менеджера проектов, который может быть выведен на экран с помощью пункта главного меню View->Project Manager (см. рис. 11.2).
Path EAProgram Files\Borland\Delphi7\Pr Lp Pioiecl1.exe E:\Midiael\NT_BOOK_D\PrograrmS В Щ! Unill E:\Michael\NT_BOOK_D\ProgramsV | | | Unid.pas E:\Michael\NT_BOOK_D\ProgiamsV '-Ш F«m1
EAMichael\NT_BOOK_D\ProgramsV
Рис. 11.2. Менеджер проектов
Данный диалог представляет структуру группы проектов в виде дерева. В каждый момент времени один из проектов является активным, то есть все действия, которые производятся на уровне проекта, производятся именно с ним. Для активации проекта следует выбрать его мышью в дереве проектов и нажать на кнопку Activate »,£» (англ. Activate — Активизировать), расположенную в верхнем правом углу Менеджера. Кнопка Activate доступна, только если проектов в группе несколько. Для добавления нового или уже существующего, сохраненного на диске проекта, предназначены пункты главного меню Project->Add New Project и Project->Add Existing Project. Кнопка New и] (англ. New — Новый) вызывает диалог добавления элемента в дерево. Если в этом диалоге будет выбран элемент, подразумевающий под собой наличие проекта (например, приложение — Application), то будет создан и добавлен в дерево новый проект, если же новый элемент может существовать в рамках другого проекта, то он будет добавлен в активный проект и также отображен в дереве. Кнопка Remove ЙЙ (англ. Remove — Удалить) удаляет элемент, который выбран в дереве в данный момент. Этот элемент может быть проектом или его частью28. 28
Некоторые элементы из дерева удалить невозможно, так как на них ссылаются некоторые модули. Например, нельзя удалить файл описания формы без удаления модуля формы.
219
Часть III. Методика создания программ в Delphi
Сохранить группу проектов можно, вызвав контекстное меню Менеджера проектов правой кнопкой мыши, когда она находится над названием группы проектов. Далее, в появившемся меню следует выбрать пункт Save Project Group As. Также, данное меню позволяет активизировать, откомпилировать, построить проект, над которым меню было вызвано, или изменить очередность его компиляции относительно других проектов в группе, если он должен быть построен раньше или позже других при выборе пунктов меню Project->Compile All Projects И Project->Build All Projects. Остальные подпункты меню Project редко используются в работе и при желании могут быть изучены самостоятельно с помощью справочной системы Delphi. Подменю Run Команды подменю Run предназначены для управления запуском и отладкой приложений и разбиты на три логические группы. Первая группа команд управляет запуском приложения. Основные команды этой группы — Run->Run (запуск приложения) и Run->Parameters (эмуляция передачи параметров приложению через командную строку). Остальные команды данной группы выходят за рамки нашей книги. Вторая группа команд предназначена для пошагового выполнения программы во время отладки: Step Over
выполнить текущую строку программы, не заходя в подпрограммы, использованные в ней. Trace Into выполнить текущую строку программы, заходя в подпрограммы, использованные в ней. Trace to Next Source Line ... выполнить фрагмент кода до следующей строки исходного текста. Run to Cursor выполнить программу от места остановки до положения курсора в текстовом редакторе. Run Until Return выполнить подпрограмму до выхода из нее. Show Execution Point ... показать в текстовом редакторе место, на ко.тором остановлена программа. Program Pause приостановить выполнение программы и перейти в режим отладки. Program Reset остановить выполнение программы или отладку. И, наконец, последняя группа команд предназначена для просмотра информации о выполняемой программе: Inspect Evaluate/Modify 220
вызвать диалог просмотра состояния переменной, в том числе и являющейся ссылкой на экземпляр класса. Вызов диалога просмотра и изменения значения переменной.
Глава 11. Использование ИСР Delphi в разработке Add Watch Add Breakpoint
добавить переменную в список просматриваемых (сам список вызывается через пункт меню View->Debug Windows->Watches). группа команд добавления точек останова программы и перевода ее в отладочный режим.
Остальные пункты меню либо интуитивно понятны, либо выходят за рамки нашей книги. Однако мы будем в случае необходимости иногда ссылаться на них (с соответствующими пояснениями).
11.3. Компоненты. Палитра компонентов и формы Палитра компонентов и формы не имеют смысла друг без друга и представляют собой необходимый минимум для визуальной разработки приложения. Форма представляет собой заготовку окна, на котором расположены визуальные элементы управления и невизуальные компоненты, которые не будут видны пользователю, но будут обеспечивать какие-либо функциональные возможности программы в процессе ее выполнения. В каждом приложении может быть неограниченное количество форм и компонентов на них 29 . Компоненты, которые можно разместить на форме, представлены в главном окне среды разработки и для удобства разбиты на группы. Это и есть Палитра компонентов. Изначально, после установки Delphi в Палитре содержится некоторый набор компонентов, поставляемый вместе с Delphi причем он зависит от опций, указанных в процессе инсталляции. Далее набор компонентов может быть расширен добавлением новых компонентов. Каждый компонент в Палитре компонентов представляет собой некоторый класс — наследник класса TComponent, обладающего следующими существенными для визуального проектирования возможностями: 1. Интеграция в среду разработчика: возможность расположения компонента в Палитре компонентов, перенесения его на форму, то есть создание экземпляра данного класса, а также возможность управления его свойствами с помощью визуального построителя. Во время визуального проектирования компонент обычно имеет такое же визуальное представление, как и во время работы приложения, что существенно облегчает разработку программы. Следует заметить, что слово «неограниченное» в данном случае носит более чем условный характер. Каждый визуальный элемент на форме потребляет системные ресурсы, соответственно, количество элементов, отображенных на экране, существенно зависит от объема ресурсов, которыми располагает операционная система. Особенно это касается Windows 98 и более ранних версий, и, в меньшей степени, NT-систем, например, Windows 2000. Однако Delphi никаких ограничений такого рода не имеет, все зависит только от операционной системы. 221
Часть III. Методика создания программ в Delphi
2. Принадлежность: возможность управления другими компонентами в смысле ответственности за уничтожение компонентов, собственником которых является данный компонент. При создании экземпляра (объекта) компонента в конструкторе ему передается его владелец, который должен вызвать деструктор данного компонента перед вызовом своего деструктора. Например, при разрушении формы должны быть разрушены все компоненты, которые на ней находятся. Такой подход снимает необходимость разрушения объектов с разработчика программы. 3. Сохраняемость и восстанавливаемость: возможность записи значений свойств компонента в некоторый поток (например, файл на диске) и последующее восстановление этих значений из потока. Данное свойство широко используется для установки значений свойств компонентов во время проектирования, так как при сохранении проекта свойства записываются в файлы форм, а при чтении проекта состояние объектов считывается. То же самое происходит при запуске приложения. Сохраняемость и восстанавливаемость унаследована классом TComponent от класса TPersistent (англ. Persistent — постоянный), наследником которого он является. 4. Поддержка технологии СОМ: возможность преобразования компонентов Delphi в объекты ActiveX или другие СОМ-объекты, и наоборот, импорт компонентов ActiveX в Delphi с целью дальнейшего их использования с возможным расширением30. Итак, класс TComponent является базовым для создания компонентов. Прямые наследники этого класса не имеют визуального представления (если не добавляют его сами) и являются, соответственно, невизуальными. Невизуальные компоненты представлены в процессе разработки программы иконками, аналогичными сопоставляемым им в Палитре компонентов, и, так же как и другие компоненты, могут быть активными, то есть выбранными среди других компонентов формы для изменения их свойств. Компоненты, которые имеют визуальное представление в процессе выполнения программы и ее проектирования, унаследованы от потомка TComponent, класса TControl (англ. Control — управление), и называются визуальными. Такими компонентами представлены практически все стандартные элементы управления Windows, например кнопки, строки ввода и статические подписи. Также существует третий вид компонентов, которые изначально не видны пользователю, но могут появляться на экране в отдельных ок-
м
Для экспорта и импорта компонентов ActiveX в Delphi предусмотрены вспомогательные программы. Однако не все компоненты Delphi могут быть экспортированы в компоненты ActiveX, так как в Delphi возможности компонентов более широки. Также следует заметить, что технология СОМ используется только В ОС Windows и не может быть использована в кроссплатформенных (переносимых в среду Kylix) приложениях.
222
Глава 11. Использование ИСР Delphi в разработке
нах в случае необходимости — это формы и диалоги, однако формы являются наследниками класса TControl, а диалоги — наследниками TComponent через специализированный класс TCommonDialog (от англ. Common Dialog — унифицированный диалог). Класс TCommonDialog среди прочих методов добавляет к классу TComponent метод Execute, который является абстрактным и переопределяется конкретными компонентами-диалогами для вывода диалогового окна на экран. Среди такого рода компонентов в Delphi изначально присутствуют диалоги открытия и сохранения произвольных файлов, файлов с изображениями, диалоги для настройки печати и выбора принтера, выбора шрифта и цвета, а также диалоги поиска и замены. Работа с такими компонентами заключается в расположении компонента на форме, в результате чего автоматически создается экземпляр компонента, и затем в вызове их метода Execute во время выполнения программы. В процессе разработки программы такие компоненты представляются иконками, как и невизуальные компоненты. Начальный уровень иерархии компонентов Delphi представлен на рис. 11.3. I
Основные свойства классов Delphi
TObiect
I TPersistent ,—•
TComoonent
^
TControl | . Визуальные компоненты
Сохраняемость и восстанавливаемость
-»>
Интеграция с 1иЬ, принадлежность
•Toggle Form/Unit или воспользоваться клавишей F12. Для проекта, который мы в данный момент разрабатываем, модуль формы приведен в листинге 12.1. Листинг 12.1. Модуль формы unit Unitl; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForml = class(TForm) Labell: TLabel; Label2: TLabel; LabelS: TLabel; Editl: TEdit; Edit2: TEdit; EditS: TEdit; Buttonl: TButton; Button2: TButton; ButtonS: TButton; B u t t o n 4 : TButton; private ( Private d e c l a r a t i o n s } public { Public 'declarations } end; var Forml: TForml; 234
Глава 12. Общие принципы программирования в Delphi
implementation {$R *.dfm} end.
Итак, стандартный класс TForm расширяется в нашем приложении добавлением в него свойств-ссылок на объекты, соответствующие интерфейсным элементам, расположенным на форме. Так, в классе TForml присутствуют свойства Label, Label2 и Labels типа TLabel, соответствующие интерфейсным меткам, свойства Editl, Edit2 и Edit, соответствующие строкам ввода операндов и вывода результата, и свойства Buttonl, Button2, Buttons и Button4, соответствующие кнопкам вычисления результата. Из любого метода, реализованного в классе TForml и из любой части программы, имеющей доступ к объекту такого класса можно получить доступ 35 ко всем этим свойствам, а, следовательно, и к интерфейсным элементам .
12.3. Функциональность приложения Вернемся к концепции программы, управляемой событиями. Приложение, интерфейс которого сформирован в предыдущем разделе, должно реагировать на нажатие одной из кнопок, вычисляя некоторым образом результат, и помещая его в соответствующее поле ввода. Нажатие на кнопку является событием, на основе которого Windows создает специальную структуру данных, описанную выше. Данные приходят в оконную функцию приложения, затем Delphi направляет их тому компоненту, который и вызвал событие. Обработку событий мы подробно рассмотрим ниже, а пока будем считать, что обработчик события компонента — это процедура, вызываемая автоматически в ответ на событие, произошедшее в системе, и связанное с этим компонентом. Процедура-обработчик обычно является методом класса формы, которой принадлежит соответствующий интерфейсный элемент, однако это не обязательно. Дополнительная информация о событии передается в процедуру-обработчик через параметры. Список событий активного компонента представлен на закладке Events (англ. Event — событие) Инспектора объектов. Для выполнения какихлибо действий при нажатии пользователем на кнопку следует реализовать обработчик события OnClick (см. рис 12.4). Для этого следует два раза щелкнуть левой кнопкой мыши во втором столбце страницы E v e n s Инспектора объектов напротив заголовка 35
Заметим, что члены класса, не помеченные спецификатором области видимости, имеют область видимости public или published, в зависимости от состояния флага условной компиляции SM. Свойства, автоматически добавляемые Delphi — ссылки на интерфейсные элементы — как раз не имеют ограничения области видимости.
235
Часть III. Методика создания программ в Delphi Object Inspector ' > Label1 Properties Events | FocusControl OnClick 'OnContextPow :OnDblCljck' '^'T OnDiagDiop : OnDragOvei 1 OnEndDock I OnEndDrag OnMouseDowrj OnMouseEnteri QnMouseLeavi OnMouseMove OnMouseUp i OnStartbock j. OnStartbtag | All shown
Рис. 12.4. Закладка Events (события Инспектора объектов)
Onclick, после чего на экран будет выведено окно редактирования исходного текста программы, в котором будет открыт модуль формы, рассмотренный нами выше. В класс формы будет автоматически добавлен метод-обработчик события Onclick 36. Название метода состоит из названия компонента, которому принадлежит обработчик, и слова Click. Например, при создании обработчика события Onclick для кнопки с названием Buttonl будет сформирован следующий метод класса TForml: procedure TForml.ButtonlClick(Sender: TObject); begin ' end; Таким образом, если в процессе выполнения программы будет вызван метод ButtonlClick, значит, пользователь нажал на кнопку с подписью «+», то есть он хочет сложить числа, занесенные в строки ввода Editl и Edit2, а затем поместить результат в строку ввода Edits. Мы уже говорили о том, что значения, вводимые в строки Edit пользователем во время работы программы, хранятся в свойствах Text соответствующих объектов. Следовательно, задача обработчика события своНазвание процедуры-обработчика будет занесено в таблицу обработчиков данного компонента на странице Events Инспектора объектов, затем сохранено в файле формы. При запуске приложения сопоставление обработчика с интерфейсным элементом будет произведено автоматически.
236
Глава 12. Общие принципы программирования в Delphi
дится к преобразованию введенных строк в числовое представление, их сложение и вывод в строку ввода Edits. Для преобразования строк в значения с плавающей точкой мы воспользуемся функцией Delphi StrToFloat, а для обратного преобразования — функцией FloatToStr, имеющими следующий вид: Function StrToFloat(const S: S t r i n g ) : Extended; Function F l o a t T o S t r ( V a l u e : Extended): String;
Для обращения к свойствам Text объектов Editl и Edit2 будем использовать соответственно конструкции E d i t l . T e x t и E d i t 2 . T e x t . Обработчик события для кнопки сложения будет выглядеть следующим образом: procedure TForml.ButtonlClick(Sender: TObject); begin EditS.Text := FloatToStr(StrToFloat(Editl.Text) + StrToFloat(Edit2.Text)); end;
Теперь добавим обработчик события нажатия для кнопки вычитания операндов Button2. Переключимся в режим редактирования формы (клавиша F12), сделаем текущим компонент-кнопку с подписью «—», выберем в Инспекторе объектов закладку Events, и создадим обработчик события нажатия двойным щелчком левой кнопки мыши напротив события Onclick. В появившемся окне редактирования исходного текста программы реализуем автоматически созданную процедуру-обработчика ButtonlClick: procedure TForml.Button2Click(Sender: TObject); begin Edit3.Text := FloatToStr(StrToFloat(Editl.Text) StrToFloat(Edit2.Text)); end;
Аналогично можно добавить обработчики событий нажатия для кнопок умножения и деления Buttons и Button4: procedure TForml.ButtonSClick(Sender: TObject); begin Edit3.Text := FloatToStr(StrToFloat(Editl.Text) * StrToFloat(Edit2.Text)); end; procedure TForml.Button4Click(Sender: TObject); begin Edit3.Text := FloatToStr(StrToFloat(Editl.Text) / StrToFloat(Edit2.Text)); end;
237
Часть III. Методика создания программ в Delphi
12.4. Обработка исключительных ситуаций В разработанной нами программе могут возникать некоторые исключительные ситуации, связанные с некорректными исходными данными, вводимыми пользователем. Например, пользователь может ввести в поля ввода строки, которые 37 не являются числами . В этом случае в момент вызова функции strToFloat будет возникать исключительная ситуация eConvertError (от англ. Exception of Convert Error — исключительная ситуация ошибки преобразования), на экран будет выведено сообщение об ошибке, выполнение обработчика нажатия на кнопку будет досрочно прервано, а программа продолжит свое выполнение. Диалог с сообщением об ошибке представлен на рис. 12.5. Если запуск программы был выполнен из среды разработки Delphi, то перед появлением такого диалога будет выведен на экран дополнительный диалог с более полной информацией об исключении 38 , программа будет приостановлена и возобновит работу только после ее повторного запуска с помощью клавиши F9.
I l l '13,56' ts rats valid floating point value.
OK
Рис. 12.5. Сообщение об ошибке
Для того, чтобы избежать вывода сообщения об ошибке на экран, или вывести собственное сообщение, что более удобно для пользователя, необходимо обработать исключение. Для этого мы воспользуемся блоком обработки исключений T r y . .Except, причем не будем указывать, какое именно исключение следует обработать. В результате обработаны будут все исключения, возникающие внутри блока. В качестве реакции на ошибку, в ту строку ввода, куда выводится результат вычислений, будем выводить сообщение об ошибке: 7
Такие ошибки очень распространены в Windows из-за различных настроек форматов. Например, допускается изменение пользователем разделителя целой и дробной части вещественного чирла. Delphi ориентируется на национальные настройки системы, и при попытке ввода пользователем числа с разделителем-точкой при установленном в Windows разделителе-запятой будет возникать исключительная ситуация. ™ Только если установлен флаг Stop on Delphi Exceptions в диалоге Debugger Options, вызываемом через пункт главного меню Tools- > Debugger Options.
238
Глава 12. Общие принципы программирования в Delphi
Try {Попытка вычисления} Except {Вывод сообщения об ошибке} End; Для вывода сообщения.об ошибке воспользуемся свойством Text объекта Edits и присвоим данному свойству соответствующее значение: Edit3.Text := 'Ошибка в в о д а ! ' ; Тогда обработчики событий нажатия на кнопки будут выглядеть следующим образом: procedure TFo'rml .ButtonlClick (Sender : TObject); begin Try Edit3.Text := FloatToStr (StrToFloat(Editl.Text) ,+ StrToFloat(Edit2.Text)); Except Edit3.Text := 'Ошибка ввода!'; end; end;
t procedure TForml.Button2Click(Sender: TObject); begin Try Edit3.Text := FloatToStr(StrToFloat(Editl.Text) StrToFloat(Edit2.Text)); Except EditS.Text := 'Ошибка ввода!'; end; end; procedure TForml.ButtonSClick(Sender: TObject); begin Try EditS.Text := FloatToStr(StrToFloat(Editl.Text) * StrToFloat(Edit2.Text)); Except EditS.Text := 'Ошибка ввода!'; end; end; procedure TForml.Button4Click(Sender: TObject); begin
239
Часть 111. Методика создания программ в Delphi
Try Edit3.Text := FloatToStr(StrToFloat(Editl.Text) / StrToFloat(Edit2.Text)); Except Edit3.Text := 'Ошибка ввода!';
end; end;
Заметим, что программа в таком виде будет защищена и от ошибки деления на ноль, так как соответствующая исключительная ситуация будет обработана в методе Button4ciick тем же блоком, что и ошибка некорректного числового формата. На этом мы Закончим общее описание основ и начнем подробно разбирать некоторые существенные аспекты программирования в Delphi. Модуль, соответствующий форме нашего приложения, полученный в результате разработки, приведен в листинге 12.2. Листинг 12.2. Модуль формы (окончательный вариант) unit Unitl; interface uses Windows, Messages, SysUtils, Variants, Controls, Forms, Dialogs, StdCtrls; type TForml = class(TForm) Labell: TLabel; Label2: TLabel; Label3: TLabel; Editl: TEdit; Edit2: TEdit; EditS: TEdit; Buttonl: TButton; Button2: TButton; ButtonS: TButton; Button4: TButton; procedure TForml.ButtonlClick(Sender: procedure TForml.Button2Click(Sender: procedure TForml.ButtonSClick(Sender: procedure TForml.Button4Click(Sender: private { Private declarations } public { Public declarations ) end;
240
Classes, Graphics,
TObject); TObject); TObject); TObject);
Глава 12. Общие принципы программирования в Delphi
var
Forml: TForml; implemen ta ti on {$R *.dfm}
" •, procedure TForml.ButtonlClick(Sender: TObject); begin Try Edit3.Text := FloatToStr(StrToFloat(Editl.Text) + StrToFloat(Edit2.Text)); Except Edit3.Text := 'Ошибка ввода!'; end; end; procedure TForml.Button2Click(Sender: TObject); begin Try Edit3.Text := FloatToStr(StrToFloat(Editl.Text) StrToFloat(Edit2.Text)); Except Edits.Text := 'Ошибка ввода!'; end; end; procedure TForml.Button3Click(Sender: TObject); begin Try EditS.Text := FloatToStr(StrToFloat(Editl.Text) * StrToFloat(Edit2.Text)); Except Edits.Text := 'Ошибка ввода!'; end; end; procedure TForml.Button4Click(Sender: TObject); begin Try EditS.Text := FloatToStr(StrToFloat(Editl.Text) / StrToFloat(Edit2.Text)); Except EditS.Text := 'Ошибка ввода!'; end; • end;
">
end. 241
Часть III. Методика создания программ в Delphi
Вопросы с ответами для повторения по части III Поясните понятия проекта и группы проектов в Delphi Ответ: Проектом называется некоторый логически законченный набор файлов. Такая организация файлов позволяет среде разработчика следить за возможными изменениями и учитывать их в процессе компиляции. Это особенно важно с .учетом того, что один и тот же файл может использоваться в разных проектах. Группой проектов называется набор проектов. Объединение проектов в группу существенно упрощает параллельную разработку нескольких проектов.
Опишите основные инструменты, предназначенные для организации проектов и их групп Ответ: Команды, предназначенные для управления проектом и группой про-' ектов, собраны в подменю Project и File главного меню интегрированной среды разработчика. Среди пунктов подменю File следует отметить такие пункты, как Save Project as, Save All, Open и Reopen, управляющие соответственно сохранением и открытием проектов и групп проектов. В подменю Project наиболее используемыми являются Add to Project, Remove from Project, Compile Project, Compile All Projects, Build, Build All Projects, Syntax Check и Options, предназначенные соответственно для добавления и удаления файлов из проекта, компиляции и построения активного проекта или всех проектов в группе, синтаксической проверки активного проекта, а также настроек проекта. Для работы с группами проектов предназначен диалог Менеджера проектов, который представляет структуру группы проектов в-виде дерева, и может быть выведен на экран с помощью пункта главного меню View->Project Manager. Добавление проекта в группу с помощью данного диалога производится кнопками New и Remove, а активизация какого-либо проекта из группы — мышью или кнопкой Activate.
Расскажите о файлах, соответствующих каждой форме приложения в Delphi Ответ: Среда Delphi автоматически формирует и дополняет в процессе визуального построения приложения два файла: » файл описания формы (имеет расширение dfm), в котором содержится описание интерфейсных элементов, расположенных на форме. Файл описания формы используется системой для внутренних целей, а именно, для хранения значений свойств компонентов, которые представлены в Инспекторе объектов во время визуального построения формы. Изменение данного файла практически никогда не требуется, а основная часть работы происходит с файлом модуля.
242
Вопросы с ответами для повторения по части III
файл модуля формы (имеет расширение pas), в котором располагается описание класса формы — наследника класса TForm. Класс формы является логическим отображением формы и содержит, например, ссылки на все интерфейсные компоненты, добавленные на этапе визуального проектирования. Также обработчики событий от компонентов, расположенных на форме, обычно являются методами класса формы.
Опишите структуру приложения на Delphi, расскажите о видах интерфейса приложения с точки зрения расположения его окон на экране Ответ: Приложение состоит из одного или нескольких окон, одно из которых является главным. По умолчанию главным окном разрабатываемого приложения является то, которое создается при открытии нового приложения. Соответственно, при запуске программы, это окно сразу же будет отображено на экране. Какое именно окно является главным, можно указать в диалоге Project Options, вызываемом с помощью пункта главного меню Project->Options. Остальные формы отображаются на экране после вызова их методом Show. Приложения Delphi поддерживают два вида пользовательского интерфейса: « MDI — Multi Document Interface — Интерфейс множества документов, когда окно является главным, а все остальные окна находятся в его пределах. Такой интерфейс использован, например, в Microsoft Word. * SDI — Single Document Interface — Приложение одного документа, когда одно окно является главным, но остальные окна могут самостоятельно перемещаться по экрану, независимо друг от друга и от главного окна (если иное не предусмотрено логикой программы). Такой интерфейс реализован в среде разработчика Delphi.
Опишите процесс построения приложения в Delphi Ответ: Построение приложения на Delphi состоит из следующих основных этапов: 1. Создание проекта с помощью главного меню среды разработчика (File-> New->Application), или с помощью Менеджера проектов (кнопка New). 2. Визуальное проектирование пользовательского интерфейса в виде формы, на которой располагаются элементы управления, перенесенные из Палитры компонентов. Поведение компонентов настраивается с помощью свойств, представленных в Инспекторе объектов. 3. Наполнение функциональностью интерфейса, полученного в результате визуального проектирования, через реализацию событий компонентов. 4. Обеспечение устойчивой работы приложения через обработку исключительных ситуаций, реализация более дружественного взаимодействия с пользователем с помощью вывода на экран дополнительной информации для пользователя.
243
Часть III. Методика создания программ в Delphi Опишите основные инструменты среды разработчика Delphi Ответ: Среда разработчика Delphi реализована по принципу SDI — приложение одного документа — и состоит из набора окон, которые могут быть расположены в любом месте экрана независимо друг от друга. Можно указать основные окна, используемые при разработке большинства программных продуктов: 1. Главное окно с Палитрой компонентов и главным меню. 2. Инспектор объектов, предназначенный для настройки компонентов, расположенных на форме. 3. Менеджер проектов, предназначенный для удобной работы с группами проектов. 4. Окно исходного текста программы, предназначенное для реализации логики программы, которая не может быть достигнута путем визуального построения. 5. Окно навигации по коду и окно дерева объектов формы, позволяющие быстро перемещаться между элементами программы.
244
ЧАСТЬ IV
КОМПОНЕНТЫ И ИХ
ИСПОЛЬЗОВАНИЕ !
IS
АРХИТЕКТУРА СИСТЕМЫ КОМПОНЕНТОВ DELPHI ••
t4
/5
'
ВИЗУАЛЬНЫЕ КОМПОНЕНТЫ
НЕВИЗУАЛЬНЫЕ КОМПОНЕНТЫ
?б
СОЗДАНИЕ КОМПОНЕНТОВ ВО ВРЕМЯ ВЫПОЛНЕНИЯ ПРОГРАММЫ
/7
ИСПОЛЬЗОВАНИЕ ДИАЛОГОВЫХ КОМПОНЕНТОВ
1$
ФОРМЫ
ОСНОВЫ DELPHI. Профессиональный подход
73
Архитектура системы компонентов Delphi
Задача настоящей главы — глубокое изучение структуры компонентов Delphi в целом и основ использования различных групп компонентов. Для начала мы изучим верхний уровень иерархии компонентов, о котором уже начали говорить в предыдущих главах, а затем перейдем к детальному рассмотрению классов, на основе которых построены компоненты, представленные в Палитре компонентов главного окна интегрированной среды разработчика Delphi.
13.1. Класс TObject На рис. 13.1 представлен верхний уровень иерархии компонентов Delphi. Ключевой класс, наследниками которого являются не только компоненты, но и вообще все классы в Delphi-программе, называется TObject.
I
TObiect
J
Основные свойства классов Delphi
TPersistent ers
I
Сохраняемость и восстанавливаемость
TComponent TControl Визуальные компоненты
TCommonDialofi Невизуальные компоненты
1
Диалоги
Рис. 13.1. Иерархия компонентов Delphi 246
Глава 13. Архитектура системы компонентов Delphi
Данный класс обеспечивает фундаментальные основы поведения, присущие всем без исключения объектам, инкапсулируя методы, которые: 1. Создают и разрушают экземпляры классов, выделяя и освобождая память, а также инициализируя объекты. 2. Реагируют на создание и разрушение экземпляров классов. 3. Возвращают информацию об объектах и классах, к которым принадлежат эти объекты, а также информацию времени выполнения (RTTI — Run Time Type Information) о published-свойствах, реализованных в соответствующих классах. 4. Поддерживают обработку событий объектами. 5. Поддерживают интерфейсы, реализуемые классами.
13.1.1. Поддержка жизнедеятельности объектов Создание объекта Создание экземпляра класса с точки зрения программы производится с помощью вызова конструктора соответствующего класса. Конструктор определен в классе TObject в следующем виде: Constructor Create; Конструктор класса TObject выделяет в памяти место под объект и заполняет это место нулевыми значениями. Дополнительная инициализация данных не происходит, так как класс TObject не располагает информацией о полях своих наследников. Если какое-либо поле наследника класса TObject (а наследниками этого класса являются все классы в Delphi) должно иметь при создании экземпляра класса значение, отличное от нуля, то в этом случае необходимо переопределение конструктора. Механизм работы конструктора основан на использовании трех дополнительных методов класса TObject: class Function N e w l n s t a n c e : T O b j e c t ; virtual; class Function I n i t l n s t a n c e ( I n s t a n c e : P o i n t e r ) : class Function i n s t a n c e S i z e : L o n g i n t ;
TObject;
При вызове конструктора, управление передается методу Newlnstance, который с помощью метода InstanceSize определяет объем памяти, необходимый для размещения нового объекта, выделяет память, и вызывает метод Initlnstance. В свою очередь метод Initlnstance заполняет выделенную память нулевыми значениями и инициализирует таблицу виртуальных методов. Таким образом, результатом работы метода N e w l n s t a n c e является ссылка на вновь созданный объект.
247
Часть IV. Компоненты и их использование
Несмотря на то, что метод Newlnstance является виртуальным, так как помечен ключевым словом v i r t u a l , его переопределение практически никогда не требуется. Изменение данного метода в наследниках может быть необходимо для более гибкого управления памятью при одновременном создании множества объектов одного и того же класса. В случае изменения метода N e w l n s t a n c e потребуется и адекватное изменение метода Freelnstance, рассмотренного ниже при описании механизма разрушения экземпляров класса TObject. Таким образом, конструктор выполняет минимально необходимые действия для создания объекта. Поэтому при его переопределении сначала должна быть вызвана версия конструктора, описанная в родительском классе. Напомним, что родительские версии переопределенных методов, в том числе и конструкторов, вызываются с помощью указания ключевого слова Inherited. Рассмотрим небольшой пример создания класса — наследника TObject, в котором переопределен конструктор для дополнительной инициализации свойства. Код примера приведен в листинге 13.1. Листинг 13.1. Пример создания класса — наследника TObject Dnit Unitl; Interface 1
Type MyClass = Class(TObject) private fName: String; {Описание свойства, которое необходимо инициализировать в конструкторе} public ! Constructor Create; {Указание на необходимость переопределения конструктора} end; Implementation с
Constructor MyClass.Create; , begin Inherited; {Вызов родительской версии конструктора — выделение памяти и инициализация полей нулевыми значениями} fName := 'MyClass'; {Дополнительная инициализация свойства fName} end; end.
248
Глава 13. Архитектура системы компонентов Delphi
В заключение добавим, что, если в конструкторе произошла исключительная ситуация, то ее можно обработать обычным образом — с помощью блока обработки T r y . .Except. Если же исключительная ситуация не будет обработана, то есть она выйдет за пределы конструктора, то работа конструктора заканчивается, и вызывается деструктор для освобождения памяти, уже занятой под ошибочный экземпляр класса. Разрушение объекта Разрушение экземпляра класса производится с помощью вызова метода Free класса TObject, описанного в следующем виде: Procedure Free; Метод Free должен быть вызван для любого объекта, созданного во время выполнения программы и не имеющего владельца39, а для объектов, имеющих владельцев, метод Free будет вызван автоматически. Метод Free проверяет, не является ли ссылка на объект, для которого этот метод вызывается, нулевой, и, если ссылка корректна, вызывает деструктор объекта, описанный в классе TObject следующим образом: Destructor Destroy; virtual; Таким образом, вызов метода Free, в отличие от прямого вызова Destroy, не заканчивается ошибкой, если ссылка на объект, для которого Free вызывается, пуста, то есть имеет значение nil. Однако заметим, что, если ссылка некорректна, то есть указывает на некоторый ненулевой адрес памяти, в котором нет объекта для разрушения, то метод Free будет работать некорректно, причем поведение программы в этом случае непредсказуемо. Когда метод Free вызывается для экземпляров-наследников класса TComponent, которым принадлежат другие компоненты, метод Free будет вызван для всех этих компонентов. Благодаря этому с разработчика снимается необходимость внимательно следить за множеством объектов, созданных в программе. Так, например, разрушаются объекты, соответствующие интерфейсным элементам, расположенным на формах, при разрушении формы методом Free. Аналогичные методы вызываются и для компонентов, которые принадлежат форме. Все формы в Delphi, в свою очередь, по умолчанию принадлежат объекту Application класса TApplication, создаваемому в главном файле проекта. Класс Application является наследником TComponent. Поэтому при разрушении Application, разрушаются и формы, созданные в приложении. " Понятие принадлежности (наличия «владельца») определено только для компонентов и описано в данной главе при обсуждении класса TComponent.
249
Часть IV. Компоненты и их использование
Деструктор Destroy не предназначен для прямого вызова, однако именно в нем реализуется освобождение ресурсов, полученных экземпляром класса при вызове конструктора или во время его работы. Для переопределения деструктора следует указывать ключевое слово override, так как он является виртуальным методом (помечен ключевым словом v i r t u a l в описании класса T O b j e c t ) . При изменении деструктора в классе-наследнике последней строкой должен быть произведен вызов родительской версии переопределенного деструктора. Это необходимо для корректного завершения жизненного цикла объекта. Деструктор, описанный в классе TObject, освобождает память, занятую под объект, с помощью методов Cleanuplnstance и Freelnstance: Procedure Cleanuplnstance; Procedure Freelnstance; virtual; Метод Cleanuplnstance освобождает память, занятую под динамические строки и вариантные структуры данных. Таким образом, он в некотором роде аналогичен методу Initlnstance, вызываемому конструктором для начального обнуления данных объекта. Метод Freelnstance выполняет действия, противоположные методу Newlnstance, то есть освобождает память, выделенную в методе Newlnstance, используя значение, возвращаемое методом InstanceSize. Отметим, что деструктор может быть вызван не только методом Free в ходе нормального разрушения объекта, но и при исключительной ситуации, возникшей при выполнении конструктора. Таким образом, перед освобождением ресурсов, которое производится в деструкторе, следует проверить, выделены ли эти ресурсы на самом деле. В заключение приведем пример переопределения деструктора для освобождения памяти, выделенной в конструкторе (см. листинг 13.2). Листинг 13.2. Пример переопределения деструктора для освобождения памяти, выделенной в конструкторе Unit Onitl; Interface Type MyClass = Class(TObject) private fMemory: Pointer; public Constructor Create; Destructor Destroy; end; 250
Глава 13. Архитектура системы компонентов Delphi
Implementation Constructor MyClass.Create; begin Inherited; {Вызов переопределенного конструктора, выделение памяти под объект, инициализация полей. В поле fMemory автоматически заносится значение nil) GetMem(fMemory, 1024); {Выделение памяти под переменную fMemory размером 1024 байт, в этом месте может произойти исключительная ситуация} end; Destructor MyClass.Destroy; begin if fMemory nil {Проверка на корректность ссылки. Если при выделении памяти произошла ошибка, то в поле fMemory находится nil} then FreeMem(fMemory, 1024); {Освобождение памяти, если она была выделена} Inherited; end;
end.
13.1.2. Реакция на создание и разрушение объектов Реакция на создание и разрушение экземпляров классов обеспечивается в Delphi методами AfterConstruction и BeforeDestruction, описанными в классе TObject: Procedure Af t e r C o n s t r u c t i o n ; virtual; Procedure B e f o r e D e s t r u c t i o n ; virtual;
Оба метода, описанные в классе TObject, не выполняют каких-либо действий, и предназначены для переопределения в классах-наследниках. Метод AfterConstruction вызывается автоматически после полного и безошибочного завершения конструктора. В принципе, все действия, которые обычно выполняются в переопределенных версиях этого метода, можно было бы выполнить и в конструкторе. 251
Часть IV. Компоненты и их использование
Однако реализованная в Delphi структура объекта более корректна. Поясним, почему. При возникновении исключительной ситуации в конструкторе объект не будет создан, а вместо этого будет вызван деструктор. С другой стороны, при ошибке в методе A f t e r C o n s t r u c t i o n объект будет создан, а исключительная ситуация будет выброшена за пределы объекта. Например, класс формы TForm, который является наследником TObject, переопределяет метод AfterConstruction для вызова обработчика сообщения о создании формы. Если обработчик вызывался бы из конструктора, то при ошибке в нем не был бы создан объект формы. Выходом из такой ситуации может быть защищенный вызов процедуры-обработчика из блока T r y . .Except. Однако в этом случае будет затруднена отладка приложения, так как разработчик не получит уведомление об исключительной ситуации. Если при создании экземпляра класса конструктор выполнен без ошибок, то при разрушении этого экземпляра непосредственно перед вызовом деструктора будет автоматически вызван метод Bef oreDestruction. Таким образом, методы AfterConstruction и BeforeDestruction предназначены для выполнения действий, специфичных для объектов некоторого класса, в котором эти методы могут быть переопределены, причем в момент их вызова объект не находится ни в стадии создания, ни в стадии разрушения, то есть возможен доступ ко всем структурам объекта.
13.1.3. Информация об объектах и классах В классе TObject реализованы методы, возвращающие различную информацию об экземплярах классов, необходимую во время выполнения программы для доступа к методам и свойствам, в том числе и к publishedсвойствам. Большинство методов используется Delphi для внутренних целей и не рекомендуется к использованию в приложениях. Наиболее простые методы такого рода — это ClassName и CLassNamels, возвращающие, соответственно, название класса, экземпляром которого является объект, из которого вызывается метод ClassName, и признак принадлежности объекта к классу, заданному именем: class Function ClassName: ShortString;
class Function ClassNamels(Const Name: string): Boolean; Для определения места заданного объекта в иерархии классов предусмотрены методы C l a s s P a r e n t , C l a s s T y p e и InheritsFrom, возвращающие, соответственно, ссылки на родительский и свой класс, а также признак принадлежности к заданному классу, эквивалентный оператору is. 252
Глава 13. Архитектура системы компонентов Delphi class Function ClassParent: TClass; Function ClassType: TClass; class Function InheritsFrom(aClass: TClass): Boolean; Тип данных TClass, использованный в приведенных описаниях является, так называемым метаклассом или ссылкой на класс — указателем на область памяти, в которой находится RTTI-информация о заданном объекте. Еще один метод, аналогичный вышеописанным, это c l a s s l n f o , возвращающий нетипизированный указатель на таблицу RTTI: class Function Classlnfo: Pointer; Использование данного метода — редкое явление, более того, формат таблицы, на которую возвращается ссылка, может быть изменен в более поздних версиях Delphi, поэтому обычно используются другие методы для получения информации времени выполнения. Оставшиеся методы, поддерживающие работу объекта во время выполнения программы, сводятся к операциям с адресами методов и свойств: Function FieldAddress(Const Name: ShortString): Pointer; class Function MethodAddress(Const Name: ShortString): Pointer; class Function MethodName(Address: Pointer): ShortString; Как видно из названий этих методов, они возвращают ссылки на свойства и методы по заданным именам, и имена методов по заданному адресу.
13.1.4. Обработка событий объектов Объекты, унаследованные от TObject, могут обмениваться событиями с системой и между собой. Каждое событие имеет свой идентификатор, на основе которого специальный метод Dispatch класса TObject может выбрать метод, который обрабатывает заданное событие. Procedure Dispatch(var Message); virtual; Если метод Dispatch не может обнаружить необходимый метод, он вызывает метод DefaultHandler, передавая ему сообщение, полученное в качестве параметра: Procedure D e f a u l t H a n d l e r ( v a r Message); virtual;
13.1.5. Объектные интерфейсы Понятие интерфейса в Delphi Введение механизма объектных интерфейсов в Delphi призвано восполнить отсутствие так называемого множественного наследования, используемого в некоторых языках программирования, например в C++. 253
Часть IV. Компоненты и их использование
Множественное наследование заключается в возможности создания некоторого дочернего класса на основе не одного, а нескольких родительских. Данный инструмент может быть очень удобным в программировании для наделения нескольких разных классов, не являющихся потомками одного и того же класса, одинаковой дополнительной функциональностью . Например, в Delphi предусмотрена целая линейка визуальных компонентов, унаследованных от класса TWinControl, в котором предусмотрено множество свойств и методов, присущих окну в Windows. Однако для управления окном в Windows предусмотрены и некоторые функции, не реализованные в Delphi по тем или иным причинам. Например, в Windows существует возможность изменять форму оконного компонента, а в Delphi нет. Если метод, устанавливающий форму окна, будет добавлен в класс T W i n C o n t r o l , то все наследники этого класса получат к нему доступ, но для этого требуется перекомпилировать стандартную библиотеку визуальных компонентов Delphi, что невозможно. Множественное наследование могло бы помочь решить эту проблему через объединение всех оконных классов с некоторым другим, который поддерживает работу с формой окна. Описанный случай на самом деле является практически единственным (кроме случая вообще не связанных между собой классов), когда множественное наследование действительно необходимо. Во всех остальных случаях можно обойтись и без него, так как если доступ к классу — общему родителю других классов — открыт, то необходимую функциональность можно добавить и к нему. К недостаткам множественного наследования можно отнести сложность в использовании, в частности, из-за проблем пересечения идентификаторов в объединяемых классах и усложнении иерархии классов. Вообще, современные языки программирования, в основном, отказываются от поддержки множественного наследования40, взамен предлагая механизм интерфейсов. Итак, объектный интерфейс в Delphi, или просто интерфейс, представляет собой описание структуры, аналогичной классу, за исключением того, что интерфейсы не имеют свойств, методы интерфейса не должны (и не могут) быть реализованы, а экземпляр интерфейса не может быть создан в программе. Также интерфейсы не поддерживают областей видимости, и все их методы являются доступными для любых фрагментов программы, то есть, на самом деле, имеют область видимости public. Описание интерфейса производится в разделе Туре и выглядит следующим образом:
™ Например, Java и С#. 254
Глава 13. Архитектура системы компонентов Delphi Туре = Interface(); Оаголовок метода 1>; Оаголовок метода N>; end; Таким образом, описание интерфейса аналогично описанию класса, все методы которого абстрактные41. Сам интерфейс в программе смысла не имеет, а предназначен для реализации его в описаниях классов, в связи с чем интерфейсная часть описания класса может быть расширена указанием, какие именно интерфейсы поддерживаются данным классом (реализованы в нем): Туре TInterfacedObject = class(, , . . . , ); Оаголовки методов, описанные в интерфейсах> end; При описании класса, который реализует один или несколько интерфейсов, в заголовке интерфейсной части его описания необходимо указывать название родительского класса, даже если этим классом является TObject. Все методы, заявленные в указанных интерфейсах, должны быть приведены в интерфейсной части описания класса, и затем х реализованы в его описательной части, либо помечены ключевым словом abstract. Рассмотрим пример использования интерфейсов в программе (листинг 13.3). Опишем два интерфейса, а затем класс, который их реализует, процедуру, в которой создается экземпляр данного класса, присваивается ссылке на интерфейс, а затем по этой ссылке вызывается метод, заявленный интерфейсом и реализованный классом. Листинг 13.3. Пример использования интерфейсов в программе Unit Usinglnterfaces; Interface Type Interfacel = Interface Procedure IntlMethod; end; {Описание интерфейса, заявлен один метод (процедура) IntlMethod} 41
Заметим, что, как видно из описания интерфейса, интерфейсы тоже поддерживают наследование. 255
Часть IV. Компоненты и их использование Interface2 = Interface Function Int2Method: Integer; end; {Описание интерфейса, заявлен один метод (целочисленная функция) Int2Method}
InterfacedClass = Class(TObject, Interfacel, Interface2) {Описание класса-наследника TComponent, реализующего два интерфейса — Interfacel и Interface2} Procedure IntlMethod; {Заголовок метода IntlMethod, который будет реализован в описательной части модуля} Function Xnt2Method: Integer; virtual; abstract; {Заголовок метода Int2Method, который не будет реализован, так как является абстрактным) end; Implementation Procedure InterfacedClass.IntlMethod; begin {Реализация метода} end; {Описание метода IntlMethod класса InterfacedClass, представляющего собой реализацию соответствующего метода интерфейса Interfacel} Procedure Uselnterface; Var Int: Interfacel; {Описание ссылки на класс, реализующий интерфейс Interfacel} IntClass: InterfacedClass; {Описание ссылки на класс InterfacedClass} begin IntClass := InterfacedClass.Create; {Создание экземпляра класса InterfacedClass и занесение ссылки в переменную-указатель на интерфейс} Int := IntClass;{Копирование ссылки на класс в переменнуюссылку на интерфейс Interfacel. Операция допустима, так как в классе InterfacedClass реализован интерфейс Interfacel.} Int.IntlMethod; {Вызов метода интерфейса Interfacel из экземпляра класса InterfacedClass} IntClass.Free; {Разрушение объекта через переменную-ссылку на класс, так как в интерфейсе Interfacel не описан метод Free.} end; End.
256
Глава 13. Архитектура системы компонентов Delphi
Базовый интерфейс (Interface Интерфейсы, так же как и классы, поддерживают наследование. То есть класс, реализующий некоторый интерфейс, должен описать все методы, заявленные в этом интерфейсе, и методы, заявленные в его родительском интерфейсе. Если интерфейс описывается без указания родительского интерфейса, 42 то его родителем считается интерфейс Ilnterface . В данном интерфейсе описаны следующие методы, поддерживающие работу со ссылками на экземпляры классов, реализующих интерфейсы: Function _AddRef: I n t e g e r ; stdcall; Function _Release: I n t e g e r ; stdcall; Function Querylnterface(const IID: TGUID; H R e s u l t ; stdcall;
out O b j ) :
Методы _AddRef и _Release управляют счетчиком обращений к интерфейсу, то есть количества созданных экземпляров интерфейса. Метод Querylnterface возвращает ссылку на интерфейс, заданный его уникальным идентификатором. Запрашиваемый интерфейс должен быть реализован классом, из экземпляра которого вызывается метод Querylnterface. » Глобальный уникальный идентификатор GUID В описании интерфейса можно указать его глобальный уникальный идентификатор (от англ. GUID — Globally Unique Identifier) — строку символов, имеющую следующий вид: [v{хххххххх-хххх-хххх-хххх-хххххххххххх}']
где х может быть любой шестнадцатеричной цифрой (от '0' до Т'). Данный идентификатор необходимо добавлять в описание интерфейса для поддержки работы функций, определяющих принадлежность объектов интерфейсам во время выполнения программы. Новый GUID можно сформировать, нажав комбинацию клавиш Ctrl+Shift+G в окне редактирования текста. Описание интерфейса с GUID выглядит следующим образом:
Туре = Interface(); Л [ {хххххххх-хххх-хххх-хххх-хххххххххххх}'] Оаголовок метода 1>; Оаголовок метода N>; end; 4г
В Delphi предусмотрен еще один базовый интерфейс, аналогичный [Interface, для поддержки СОМ-объектов. Этот интерфейс называется ILJnknown, является наследником Ilnterface, и реализуется системой автоматически при использовании СОМ-технологии. 9 Зак. 867
257
Часть IV. Компоненты и их использование
Поддержка интерфейсов классом TObject Поддержка интерфейсов реализована в классе TObject тремя методами: GetlnterfaceTable .... возвращает ссылку на таблицу интерфейсов, реализованных в классе, из экземпляра которого вызывается этот метод. Getlnterface возвращает ссылку на заданный через GUID интерфейс. GetlnterfaceEntry возвращает информацию о расположении интерфейса внутри объекта. Описание методов таково: class Function GetlnterfaceTable: PInterfaceTable; Function Getlnterface(Const IID: TGUID; out Obj): Boolean; class Function GetlnterfaceEntry(Const IID: TGUID): PInterfaceEntry; В функциях, которые принимают в качестве параметра глобальный уникальный идентификатор, допускается указание названия интерфейса, вместо которого GUID будет подставлен автоматически.
13.2. Класс TPersistent Класс TPersistent является следующей ступенью компонентной модели Delphi после класса TObject и инкапсулирует методы для поддержки переносимости информации из одного объекта в другой, а также для записи и чтения значений свойств объектов в потоки, в том числе и файловые (например, в файлы форм).
13.2.1. Переносимость информации между объектами В любой момент времени выполнения программы состояние экземпляра класса определяется набором значений его свойств. В классе TPersistent определены методы A s s i g n и A s s i g n T o для копирования значений из одного объекта в другой объект того же класса. Procedure A s s i g n ( S o u r c e : Procedure A s s i g n T o ( D e s t :
TPersistent); TPersistent);
virtual; virtual;
Метод A s s i g n предназначен для занесения свойств объекта, заданного параметром Source, в свойства объекта, для которого вызывается данный метод. В классе T P e r s i s t e n t метод A s s i g n реализован таким образом, что вызывает метод A s s i g n T o того объекта, 258
Глава 13. Архитектура системы компонентов Delphi
который задан параметром Source. Обычно метод A s s i g n переопределяется в классах-наследниках для перенесения значений свойств, присущих экземплярам именно этого класса. Метод AssignTo предназначен для присвоения значений свойств объекта, из которого он вызывается, свойствам объекта, заданного параметром Dest, и по умолчанию выбрасывает исключение EConvertError. В качестве примера использования переноса значений свойств можно привести класс TMemo, реализующий многострочный редактор. И хотя в самом классе TMemo метод A s s i g n не переопределен, и при попытке его вызова выбрасывается исключение EConvertError с сообщением ^Cannot assign a TMemo to a TMemo', но зато данный метод определен в свойстве Lines этого класса. В свойстве Lines находятся строки текста, введенные пользователем в процессе работы с компонентом. Вызов метода A s s i g n для свойства Lines одного экземпляра класса TMemo с указанием свойства Lines другого экземпляра класса TMemo в качестве параметра, приводит к ко^ пированию введенной пользователем информации из одного многострочного редактора в другой: Memol.Lines.Assign(Memo2.Lines);
13.2.2. Сохраняемость свойств В Delphi реализована возможность сохранения property-свойств без каких-либо дополнительных усилий со стороны разработчика прикладного программного обеспечения для всех классов без исключения, в том числе и не являющихся наследниками TPersistent. Под сохраняемостью же свойств экземпляров именно этого класса понимается возможность сохранения свойств объектов, не заявленных в секции published, в файлы форм и другие потоки данных. Такая возможность может потребоваться при создании компонентов, свойства которых ссылаются на другие компоненты, созданные во время выполнения программы или ее визуальной разработки43. Для поддержки сохраняемости свойств предназначен метод Def ineProperties, который вызывается при записи или чтении свойств объекта, и должен быть переопределен для выполнения специфических для соответствующего класса действий: Procedure D e f i n e P r o p e r t i e s ( F i l e r :
T F i l e r ) ; virtual;
Необходимо отметить, что с точки зрения компонента время выполнения программы и визуальная разработка практически не отличаются друг от друга, так как во время визуальной разработки создается реальный экземпляр компонента и вызываются все необходимые методы.
259
Часть IV. Компоненты и их использование
Методу Define Properties передается ссылка на объект, предназначенный для чтения и записи компонентов в поток. С помощью данной ссылки объект, переопределивший метод DefmeProperties, может записать в заданный поток любую информацию, или считать ее из потока. Более глубокое изучение метода имеет смысл только для разработчиков сложных компонентов и выходит за рамки нашей книги.
13.3. Класс TComponent Класс TComponent является базовым классом для всех компонентов в Delphi и реализует следующие возможности: * Интеграция с IDE Delphi, то есть возможность находиться в Палитре компонентов IDE и участвовать в процессе визуального проектирования. » Поддержка свойства принадлежности, то есть управления другими компонентами. * Поддержка сохраняемости и восстанавливаемости, реализованная в классе TPersistent. * Поддержка технологии СОМ, то есть возможности экспорта компонентов в компоненты ActiveX или импорта компонентов ActiveX в компоненты Delphi.
13.3.1. Именование компонентов и доступ к ним из программного кода Именование компонентов во время разработки Поскольку визуальная разработка приложения в Delphi подразумевает использование в программном коде ссылки на компонент, добавленный на форму средствами визуального построителя, то компоненты должны поддерживать некоторые правила именования для обеспечения совместного доступа к ним из модуля формы и из построителя. Для создания пространства имен компонентов в класс TComponent введено property-свойство Name, доступное во время визуальной разработки программы через Инспектор объектов. В модуле формы данный идентификатор является ссылкой на соответствующий компонент для доступа к нему во время выполнения программы. При перенесении компонента на форму из Палитры компонентов ему назначается имя по умолчанию, состоящее из имени класса без префикса «Т» и порядкового номера компонента данного класса на форме. Например, при добавлении на форму первого компонента типа 260
Глава 13. Архитектура системы компонентов Delphi
TLabel ему назначается имя Label 1. При добавлении второго такого же компонента ему будет назначено имя Label2 и так далее. Одновременно с добавлением компонентов на форму IDE автоматически изменяет модуль формы, добавляя в описание класса формы ссылки на новые компоненты: TForml = class(TForm) Label1: TLabel; Label2: TLabel; Label3: TLabel; private { Private declarations } public { Public declarations } end;
Во время визуальной разработки возможно изменение имени активного компонента. При этом название ссылки на него в классе формы изменяется соответствующим образом. Например, если переименовать компонент Label 1 в lUserName, то класс формы будет выглядеть следующим образом: TForml = class(TForm) lUSerName: TLabel; Label2: TLabel; Label3: TLabel; private { Private declarations } public { Public declarations } end; Заметим, что ссылки на компоненты, описанные в классе формы, имеют область видимости published. При попытке изменить область видимости приложение не сможет быть запущено, а на экран будет выведено сообщение о невозможности обнаружить ссылку на компонент в классе формы: TForml = class(TForm) private { Private declarations } lUserName: TLabel; Label2: TLabel; LabelS: TLabel; public { Public declarations } end;
Сообщение имеет следующий вид: "Exception EClassNotFound in module ... at . . . " . 261
Часть IV. Компоненты и их использование
Именование компонентов во время выполнения программы Свойство Name, имеет область видимости published, причем оно может быть как считано, так и изменено в процессе выполнения программы. Однако ссылки на компонент, имя которого изменено во время выполнения программы, теряют актуальность, а их использование недопустимо. Проиллюстрируем описанную особенность свойства Name на примере. Создадим новый проект, в котором автоматически будет открыта одна форма (листинг 13.4) Листинг 13.4. Модуль формы без добавленных к ней компонентов unit Unitl; interface uses
Windows, Messages, SysUtils, Variants, Cla'sses, Graphics, Controls, Forms, Dialogs; type TForml = class(TForm) private { Private declarations } public { Public declarations } end;
var
Forml: TForml; implementation {$R *.dfm} end.
Теперь добавим на форму компонент TLable, которому будет автоматически присвоено имя Labell, и компонент TButton, которому присвоится имя Buttonl (см. рис. 13.2). Tfe-Forml . Labell. • . • Buttonl
Рис. 73.2. Разработка формы
262
Глава 13. Архитектура системы компонентов Delphi
Перейдя на закладку Events Инспектора объектов, создадим обработчик события Onclick компонента Buttonl. Внутри обработчика (соответственно, данный фрагмент кода будет выполнен во время выполнения программы после нажатия на кнопку) изменим имя компонента Labell на другое (LabelNotl): .
procedure TForml.ButtonlClick(Sender: TObject); begin Labell.Name := 'LabelNotl'; end;
Таким образом, модуль формы будет выглядеть так, как в листинге 13.5: Листинг 13.5. Модуль формы unit Unitl; interface i uses Windows, Messages, Syslltils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForml = class(TForm) Labell: TLabel; Buttonl: TButton; procedure ButtonlClick(Sender: TObject); private { Private declarations } public { Public declarations } end; var Forml: TForml;
,
r implementation {$R *.dfm}
procedure TForml.ButtonlClick(Sender: TObject); begin Labell.Name := 'LabelNotl'; end; end.
263
Часть IV. Компоненты и их использование Запустим приложение и нажмем на кнопку B u t t o n l . При этом название компонента Labell изменится на LabelNotl, что будет видно на экране по изменению подписи на этом компоненте44. При повторном нажатии на кнопку приложение уже не сможет найти компонент L a b e l l , программа будет приостановлена, и на экране появится сообщение об ошибке, выданное средой разработки: 'Project Projectl.exe raised exception class EAccessViolation with message 'Access violation at address 0044EAC7 in module ' P r o j e c t l . e x e ' . Read of address 0 0 0 0 0 0 0 0 ' . Process stopped. Use Step or Run to c o n t i n u e ' . Затем, при попытке возобновления программы, будет выдано более короткое: 'Access violation at address 0 0 4 4 E A C 7 in module ' " P r o j e c t l . e x e ' . Read of address 0 0 0 0 0 0 0 0 ' . Таким образом, изменение имен компонентов во время выполнения программы приводит к невозможности работы с ними. Однако определение имени компонента, то есть чтение свойства Name, может быть очень полезным для решения широкого круга задач. В качестве примера приведем процедуру, которая сравнивает название заданного ей в качестве параметра компонента с некоторой строкой, и при их идентичности выполняет какие-либо действия: procedure CheckComponent(Component: TComponent; EtalonName: String); begin If Component.Name = EtalonName Then ...; end;
С помощью аналогичных подпрограмм можно реализовать, например, запрещение доступа пользователя к некоторым элементам управления, расположенным на форме45. Список таких элементов управления может отличаться для разных пользователей, в результате чего нетрудно реализовать разделение доступа к программному продукту.
13.3.2. Принадлежность компонентов При создании компонента во время выполнения программы автоматически вызывается конструктор компонента, переопределенный в классе TComponent следующим образом: constructor C r e a t e ( A O w n e r : TComponent); virtual; 44
45
Строка текста, выводимая на компоненте, задается свойством Caption и по умолчанию эквивалента имени компонента. Если изменяется свойство Name, то соответствующим образом изменяется и свойство Caption. Связь между свойствами пропадает после установки какого-либо значения свойству Caption во время выполнения программы или с помощью Инспектора объектов в процессе визуальной разработки программы, что с точки зрения компонента одно и то же. Элементы управления имеют для этого свойство Enabled, описанное ниже, при обсуждении визуальных компонентов.
264
Глава 13. Архитектура системы компонентов Delphi
Параметр AOwner, передаваемый конструктору, является ссылкой на компонент, который будет отвечать за разрушение создаваемого компонента. Например, при создании кнопки на форме в конструктор кнопки будет передана ссылка на форму. Создаваемый компонент обращается к компоненту-владельцу, вызывая его метод InsertComponent. Данный метод проверяет возможность владения вновь созданным компонентом, добавляет его в спи46 сок зависимых компонентов , и заносит ссылку на себя в свойство Owner заданного компонента. При вызове метода Free из экземпляра любого наследника класса TComponent сначала будут вызваны методы Free для каждого объекта из списка зависимых, что обеспечивает корректное разрушение объектов, созданных во время выполнения программы. Для поддержки списка зависимых компонентов в классе TComponent предусмотрены два property-свойства: 1. Свойство ComponentCpunt типа Integer, в котором хранится количество зависимых компонентов. 2. Свойство Components, имеющее индексный механизм доступа, то есть представляющее собой массив, каждый элемент которого является ссылкой на экземпляр класса TComponent. Элементы массива нумеруются с нуля, соответственно, последний элемент массива имеет индекс ComponentCount - 1. Оба свойства имеют область видимости public, то есть они доступны любым фрагментам программы, имеющим доступ к ссылке на компонент. Во время разработки программы данные свойства не доступны, так как в этом нет необходимости. С помощью свойств ComponentCount и Components можно организовать доступ к списку зависимых компонентов для получения ссылок на них и изменения значений их свойств. Представим себе форму, на которой расположено множество компонентов, причем некоторые из них являются кнопками TButton. Такие кнопки имеют свойство Left, содержащее горизонтальную координату левого угла компонента. При изменении данного свойства компонент немедленно перемещается в новое положение. Реализуем метод формы, который будет искать среди зависимых компонентов кнопки TButton и изменять их свойство Left. После вызова такого метода (см. листинг 13.6) все кнопки, расположенные на форме, будут визуально выровнены по левому краю. 46
Заметим, что несмотря на существование специализированного класса TComponentList, казалось бы, созданного для решения задач такого рода, для хранения списка зависимых компонентов разработчики Delphi используют обычный список TList.
265
Часть IV. Компоненты и их использование Листинг 13.6. Метод
,
procedure TForml . AlignButtons ; Var i: Integer; , begin For ' i := 0 To Self .ComponentCount-1 do {Циклический перебор всех зависимых компонентов} if Self .Components [i] Is TButton {Если очередной компонент принадлежит классу TButton. . . } Then (Self .Components [i] As TButton) .Left := 10; {... то изменяем координату этого компонента) end;
13.3.3. Взаимосвязи между компонентами Механизм взаимосвязей Компоненты могут взаимодействовать с другими компонентами, создавая и разрушая их, а также, вызывая их методы и обращаясь к свойствам. Примером такого взаимодействия является свойство принадлежности компонентов, когда один компонент может «владеть» несколькими другими, храня ссылки на них в собственном списке зависимых компонентов. Однако доступ к компонентам есть не только у компонента-владельца, но, например, любой метод класса формы может разрушить компонент, находящийся на форме. Поскольку форма несет ответственность за разрушение компонентов, расположенных на ней, то при изменениях, произошедших с зависимым компонентом, необходимо обновить список зависимых компонентов. Аналогичное обновление требуется и для всех свойств, ссылающихся на какие-либо объекты. Для реализации такого поведения системы компонентов предназначен механизм уведомлений (англ. N o t i f i c a t i o n — уведомление). Механизмы уведомлений В Delphi предусмотрено два похожих по назначению, но разных по реализации, механизма уведомлений — обязательный и свободный. Обязательный механизм уведомлений поддерживается автоматически для компонентов, которые являются зависимыми по отношению к другим компонентам. Если при создании компонента в его конструктор передается ненулевая ссылка на компонент-владелец, то создаваемый ком-
266
Глава 13. Архитектура системы компонентов Delphi
понент обращается через нее к методу insertcomponent владельца. В этом методе компонент-владелец заносит ссылку на создаваемый компонент в список зависимых, а в свойство Owner нового компонента заносит ссылку на себя. Перед разрушением зависимого компонента владелец, на которого ссылается свойство Owner, уведомляется об этом с помощью вызова метода RemoveComponent, и сможет обновить свой список зависимых компонентов. Во время разрушения компонента-владельца будут автоматически разрушены все компоненты, ссылки на которые хранятся в списке. Свободный механизм уведомлений необходим для обновления связей объектов, не состоящих в связи владелец-зависимый, и используется компонентами при необходимости обновления ссылочных полей. Например, компонент DataSource имеет свойство DataSet, в котором находится ссылка на компонент DataSet. Присвоение этому свойству ссылки на объект приводит к необходимости обновления ее в случае разрушения, компонента DataSet. Для реализации механизма уведомлений в таких случаях ссылающийся объект вызывает метод FreeNotification того объекта, ссылку на который он хранит. В качестве параметра этому методу передается ссылка на объект, заинтересованный в получении уведомлений. Когда необходимость в получении уведомлений пропадает, вызывается метод RemoveFreeNotif ication. Методы FreeNotification и Remove FreeNotification описаны в классе TComponent следующим образом: procedure FreeNotification(AComponent: TComponent); procedure RemoveFreeNotification(AComponent: TComponent);
Уведомление заинтересованных объектов происходит через вызов их метода Notification: procedure Notification(AComponent: TComponent; T O p e r a t i o n ) ; virtual;
Operation:
В параметре AComponent передается ссылка на объект, который вызвал уведомление. Тип операции, на которую следует отреагировать, передается в параметре O p e r a t i o n , имеющем перечислимый тип TOperation: Type TOperation = (oplnsert, opRemove);
Таким образом, компоненты «сообщают» друг другу о своем создании или разрушении. Отметим, что если компонент заинтересован в получении уведомлений от других компонентов, в его классе должен быть соответствующим образом переопределен метод Notification.
Часть IV. Компоненты и их использование
13.4. Визуальные и невизуальные компоненты Класс TComponent обеспечивает взаимодействие компонентов между собой и с визуальным построителем, но не предоставляет каких-либо возможностей для взаимодействия компонентов с пользователем и визуального отображения компонентов на экране. Таким образом, TComponent предназначен не для прямого использования в программе, а исключительно для целей построения других компонентов на его основе с помощью стандартных механизмов наследования классов. Компоненты, реализованные в стандартной поставке Delphi, можно условно разделить на три группы: » Невизуальные компоненты, не имеющие представления на экране, и не взаимодействующие с пользователем. » Визуальные компоненты, называемые также элементами управления, имеющие визуальное представление и возможность взаимодействия с пользователем. » Диалоговые компоненты, которые не имеют визуального представления, но могут его приобретать для временного взаимодействия с пользователем. Рассмотрим основы строения каждой группы компонентов подробнее.
13.4.1. Визуальные компоненты Базовый класс визуальных компонентов Группа визуальных компонентов (элементов управления) строится на основе прямого наследника TComponent — класса TControl, описывающим свойства, методы и события, присущие всем элементам управления, такие как: » расположение на экране; * правила выравнивания относительно других элементов управления; * курсор мыши, отображаемый при наведении мыши на элемент управления; * шрифт, используемый для вывода надписей на компоненте; » цвет фона; » подсказка, выводимая операционной системой при попадании элемента управления в фокус. Некоторые из свойств, описанных в классе TControl, имеют область видимости protected. Данные свойства доступны только классам-наследникам, которые переносят их в более широкую область видимости
Глава 13. Архитектура системы компонентов Delphi
public или published. Такой подход выбран для придания большей универсальности классу T C o n t r o l , хотя некоторые его возможности заведомо бесполезны для части элементов управления. Программный интерфейс с точки зрения элементов управления — это иерархическая структура объектов-компонентов, каждый из которых отображается внутри другого, причем самый верхний уровень такой иерархии обязательно представляет собой Windows-окно. Остальные визуальные компоненты могут являться либо стандартными элементами управления Windows, построенными на основе окон (так называемые объекты-оболочки, wrapper-controls), либо отображаются средствами Delphi — такие компоненты называют легковесными (lightweight-controls), так как они используют меньшее количество ресурсов системы. Таким образом, и объекты-оболочки, и легковесные объекты могут находиться только внутри объектов-оболочек. Компоненты-оболочки. Класс TWinControl Основная задача класса T W i n C o n t r o l — обеспечение более удобной работы со стандартными элементами управления Windows за счет реализации удобного программного интерфейса, не перегруженного подробностями реализации операционной системы. Свойства класса T W i n C o n t r o l определяются, соответственно, его назначением — быть оболочкой вокруг абстрактного элемента управления Windows. Важными свойствами окон в Windows являются наличие уникального идентификатора окна и возможность «находиться в фокусе», то есть получать сообщения с клавиатуры. Так как класс TWinControl содержит в себе элемент управления, то он имеет доступ к его идентификатору, и может выдать его при обращении к property-свойству Handle: property Handle: HWND; Данное свойство доступно только для чтения, определяется при создании Delphi-компонентом окна соответствующего Windows-элемента управления и может быть использовано для взаимодействия с Windowsэлементом через системные сообщения. Реализация такого взаимодействия в программе имеет смысл при отсутствии соответствующего интерфейса в компоненте-оболочке. Например, Delphi-компонент P r o g r e s s B a r является оболочкой Windows-элемента управления и, в отличие от самого «оборачиваемого» элемента, не имеет свойства, определяющего его цвет. Поэтому цвет компонента ProgressBar всегда соответствует указанному в настройках Windows. Однако цвет компонента все-таки можно изменить, послав его окну по идентификатору Handle сообщение PBM_SETBARCOLOR 269
Часть IV. Компоненты и их использование 47
с помощью функции SendMessage . Таким образом, в Delphi обеспечивается гибкость компонентов-оболочек относительно изменений элементов управления Windows. Даже если разработчики Delphi не предусмотрели какие-либо возможности элементов управления в компонентах, или эти возможности появились позднее выхода очередной версии Delphi, они могут быть доступны через идентификатор окна Handle. Передача фокуса ввода организована в Windows следующим образом. Для каждого контейнера — окна, в котором могут располагаться другие окна — предусмотрен порядок передачи фокуса между дочерними окнами. При начальном отображении контейнера на экране фокус передается первому по списку окну. Далее, фокус передается всем окнам в очередности, заданной порядком передачи, при нажатии пользователем клавиши Tab. После последнего окна фокус получает первое. Пользователь имеет возможность перемещаться по элементам управления в обратном порядке, нажимая комбинацию клавиш Shift-Tab. Для управления правилами получения фокуса компонентом-оболочкой предусмотрены свойства Tab stop, позволяющее указать, должен ли компонент получать фокус вообще, и TabOrder, определяющее порядковый номер компонента при передаче: property TabStop: Boolean; property TabOrder: TTabOrder;
Описание интервального типа TTabOrder выглядит следующим образом:
Type TTabOrder = - 1 . . 3 2 7 6 7 ;
Значение —1 используется только для компонентов, не имеющих визуальной принадлежности, то есть содержащие значение nil в свойстве Parent, остальные компоненты .могут иметь значения от 0 (первый по порядку компонент) до 32767. Легковесные компоненты.
Класс TGraphicControl
Компоненты, наследуемые напрямую от класса TControl, без использования TWinControl, не имеют ссылки на окно, так как не имеют окна, не могут получать фокуса и содержать в себе другие компоненты, то есть являться контейнером. Для создания легковесных компонентов используется базовый класс TGraphicControl. Данный класс напрямую наследуется от TControl, " Можно привести множество таких примеров, однако это выходит за рамки нашей книги. Для получения информации о возможностях стандартных элементов управления Windows мы рекомендуем использовать справочную систему MSDN, поставляемую фирмой Microsoft.
270
Глава 14. Визуальные компоненты
но дает своим наследникам некоторые общие возможности элементов управления, аналогичные возможностям наследников TWinControl, а именно: » отображение элементов управления на экране внутри некоторого окна; • возможность получать сообщения от мыши, то есть взаимодействовать с пользователем. Так как компоненты, унаследованные не от T W i n C o n t r o l , не имеют ссылки на окно, то, они не могут выводить информацию на экран. Для того, чтобы легковесный компонент мог создать свое изображение, предусмотрено свойство Canvas типа TCanvas. Компоненты, в которых визуально расположены наследники TGraphicControl, предоставляют свое свойство Canvas для вывода на него изображений легковесных компонентов. Свойство визуальной принадлежности Одно из самых важных свойств элементов управления — это свойство визуальной принадлежности Parent, описанное в классе TControl, и определяющее внутри какого компонента визуально располагается данный элемент управления. Данное свойство имеет тип TWinControl, так как любой элемент управления может располагаться только в Windows-окне. Существенным отличием свойства визуальной принадлежности Parent от свойства принадлежности Owner является то, что оно показывает, где элемент управления выводится на экран, а не кто ответственен за его хранение и разрушение в программе.
13.4.2. Невизуальные компоненты Невизуальные компоненты наследуются напрямую от класса TComponent и поддерживают все его возможности взаимодействия со средой разработки, например, сохранение свойств и связей с событиями в файлы формы и их восстановление при последующем открытии проекта. Невизуальные компоненты не имеют представления на экране во время выполнения программы, в процессе разработки отображаются на формах в виде иконок, сопоставленных им в Палитре компонентов, и предназначены для добавления в программу поддержки некоторой технологии. В качестве примера можно привести наборы компонентов для работы с базами данных с использованием механизмов доступа BDE или ADO, или набор компонентов, обеспечивающих взаимодействие программных продуктов через сети. Невизуальные компоненты позволяют облегчить работу с теми или иными технологиями путем инкапсуляции в классы сложных процедур, 271
Часть IV. Компоненты и их использование
перегруженных особенностями конкретной технологии. При этом они предоставляют более простой интерфейс в виде свойств и методов компонента. Задачей невизуальных компонентов, таким образом, является перевод конкретной задачи с системного уровня на логический. Следует отметить, что компоненты, предназначенные для работы со схожими технологиями, такими, как доступ к информации в базах данных, обычно наследуются в Delphi от одних базовых классов, в результате чего имеют одинаковые свойства и методы. Это приводит к повышению гибкости программных продуктов, их масштабируемости и некоторой независимости программного кода от используемых технологий.
13.4.3. Диалоговые
компоненты
Диалоговые компоненты являются наследниками класса TCommonDialog — прямого наследника класса TComponent — и представляют собой невизуальные компоненты, которые при вызове метода Execute, описанного в классе TCommonDialog, отображают на экране диалоговое окно для взаимодействия с пользователем. Метод Execute не имеет параметров и описан следующим образом: Function Execute: Boolean; virtual; abstract; Возвращаемое логическое значение используется вызывающей подпрограммой для анализа ввода информации пользователем в зависимости от типа используемого диалога.
Вопросы с ответами для повторения по главе 13 Опишите жизненный цикл экземпляров классов Ответ: Жизненный цикл экземпляра класса начинается с его создания с помощью вызова конструктора и дополнительных методов, причем эти методы вызываются автоматически в такой последовательности: 1. Newinstance — выделение памяти, использует InstanceSize для определения размера памяти. 2. initinstance — очистка выделенной памяти и настройка таблицы виртуальных методов. 3. Версия конструктора Create, определенная в классе, из которого он вызывается. 4. Af terConstruction — только в случае безошибочного выполнения предыдущих методов. Далее объект становится полноправной частью программы и может использоваться для работы с его свойствами и методами, но только, если в кон272
Глава 13. Архитектура системы компонентов Delphi структоре не произошла исключительная ситуация, не обработанная самим конструктором. Если исключение вышло за пределы конструктора (не обработано в нем), то будет автоматически вызван деструктор данного класса для освобождения памяти, выделенной под объект. В случае отсутствия необходимости дальнейшего существования экземпляра класса, его следует разрушить с помощью вызова метода Free. Если объект является экземпляром класса-наследника TComponent, и был создан таким образом, что имеет владельца, то его разрушение не обязательно и будет вызвано автоматически при разрушении владельца. Автоматическое разрушение производится аналогично — вызовом метода Free. После вызова метода Free автоматически вызываются следующие методы: 1. BeforeDestruction — только в случае безошибочного создания данного экземпляра конструктором. 5. Версия деструктора Destroy, определенная в классе, из которого он вызывается. 2. Cleanuplnstance — для очистки памяти. 3. Freelnstance — для освобождения памяти, использует InstanceSize для определения размера памяти.
Расскажите о механизме интерфейсов в Delphi Ответ: Интерфейсы в Delphi аналогичны классам, не имеющим свойств, и все методы которых абстрактны. Для идентификации интерфейса, его описание может включать в себя указание глобального уникального идентификатора — GUID. Описание интерфейса выглядит следующим образом: Туре = Interface(); [ Л {хххххххх-хххх-хххх-хххх-хххххххххххх}' ] Оаголовок метода 1>; ;
end;
.
Механизм интерфейсов позволяет частично осуществлять множественное наследование, то есть создание класса на основе одного родительского класса и нескольких интерфейс'ов. Для этого стандартное описание класса расширено, и в его заголовке после родительского класса указывается список реализуемых интерфейсов: Туре = class ( end; Если в заголовке класса указаны интерфейсы, которые он реализует, то все методы этих интерфейсов должны быть либо описаны полностью, либо указаны в интерфейсной части описания класса, как абстрактные.
Расскажите об интерфейсах (Interface и (Unknown Ответ: Интерфейсы, так же как и классы, поддерживают наследование, то есть класс, реализующий некоторый интерфейс, должен описать все методы, заявленные и в нем самом, и заявленные в его родительском интерфейсе. Если интерфейс описывается без указания родительского интерфейса, то его родителем считается интерфейс (Interface. В данном интерфейсе описаны методы _AddRef и _Release, поддерживающие работу со ссылками на экземпляры классов, реализующих интерфейсы, и, фактически, обеспечивающие счетчик использования экземпляров класса. При первом обращении к интерфейсу (создании объекта, реализующего данный интерфейс) счетчик устанавливается на 1, далее, при последующих обращениях, увеличивается методом _AddRef, а при разрушении уменьшается методом _Release. Еще одна важная функция базового интерфейса llnterface — определение информации о других интерфейсах, реализуемых классом. Для объектов, которые необходимо использовать для реализации в программе технологии СОМ, используется другой базовый интерфейс, полностью совпадающий с llnterface по методам — lUnknown.
Опишите возможности класса TPersistent Ответ: Класс TPersistent инкапсулирует методы для поддержки переносимости информации из одного объекта в другой (методы Assign и AssignTo), а также для сохраняемости значений свойств объектов в файлы форм. Под сохраняемостью свойств экземпляров именно этого класса понимается возможность сохранения свойств объектов, не заявленных в секции published, в файлы форм и другие потоки данных. Такая возможность может потребоваться при создании компонентов, свойства которых ссылаются на другие компоненты, созданные во время выполнения программы или ее визуальной разработки.
Поясните понятие компонента в Delphi Ответ: Компоненты являются наследниками класса TComponent, имеющего следующие возможности:
274
Глава 13. Архитектура системы компонентов Delphi Интеграция с IDE Delphi, то есть возможность находиться в Палитре компонентов IDE и участвовать в процессе визуального проектирЬвания; « Поддержка свойства принадлежности, то есть управления другими компонентами; » Поддержка сохраняемости и восстанавливаемости, реализованная в классе T P e r s i s t e n t ; » Поддержка технологии СОМ, то есть возможности экспорта компонентов в компоненты ActiveX или импорта компонентов ActiveX в компоненты Delphi. Компоненты могут быть визуальными, то есть имеющими представление на экране во время работы приложения, невизуальными — не имеющими такого представления, и диалоговыми — не имеющими визуального представления, но выводящими отдельные окна для взаимодействия с пользователем. Визуальные компоненты являются наследниками класса TControl, невизуальные наследуются напрямую от класса TComponent, а диалоговые компоненты имеют в качестве родительского класса TCommonDialog.
Расскажите о правилах именования компонентов и автоматическое именование их Средой разработчика Ответ: Компоненты присутствуют в программе в виде экземпляров классов на формах, а также в виде ссылок на лих из классов форм. Поэтому правила именования компонентов должны соответствовать правилам именования идентификаторов в программах Delphi. При создании компонентов путем добавления их на форму из Палитры компонентов, Среда разработчика автоматически назначает им имена, состоящие из названия класса, экземпляром которого компонент является (без префикса Т), и его порядкового номера на данной форме. Название экземпляра компонента содержится в его published-свойстве Name, доступном для чтения и записи, однако изменение этого свойства во время выполнения программы приведет к невозможности дальнейшего использования компонента.
Поясните разницу между свойством принадлежности компонентов и свойством визуальной принадлежности Ответ: Свойство принадлежности Owner описано в классе TComponent и указывает на объект, который несет ответственность за разрушение данного компонента. Такой механизм снимает необходимость прямого разрушения компонентов, созданных программой во время работы. Свойство визуальной принадлежности Parent описано в базовом классе визуальных компонентов TControl, и указывает, в каком компоненте находится визуальное представление данного компонента. 275
Часть IV. Компоненты и их использование Свойство принадлежности заполняется в конструкторе класса TComponent с помощью переданного параметра,AOwner, тогда как свойство визуальной принадлежности заносится прямым указанием из того фрагмента программы, который создает визуальный компонент.
Расскажите о взаимосвязях компонентов через механизм уведомлений Ответ: Для поддержания ссылочной целостности в программе компоненты поддерживают механизм уведомлений, реализованный в классе TComponent, с помощью которого один объект может быть извещен (с помощью вызова его метода Notification) о разрушении или добавлении другого объекта. Для компонентов, устанавливающих в своем конструкторе свойство принадлежности, поддержка извещений устанавливается автоматически, такой механизм уведомлений называется обязательным. Существует также свободный механизм уведомлений, реализуемый с помощью того же метода Notification и еще двух методов для добавления в список заинтересованных компонентов или удаления их оттуда — FreeNotif ication и RemoveFreeNotif ication.
Расскажите о визуальных компонентах-оболочках и легковесных компонентах Ответ: Визуальные компоненты могут иметь визуальное представление на экране в виде обычного окна Windows, в которое выведено некоторое изображение. Такие компоненты называются компонентами-оболочками и наследуются от класса TWinControl. Отличительная черта таких компонентов — наличие свойства Handle, в котором содержится ссылка на окно, в которое выводится визуальное представление элемента управления. Компоненты такого рода используются, когда элемент управления должен находиться в фокусе и получать события с клавиатуры. В остальных случаях рекомендуется использование легковесных компонентов — наследников класса TGraphicControl, использующих для вывода своего изображения на экран оконные компоненты-оболочки. Вывод на экран обеспечивается через свойство Canvas типа TCanvas, инкапсулирующее в себя графические возможности операционной системы. Компонент, которому визуально принадлежит легковесный элемент управления, перенаправляет графический вывод легковесного компонента в свое окно.
276
^Т"
d
1/9
/¥
Визуальные компоненты
Как было указано выше, визуальные компоненты являются наследниками класса TControl и разделяются на две группы: 1. Компоненты-оболочки, представляющие собой классы, имеющие ссылки на реальные элементы управления Windows. Компонентыоболочки наследуются от базового класса TWinControl. 2. Легковесные компоненты, не являющиеся окнами Windows, а выводящие свое изображение на область отображения компонентовоболочек, внутри которых они находятся. Легковесные компоненты наследуются от базового класса TGraphicControl. 1
14.1. Общие свойства визуальных компонентов Компоненты-оболочки и легковесные компоненты несущественно отличаются друг от друга в использовании, хотя и отличны друг от друга во внутренней реализации. Большинство свойств и методов визуальных компонентов либо унаследованы от класса TControl, либо описаны в самих компонентах в зависимости от их назначения. Рассмотрим для начала общие свойства визуальных компонентов, необходимые для качественного построения интерфейса48.
Особое внимание будем уделять, в основном, тем свойствам, значения которых можно изменять с помощью Инспектора объектов. Если описываемое свойство доступно только в программном коде, это будет оговорено особо. 277
Часть IV. Компоненты и их использование
14.1.1. Положение и размеры элемента управления на экране Положение визуального компонента (элемента управления) на экране задается в точках относительно окна компонента-контейнера, в котором находится данный компонент, и содержится в целочисленных свойствах L e f t и Тор. Размеры компонента также задаются в точках и содержатся в свойствах W i d t h и Height: property property property property
L e f t : Integer; Top: Integer; W i d t h : Integer; Height: Integer;
Значения этих свойств автоматически изменяются при расположении компонента и настройке его размеров мышью в визуальном построителе, либо могут быть введены в окне Инспектора объектов. Далее значения сохраняются в файле формы и считываются при последующем открытии проекта или запуске приложения. На рис. 14.1 показано окно, в котором находится два компонента — компонент-оболочка (контейнер) Panel 1 и компонент Buttonl, вставленный в контейнер. Значения свойств L e f t и Тор для обоих компонентов одинаковы (см. фрагменты Инспектора объектов для каждого из компонентов), однако их левые верхние углы не совпадают, так как координаты кнопки отсчитываются относительно панели, а координаты панели — относительно формы. Таким образом, положение кнопки в оконных координатах составляет (20, 20). Для получения положения и размеров элемента управления во время выполнения программы также используются свойства L e f t , Top, W i d t h и Height, однако предусмотрено еще одно свойство, не доступное через Инспектор объектов: property BoundsRect: TRect;
I ...101 xl Buttonl
PaneH
11
L?
IIP
Width
1321
TOP |if.~ Рис. 14.1. Расположение компонентов 278
Глава 14. Визуальные компоненты
Использование .свойства BoundsRect удобно в случаях, когда необходимо получить и положение и размер визуального компонента одновременно в одной структуре типа TRect, например, для использования ее в каких-либо специальных функциях, таких, как API-функции вывода текста.
14.1.2. Автоматическое управление положением При изменении размеров окна, в котором находятся визуальные компоненты, появляется дополнительная область, с учетом которой можно расположить интерфейсные элементы для достижения большего удобства пользователя. Характеристики положения компонентов могут быть изменены в обработчике события OnResize формы с помощью свойств L e f t , Top, W i d t h и Height, однако такой подход является трудоемким и практически сводит на нет смысл визуального построения приложения. При этом сами визуальные компоненты снабжены средствами автоматического изменения своего положения в зависимости от размеров формы. Поведение компонентов определяется свойствами Align, Anchors и Constraints, рассмотренными нами далее. Привязка визуального компонента к одной из сторон контейнера Свойство Align (англ. Align — выравнивание) управляет автоматическим изменением размера и положения визуального компонента, привязывая его к одной из сторон компонента-контейнера, и имеет перечислимый тип TAlign. . property Align: TAlign; Type TAlign = (alNone, alTop, alBottom, alLeft, alRight, alClient, alCustom); При использовании значений данного свойства, отличных от alNone, компонент «прилипает» к одной из сторон формы, располагаясь вдоль нее, и в дальнейшем перемещается вместе с ней. На рис. 14.2 приведен пример формы, на которой расположен компонент-панель Panell. Изначально, при добавлении панели на форму, свойство Align панели имело значение alNone, а расположение определялось только свойствами Left, Top, width, Height (верхняя часть рисунка). Далее свойству Align было установлено значение alLeft, в результате чего панель расположилась в левой части формы, полностью заняв ее по высоте (центральная часть рисунка). При изменении размеров формы (нижняя часть рисунка) высота панели изменяется соответственно высоте формы, а ширина остается неизменной. Возможные значения свойства Align приведены в табл. 14.1. 279
Часть IV. Компоненты и их использование
i
Panell
Рис. 14.2. Использование свойства Align Возможные значения свойства Align объектов класса TControl Значение
Выравнивание
Таблица 14.1 Примечание
aINone
Компонент не следит за изменениями контейнера, в котором он находится
alTop
При установке данного значения компонент располагается в верхней части контейнера, занимая всю его ширину. При изменениях размеров контейнера автоматически изменяется ширина компонента, высота остается неизменной
Использование свойства Width не приводит к изменению ширины компонента
alBottom
При установке данного значения компонент располагается в нижней части контейнера, занимая всю его ширину. При изменениях размеров контейнера автоматически изменяется ширина компонента, высота остается неизменной
Использование свойства Width не приводит к изменению ширины компонента
alLeft
При установке данного значения компонент располагается в левой части контейнера, занимая всю его высоту. При изменениях размеров контейнера автоматически изменяется высота компонента, ширина остается неизменной
Использование свойства Height не приводит к изменению ширины компонента
alRight
При установке данного значения компонент располагается в правой части контейнера, занимая всю его высоту. При изменениях размеров контейнера автоматически изменяется высота компонента, ширина остается неизменной
Использование свойства Height не приводит к изменению ширины компонента
alClient
При установке данного значения компонент занимает всю внутреннюю область контейнера. При изменениях размеров контейнера автоматически изменяются ширина и высота компонента
Использование свойств Width и Height не приводит к изменению размеров компонента
Использование свойства A l i g n удобно для организации элементов управления, которые всегда должны находиться у одного края своего контейнера, например, строк состояния или панелей с набором элементов управления. Еще одной часто используемой возможностью является привязка отдельных сторон компонента к одной из сторон его контейнера. В этом случае не происходит «прилипания» элемента управления к одной из сторон контейнера и сохраняется возможность независимого выбора размеров для компонента, просто положение и размеры компонента изменяются в соответствии с изменениями размеров формы.
280
Глава 14. Визуальные компоненты
Настройка автоматического изменения положения стороны компонента в соответствии с такой же стороной его контейнера Множественное свойство A n c h o r s (англ. Anchors — якоря) определяет, следует ли изменять положение какой-либо стороны компонента в соответствии с такой же стороной его контейнера: . property Anchors:1 TAnchors; Описание типа TAnchors выглядит следующим образом: Туре T A n c h o r K i n d = (akTop, a k L e f t , a k R i g h t , a k B o t t o m ) ; T A n c h o r s = Set Of TAnchorKind;
.* Возможные значения свойства Anchors приведены в табл. 14.2. Возможные элементы множества Anchors объектов класса TControl Элемент
Таблица 14.2
Изменение поведения
akLeft
При включении данного элемента в множество Anchors положение левой стороны элемента управления (то есть значение свойства Left) не изменяется при изменении размеров контейнера. Если элемент не включен в множество, левая граница элемента управления перемещается вслед за правой границей контейнера
akTop
При включении данного элемента в множество Anchors/положение верхней стороны элемента (то есть значение свойства Тор) управления не изменяется при изменении размеров контейнера. Если элемент не включен в множество, верхняя граница элемента управления перемещается вслед за нижней границей контейнера
akRight
При включении данного элемента в множество Anchors правая сторона элемента управления перемещается вслед за правой границей контейнера. При этом, если в множестве присутствует элемент akLeft, изменяется ширина компонента (свойство Width)
akBottom
При включении данного элемента в множество Anchors нижняя сторона элемента управления перемещается вслед за нижней границей контейнера. При этом, если в множестве присутствует элемент akTop, изменяется высота компонента (свойство Height)
Элементы множества A n c h o r s используются парами — a k L e f t совместно с akRight, a akTop совместно с akBottom. При различных значениях элементов в парах, элемент управления либо изменяет свои размеры, либо изменяется его положение, либо состояние элемента управления соответствует начальному (табл. 14.3). Автоматическое изменение положения элемента управления
Таблица 14.3
akRight (akBottom)
I
о
£. с % 3 3 £ я
False
True Изменение ширины (высоты) соответственно изменению ширины контейнера
Без изменений
Перемещение без изменения размера соответственно изменению ширины контейнера
Перемещение без изменения размера на величину, меньшую, чем изменение размеров контейнера
281
Часть IV. Компоненты и их использование
Начальные значения, устанавливаемые при добавлении визуального компонента на форму, подобраны таким образом, чтобы положение элемента управления не изменялось при изменении размера контейнера. То есть множество A n c h o r s состоит из элементов a k L e f t и a k T o p , что указывает на необходимость привязки левого верхнего угла компонента к левому верхнему углу контейнера, и отсутствие привязки правого нижнего угла компонента к правому нижнему углу контейнера. На рис. 14.3 показана форма, на которой расположены три панели. Причем у компонента Рапе 11 (слева наверху) значение свойства A n c h o r s не изменено, у Рапе12 (слева внизу) изменено на обратное, а у P a n e l s (справа наверху) в множество добавлены элементы akRight И akBottom.
Рис. 14.3. Использование свойства Anchors
При изменении размеров формы (увеличения ширины и высоты), которая является контейнером для панелей, первый компонент не изменяет своего положения, второй смещается за правым нижним углом формы, а третий изменяет размеры без смещения. Если для панели Рапе12 в множество Anchors не были бы включены элементы a k L e f t и akTop, то панель сместилась бы на меньшее расстояние от своего начального положения. Заметим, что свойство Anchors может применяться совместно со свойством Align, когда необходимо автоматическое изменение размеров компонента с одновременным выравниванием его по стороне контейнера. 282
Глава 14. Визуальные компоненты
Задание минимально и максимально возможных размеров компонентов И, наконец, перейдем к рассмотрению свойства Constraints, управляющего минимально и максимально возможными размерами компонента: property Constraints: TSizeConstraints; Тип данных, с которым описано это свойство, представляет собой класс с четырьмя основными полями MaxHeight, MaxWidth, MinHeight и MinWidth, определяющими соответственно максимальные высоту и ширину элемента управления, а также его минимальную высоту и ширину. Свойство Constraints может быть полезно при использовании автоматического изменения размеров компонента, описанного выше, для ограничения пределов такого изменения. Например, с помощью свойства Constraints можно задать минимальные размеры панели с элементами управления, чтобы пользователь мог иметь к ним доступ вне зависимости от размера окна.
14.1.3. Управление видимостью и доступом пользователя к управлению Элементы управления, которые подразумевают воздействие пользователя, обычно так или иначе связаны с некоторыми параметрами программы. Причем некоторые из этих параметров могут не допускать изменения значения в некоторые моменты времени. Например, если программа теоретически поддерживает несколько языков интерфейса, то должен существовать элемент управления, с помощью которого пользователь может выбрать подходящий ему язык. Однако если при инсталляции программы поддержка многоязыкового интерфейса не установлена, то пользователь не должен иметь доступ к этому элементу управления. Скрытие визуального компонента от пользователя осуществляется с помощью свойства видимости Visible: property Visible: Boolean; Начальное значение данного свойства (True) позволяет выводить элемент управления на экран, а при его изменении компонент исчезает, либо снова появляется. Использование свойства visible не всегда дружественно по отношению к пользователю, так как скрывает от него информацию о возможностях программы. Для того, чтобы элемент управления отображался на экране, но не допускал изменения своего состояния, предусмотрено свойство Enabled (свойство «доступности»):
283
Часть IV. Компоненты и их использование property E n a b l e d :
Boolean;
При создании компонента значение данного свойства (True) разрешает доступ пользователя к элементу управления. В дальнейшем, при установке значения False компонент обычно изменяет свое визуальное представление и ввод в него информации недопустим. Заметим, что вне зависимости от состояния свойств Visible и Enabled, программа имеет полный доступ к компонентам и может изменять их свойства.
14.1.4. Дружественное поведение элементов управления Изменение вида курсора мыши при наведении на компонент Операционная система Windows предлагает широкие возможности для организации гибкого и дружественного пользовательского интерфейса, и это касается не только набора элементов управления, которые можно применять в программных продуктах, но и некоторых средств сопровождения работы пользователя, не занимающих места на экране. Одной из
Выход из программы без сохранения данных
Рис. 14.4. Курсоры и подсказки
таких возможностей является изменение курсора мыши при наведении ее на элемент управления (см. рис. 14.4). В Delphi также реализовано изменение курсора мыши при наведении, ее на компонент. Курсор, который следует отобразить, находится в свойстве Cursor всех наследников класса TControl: property C u r s o r :
TCursor;
Курсор мыши является системным ресурсом, что затрудняет работу с ним. Например, значением свойства Cursor должен быть его системный идентификатор. Однако в Delphi эта проблема решена с .помощью набора стандартных курсоров, поддерживаемого средой разработки. Для того, чтобы изменить курсор, выводимый над элементом управления, достаточно просто выбрать его из списка, причем, начиная с шестой версии Delphi, список констант, соответствующих курсорам в Инспекторе объектов, снабжен еще и их изображениями. 284
Глава 14. Визуальные компоненты
Использование всплывающих подсказок Еще одним широко применяемым средством сопровождения пользователя во время работы с программой являются быстрые подсказки, выводимые операционной системой автоматически, на основе строки. Использование таких подсказок существенно упрощает организацию интерфейса, так как окно с подсказкой возникает только при наведении мыши на элемент управления, и исчезает сразу же после того, как мышь его покинет, что позволяет снизить количество подписей под элементами управления, используемых для пояснения их назначения. Для поддержки подсказок используется свойство Hint (англ. Hint — транспарант), содержащее строку, выводимую в качестве подсказки, и свойство ShowHint, указывающее, следует ли подсказку использовать49. property H i n t :
string;
property S h o w H i n t :
Boolean;
Пример измененного курсора мыши (в виде руки с пальцем) и выведенной на экран подсказки (с текстом «Выход из программы без сохранения данных») показан на рис. 14.4.
14.2. Компоненты-контейнеры 14.2.1. Общее описание Компоненты-контейнеры предназначены для добавления в них других элементов управления, в том числе и других контейнеров тоже. Количество контейнеров на экране и степень их вложенности зависит от сложности интерфейса и не ограничивается ни средой разработки, ни операционной системой. Контейнеры обычно имеют рамку и подпись. Визуально элемент-контейнер охватывает группу компонентов. Но иногда контейнеры применяются только для организации других элементов управления и не присутствуют визуально на экране. Зато они помогают организовать интерфейс на основе свойств автоматического расположения компонентов Align и Anchors. На рис. 14.5 представлено окно программы, в котором друг под другом находятся два компонента-контейнера. В контейнерах размещаются другие элементы управления. Такая организация удобна как с точки зрения пользователя, так как контейнеры своей рамкой отделяют раз-
Здесь приведены средства управления подсказками к элементам управления, относящиеся к визуальным компонентам и описанные в классе TControl. Однако возможности настройки системы подсказок более широки и описаны при обсуждении глобальной переменной Application.
285
Часть IV. Компоненты и их использование
амидов Buttons (• RadioButtonl Г Radk>Butlon2
!7 CheckBoxt F CheckBox2
PaneK
J7 CheckB охЗ Г
CheckBox4
Рис. 14.5. Компоненты-контейнеры личные группы элементов управления, так и с точки зрения разработчика, так как позволяет при визуальном проектировании передвигать по форме множество компонентов одновременно. Напомним, что положение компонентов определяется относительно контейнера, в кото50 ром они расположены . Приведем список наиболее часто используемых контейнеров, а затем рассмотрим каждый из них подробнее (см. табл. 14.4).
Таблица 14.4
Наиболее часто используемые контейнеры Компонент :
w
Название
Описание
Panel
Простой контейнер для элементов управления
а
ScrollBox
Контейнер для элементов управления с возможностью изменения (прокрутки) области видимости, если элементы управления занимают область большую, чем клиентская область данного компонента
ISiJ
PageScroller
Контейнер для одного элемента управления с возможностью изменения (прокрутки) области видимости в одном направлении (либо по горизонтали, либо по вертикали)
'Г{|
GroupBox
Контейнер для элементов управления с заголовком
sffl
RadioGroup
Контейнер с заголовком для создания групп зависимых переключателей
-l-t
TabControl
Контейнер для организации многостраничных диалогов средствами программы
PageControl
Контейнер для организации многостраничных диалогов с автоматической сменой страниц
111
ControlBar
Контейнер для организации панелей инструментов
Щ
CoolBar
Контейнер для организации панелей инструментов
..., . J
Заметим, что использование .контейнеров исключительно для упрощения процесса разработки нецелесообразно, так как такие компоненты являются наследниками класса TWinControl, то есть используют ресурсы операционной системы.
286
Глава 14. Визуальные компоненты
14.2.2.
Контейнеры-панели
Виды панелей и общее описание Панели используются для расположения групп элементов управления, визуальная структура которых не меняется во время выполнения программы, за исключением, возможно, видимости и доступности некоторых компонентов. Такие панели предназначены только для визуального отделения какой-либо группы компонентов от остального содержимого окна. Контейнеры-панели представлены компонентами: * » * «
Panel (закладка standard Палитры компонентов); ScrollBox (закладка Additional); GroupBox (закладка Standard); RadioGroup (закладка Standard).
Примеры панелей показаны на рис. 14.6 в перечисленном порядке сверху вниз.
:3агразить[ Сохранить |
Рапеп
Сохранять в двоичном виде : Г~ П одтвеожаение пои выходе 0X1"
Использовать черно-белчю гаммч Автоматически подбирать размер roupl
' "
Для всех пользователей
С Только для текущего
Рис, 14.6. Контейнеры-панели
Все панели являются наследниками класса TWinControl и обладают свойствами, общими для визуальных элементов управления, такими, как: свойства расположения, выравнивания, видимости и доступности. Причем при изменении расположения, видимости или доступности панели соответствующим образом изменяются одноименные свойства компонентов, расположенных в ней. Так, если свойство Enabled какого-либо контейнера имеет значение False, то и все компоненты внутри него будут недоступны пользователю. Одновременно с этим компоненты ScrollBox и RadioGroup имеют дополнительную функциональность. 287
Часть IV. Компоненты и их использование
Панель ScrollBox — панель с полосами прокрутки
Панель ScrollBox предназначена для группировки большого числа элементов управления, и имеет возможность отображать на экране только часть группы. Для управления видимой областью пользователь может использовать полосы скроллинга, расположенные в правой и нижней частях панели. Полосы скроллинга представлены в классе TScrollBox отдельными published-свойствами Н о г z S c r o l I B a r и V e r t S c r o l I B a r типа TControlScrollBar и имеют в свою очередь свойства, перечисленные в табл. 14.5. Например, для того, чтобы программно изменить положение видимой области, следует изменить значения свойств Position полей HorzScrollBar и VertScrollBar: ScrollBoxl.VertScrollBar.Position := 30; Таблица 14.5
Свойства класса TControlScrollBar Тип
Свойство
Описание
Size
Integer
Высота в точках (для горизонтальной полосы скроллинга) или ширина (для вертикальной)
ButtonSize
Integer
Размер (в точках) кнопок со стрелками, расположенных по краям полосы скроллинга
ThumbSize
Integer
Размер (в точках) перемещаемой части полосы скроллинга
Margin
Word
Расстояние (в точках) между кнопками со стрелками, расположенными по краям полосы скроллинга
Tracking
Boolean
Указывает на необходимость (значение True) немедленного перемещения видимой области при изменении положения перемещаемой части полосы скроллинга, иначе (значение False) положение видимой области изменяется после отпускания пользователем кнопки мыши
Smooth
Boolean
Указывает, следует ли перемещать плавно (значение True) видимую область при нажатии мышью на фоновой области полосы скроллинга. Не рекомендуется для использования в программах, выполняемых на маломощных компьютерах
ParentColor
Boolean
Указывает на необходимость использования (значение true) фонового цвета родительского компонента (в котором находится данный компонент), иначе (значение False) фон полосы скроллинга устанавливается в соответствии со свойством Color
Color
TColor
•Цвет фона полосы скроллинга
Range
Integer
Высота (для вертикальной полосы скроллинга) и ширина (для горизонтальной) видимой области в точках
Position
Integer
Положение видимой области в точках. Изменяется в диапазоне от 0 до значения свойства Range
Increment
Integer
Шаг изменения регулируемого значения (положения видимой области) в точках
Style
TScrollBarStyle
Внешний вид полосы скроллинга. Свойство может иметь три значения: ssRegular (обычный стиль Windows с трехмерными элементами управления),-5зР1а1 (элементы управления полосой плоские и становятся трехмерными при наведении мыши) и ssHotTrack (элементы управления плоские и подсвечиваются при наведении мыши)
Visible
Boolean
Видимость полосы скроллинга
288
Глава 14. Визуальные компоненты
Панель ScrollBox удобно использовать в случаях, когда необходимо сохранить возможность использования некоторого набора элементов управления вне зависимости от размеров окна. Второй мотив использования компонента ScrollBox — это организация в одном окне нескольких независимых друг от друга областей, использующих возможность прокрутки. Отметим, однако, что чрезмерное употребление компонентов такого рода существенно усложняет работу пользователя с интерфейсом. Более простым аналогом компонента S c r o l l B o x является PageScroller, расположенный на странице W i n 3 2 . Данный компонент имеет такое же назначение, но предусмотрен для прокрутки одного элемента управления, который, впрочем, может являться контейнером и содержать в себе другие компоненты. Направление прокрутки задается свойством Orientation, которое может иметь два значения: ' » soHorizontal — прокрутка в горизонтальном направлении; • soVertical (прокрутка в вертикальном направлении). Компонент PageScroller часто используется совместно с панелями инструментов, которые обычно имеют фиксированную ширину или высоту, то есть не требуют перемещения видимой области во всех направлениях. На рис. 14.7 представлено окно программы, в котором находится компонент PageScroller. Элементом управления, область видимости которого перемещается, является компонент-контейнер Panel, в который вставлено 10 кнопок. Из них на экране видно только 8, но с помощью кнопок-стрелок компонента PageScroller можно сделать видимыми другие кнопки.
Panel
PageScroller
Button
.
Рис
. . Компонент PageScroller
14 7
Панель RadioGroup — панель зависимых переключателей
Панель RadioGroup предназначена для создания группы зависимых переключателей. Если пользователем выбирается один из переключателей из такой группы, то значения всех остальных сбрасываются. Данный компонент может выступать в качестве замены группы зависимых переключателей, представленных компонентами RadioButton, описанным ниже. В случае использования RadioGroup зависимые переключатели создаются автоматически на основе списка строк, заданного published-свойством Items: property I t e m s : T S t r i n g s ; 10 Зак. 867
289
Часть IV. Компоненты и их использование
Разработчик имеет доступ к свойству items как во время работы программы, так и во время визуального построения (см. рис. 14.8), в результате чего существенно упрощается создание элементов программы, управляющих состоянием'какого-либо параметра. Для определения, какой из элементов является выделенным, используется свойство itemlndex (элементы нумеруются с нуля): property I t e m l n d e x : Integer; Данное свойство доступно и на этапе разработки программы, что позволяет заранее установить выбранный по умолчанию элемент группы. Например, на рис. 14.8 свойство itemlndex имеет значение 0. Компонент Radio-Group выравнивает элементы управления таким образом, чтобы они равномерно занимали его поверхность. Также предусмотрена возможность автоматического разбиения группы элементов управления на несколько столбцов, количество которых задается свойством Columns: property Columns: Integer; На рис. 14.8 свойство Columns имеет значение 2, и соответственно элементы управления разбиты на два столбца. Элементы управления, созданные панелью RadioGroup охвачены рамкой, в верхней части которой находится строка, поясняющая обычно смысл параметра, для настройки которого предназначена группа. Строка, выводимая в качестве заголовка, содержится в свойстве Caption: property Caption: TCaption; Такие же свойства есть и у компонентов Panel и RadioGroup. Компонент ScrollBox заголовка не имеет.
Использовать цвет: "^Красный исевый
RadioGtoupl
Y" Желтый С Зеленый
Properties J Events j Vltems
I(f Strings)
ItentfnttexiG
Рис. 14.8. Контейнер RadioGroup
290
Глава 14. Визуальные компоненты
14.2.3. Контейнеры-панели инструментов Данная группа контейнеров предназначена для организации панелей инструментов — групп компонентов, предназначенных для быстрого выполнения некоторых действий. Панели инструментов широко применяются в различных программных продуктах, например, Microsoft Word или Adobe Photoshop. При работе с такими панелями пользователь может с помощью мыши изменять визуальную структуру компонентов, расположенных в них, добиваясь наиболее удобного их расположения. Задание расположения элементов управления в панели инструментов производится автоматически и не требует каких-либо усилий со стороны разработчика. Для каждого компонента, вставляемого в контейнеры данного типа, создается специальный компонент, которыми и управляет панель. Одновременно с этим такие компоненты имеют видимую часть, за которую пользователь перемещает их вместе с элементами управления внутри панели. С учетом того, что компонентом, вставленным в панель инструментов, может являться контейнер, разработчик может создать довольно сложную структуру элементов управления. На рис. 14.9 представлена форма, в верхней части которой расположена панель инструментов ControlBar (со страницы Additional Палитры компонентов). В панель вставлены компоненты Panel, RadioGroup, и две кнопки Button. В панель Panel добавлено еще три кнопки. В верхней части рисунка показано окно сразу же после начала работы программы, а в нижней части — после действий 'пользователя, изменившего взаимное расположение элементов управления внутри панели. Заметим, что слева от каждого компонента, который принадлежит непосредственно панели ControlBar, присутствует область, за которую пользователь может перемещать эти компоненты. При перемещении какого-либо элемента управления вниз, за пределы первоначальной
•
-RadioGroupl
~
Г 1 Г 3 Г 5 Г 7 Г 9 |: Г 2 (Г 4 Г 6 Г 8 Г 10!
-••,
Рис. 74.9. Использование панелей инструментов
291
Часть IV. Компоненты и их использование
области панели инструментов, ее размер увеличивается, а внутренние компоненты располагаются в несколько рядов. Таким поведением панели управляет свойство AutoSize, указывающее на необходимость автоматической установки высоты панели при изменении положения элементов управления: property A u t o S i z e :
Boolean;
Интересным является свойство Picture панели ControlBar, позволяющее задать фоновый рисунок панели и выделить ее на фоне остального окна: property P i c t u r e :
TPicture;
Использование свойства Picture в данном компоненте, и свойств типа TPicture вообще, существенно упрощено с помощью диалога, вызываемого на экран при попытке редактирования таких свойств. В диалоге можно выбрать файл с графическим изображением, подходящим по формату типу TPicture, то есть BMP, JPG, EMF или ico, далее изображение будет считано и сохранено в файл формы, и, соответственно, в исполняемый ехе-файл. Аналогом компонента ControlBar является CoolBar, расположенный на закладке W i n 3 2 Палитры компонентов. Методы работы с данным компонентом не отличаются от вышеописанных, однако он обладает полезным свойством Bands, представляющим список внутренних панелей, на которых располагаются элементы управления. Изменяя свойства этих панелей, можно достигать различных эффектов, полезных для построения удобного интерфейса. Например, для каждой панели можно установить свой цвет или фоновое изображение, а также задавать минимальные размеры и возможность изменения размеров вообще.
14.2.4. Страничное представление групп компонентов Компоненты-контейнеры постраничного представления информации предназначены для разбиения элементов на группы и вывода в каждый момент времени работы программы только одной группы компонентов из нескольких. В Delphi данные компоненты реализованы в виде классов TabControl и PageControl, каждый из которых является наследником TCustomTabControl. Общей чертой компонентов TabControl и PageControl является наличие полосы с закладками, с помощью которых пользователь может выбрать необходимую страницу элементов управления (см. рис. 14.10). Если закладки не помещаются в окно компонента, то автоматически появляется элемент управления для их перемещения, состоящий из двух стрелок. 292
Глава 14. Визуальные компоненты - '
JSl xj
Красный ^Оранжевый) Желтый] Зеленый | Голубой ] Синий) Ч ||;1^3..^
Первая строка: И7
~4\
Пример В качестве примера ис.ти.шьзова.Т'нги^которьк визуальных компонентов, опггсднны^в настоящей плаве, реализуем нзслез&ный текстовый редактор ( ), построенный н основе компонента /? 7 chEdf t, со ет'дутоп^яли возможностями; • />ыбор размера и атрибутов шрифта (полужирный, наклонный, подчёркнутый, перечеркнутый), а также его цвета. Возможность выбора названия шрифта реализована не будет; • Л' становка выравнивания текста абзаца по левому и правому краю, а также по центру; • С/рганизацяя списков; • «У становка отступов для абзацев; Сохранение и печать..,""""" : ; Формат: 0 then begin Result := True; Memol.SelStart := NewPos — 1; Memol.SelLength := Length(ReplaceDialogl.FindText) ; Memol.SetFocus; end; end; {Если результат функции PosEx положителен, то устанавливается выделение подстроки в компоненте Memol с помощью свойств selStart и SelLength} procedure TForml.ButtonlClick(Sender: TObject); begin ReplaceDialogl.Execute; end; {Обработчик нажатия на кнопку Buttonl — вызов диалога замены на экран} procedure TForml.ReplaceDialoglFind(Sender: TObject);
429
Часть IV. Компоненты и их использование
begin FindAndSelect(frMatchCase in ReplaceDialogl.Options); end; (Обработчик нажатия на кнопку «Найти» в диалоге замены -- вызов функции FindAndSelect с передачей параметра, указывающего на необходимость учета регистра символов} procedure TForml.ReplaceDialoglReplace(Sender: TObject); begin {Обработчик нажатия на кнопки «Заменить» или «Заменить все» в диалоге замены — вызов функции FindAndSelect с передачей параметра, указывающего на необходимость учета регистра символов} if frReplaceAll in ReplaceDialogl.Options {В зависимости от состояния флага frReplaceAll в свойстве Options функция выполняется один или несколько раз} then while FindAndSelect(frMatchCase in ReplaceDialogl.Options) do Memol.SelText := ReplaceDialogl.ReplaceText {Если флаг frReplaceAll в свойстве Options установлен, то функция FindAndSelect выполняется до тех пор, пока возвращает значение True. На каждый вызов функции производится замена выделенного фрагмента с помощью изменения свойства SelText компонента Memol} else if FindAndSelect(frMatchCase in ReplaceDialogl.Options) then Memol.SelText := ReplaceDialogl.ReplaceText; {Если флаг frReplaceAll в свойстве Options отсутствует, то выполняется функция FindAndSelect. В флучае, если функция вернула значение True, производится замена выделенного фрагмента с помощью изменения свойства SelText компонента Memol} end; end.
17.7. Диалоги настройки параметров печати В Delphi предусмотрены следующие диалоговые компоненты для выбора параметров печати документов: Щ| — компонент PrintDialog. Диалог выбора принтера; Щ — компонент PrinterSetupDialog. Диалог настройки принтера; Щ — компонент PageSetupDialog. Диалог настройки страницы при печати. 430
Глава 17. Использование диалоговых компонентов
Диалоги настройки параметров печати необходимы каждой программе, которая выводит информацию на принтер. Пользователь, работающий с программой, может иметь доступ к нескольким принтерам, в том числе и сетевым, поэтому программный продукт должен предложить возможность выбора места назначения документа. Современные устройства вывода имеют множество настроек, которые могут изменяться пользователем с помощью программ, поставляемых с такими устройствами. Таким образом, прикладная программа не имеет возможности поддержать все функции, реализуемые устройством, и должна использовать стандартные диалоги. Необходимо заметить, что получение и изменение даже самой общей информации о печатающем устройстве (например, форматы бумаги, поддерживаемые устройством), является достаточно трудоемкой процедурой. Поэтому стандартные системные диалоги, используемые в Delphiкомпонентах настройки параметров печати, существенно облегчают процесс построения качественного программного продукта. .Заметим, что сами диалоговые компоненты не имеют каких-либо возможностей для вывода информации на принтер. Однако они изменяют характеристики объекта типа TPrinter, ссылка на который возвращается функцией Printer, описанной в модуле Printers. Программа использует эту ссылку для непосредственного вывода на печать, а параметры устанавливаются диалогами PrintDialog, PrinterSetupDialog и PageSetupDialog.
17.7.1. Компонент PrintDialog Компонент PrintDialog предназначен для установки следующих основных параметров печати: » печатающее устройство, выбираемое из списка доступных системе, с возможностью его настройки с помощью специализированного диалога (при использовании кнопки «Свойства»); » часть документа, выводимая на печать — весь документ, несколько страниц, или выделенная область; » число копий и их упорядочивание при печати.
События PrintDialog Диалог является модальным, поэтому определение настроек, выполненных пользователем, производится после вызова метода Execute. В данном компоненте нет событий, кроме унаследованных от класса TCommonDialog — OnShow и OnClose. 431
Часть IV. Компоненты и их использование
••Принтер Имя:
•
Состояние: Тип: Место: Комментарий: ••
[EPSON Stylus COLOR 680 (Копия 2)
Свойства..
3
Готов EPSON Stylus COLOR 680 •"•• USB001 •.
I? Печать в файл Копии
Печатать
Г Все
-••—"-•view Source)
446
Глава 18. Формы Листинг 18.3. Файл проекта с одной формой program Projectl; {Заголовок проекта} uses {Раздел подключения модулей} Forms, {Подключение модуля Forms} Unitl in 'Unitl.pas' {Forml}; {Подключение модуля Unitl, в котором описана главная форма приложения} {$R *.res} {Подключение файла с ресурсами, необходимыми приложению в целом} begin Application.Initialize ; {Инициализация приложения} Application.CreateForm(TForml, Forml); {Создание главной формы приложения. Форма создается на основе класса TForml, ссылка на форму помещается в переменную Forml, описанную в модуле Unitl вместе с классом главной формы} Application.Run; {Запуск приложения} end.. {Окончание программы}
Таким образом, если форма присутствует в списке автоматически создаваемых форм Auto-create forms в диалоге Project Options, ее создание производится в основной части программы, причем необходимей программный код создается автоматически. При удалении данной формы из списка автоматически создаваемых создание формы в основной части программы не производится (листинг 18.4). .,,.,,, .„,.... ...,.,. Листинг 18.4. Файл проекта, не соедержащего форму
program Projectl; {Заголовок проекта} uses {Раздел подключения модулей} Forms, {Подключение модуля Forms} Unitl in 'Unitl.pas' {Forml}; {Подключение модуля Unitl, в котором описана главная форма приложения) {$R *.res} {Подключение файла с ресурсами, необходимыми приложению в целом} begin Application.Initialize; {Инициализация приложения} Application.Run; {Запуск приложения} end. {Окончание программы}
447
Часть IV. Компоненты и их использование
Заметим, что ссылка на файл класса формы остается в проекте. Аналогичным образом создаются и все остальные формы, присутствующие в списке Auto-create forms. Допустим, в приложении, помимо главной формы, создана одна дополнительная. В таком случае основная часть программы будет выглядеть как показано в листинге 18.5. Листинг 18.5. Файл проекта с двумя формами program Projectl; {Заголовок проекта} uses {Раздел подключения модулей} Forms, {Подключение модуля F o r m s } Unitl in 'Unitl.pas' {Forml}; (Подключение модуля U n i t l , в котором описана главная форма приложения} {$R * . r e s ) {Подключение файла с ресурсами, необходимыми приложению в целом} begin Application.Initialize; {Инициализация приложения Application.CreateForm(TForml, Forml); Application.CreateForm(TForm2, Form2); {Создание форм, занесенных в список Auto-create forms} Application.Run; {Запуск приложения} end. {Окончание программы}
Прямое создание формы во время выполнения программы Прямое создание формы во время выполнения программы может быть произведено аналогично автоматическому созданию форм в основной части программы — с помощью метода CreateForm объекта Application: procedure C r e a t e F o r m ( F o r m C l a s s :
TFormClass; var R e f e r e n c e ) ;
Переменная Application (ссылка на экземпляр класса TApplication) относится к глобальным переменным проекта и более подробно описана ниже. Метод CreateForm создает экземпляр формы класса FormClass и заносит ссылку на созданный экземпляр в переменную, указанную в качестве параметра Reference. Допустим, в проекте существуют две формы — главная, описанная в модуле U n i t l , и вспомогательная, описанная в модуле U n i t 2 и имеющая название класса TForm2. Тогда создание формы типа TForm2 из какого-либо метода главной формы может выглядеть следующим образом: 448
Глава 18. Формы unit Onitl;
Заголовок модуля главной формы
Implementation Uses Unit2;
Начало описательной секции модуля Подключение модуля вспомогательной формы
procedure TForml .ButtonlClick(Sender: TObject); Обработчик нажатия на кнопку Buttonl главной формы Var AForm: TForm2; Описание ссылки на экземпляр вспомогательной формы begin Application.CreateForm(TForm2, AForm); Создание экземпляра вспомогательной формы с помощью метода CreateForm глобальной переменной A p p l i c a t i o n . Ссылка на экземпляр заносится в ссылочную переменную AForm
При создании формы вышеописанным методом свойство принадлежности устанавливается таким образом, что владельцем формы становится объект Application, который является наследником класса TComponent. Данный объект автоматически разрушается при завершении программы, разрушая при этом все формы, созданные его методом CreateForm. Еще один способ создания формы — это вызов конструктора Create соответствующего класса, однако такой подход обычно не имеет смысла, так как в методе CreateForm класса TApplication выполняются точно такие же действия: unit Unitl; Implementation Uses Unit2;
Заголовок модуля главной формы Начало описательной секции модуля /Подключение модуля вспомогательной формы
procedure TForml.ButtonlClick(Sender: T O b j e c t ) ; Обработчик нажатия на кнопку B u t t o n l главной формы Var
AForm: TForm2;
Описание ссылки на экземпляр вспомогательной формы
Begin AForm := TForm2.Create(Application) ; Форма создается прямым вызовом конструктора, в качестве владельца формы указывается глобальная переменная Application 15 Зак. 867
449
Часть IV. Компоненты и их использование В качестве компонента-владельца может быть указан любой другой компонент, например, экземпляр формы, из которого создается новая форма. В таком случае создаваемая форма будет разрушена при разрушении формы, указанной в качестве параметра конструктора, в соответствии с правилами поведения компонентов Delphi: unit Unitl;
Заголовок модуля главной формы
Implementation Uses Unit2;
Начало описательной секции модуля Подключение модуля вспомогательной формы
procedure TForml.ButtonlClick(Sender : T O b j e c t ) ; Обработчик нажатия на кнопку Buttonl главной формы Var AForm: TForm2; Описание ссылки на экземпляр вспомогательной формы Begin AForm := TForm2.Create(Self); Форма создается прямым вызовом конструктора, в качестве владельца формы указывается форма, которой принадлежит метод ButtonlClick. Указание происходит с помощью ключевого слова Self В заключение обсуждения создания экземпляров форм во время выполнения программы' заметим, что при прямом создании экземпляра формы не важно, является ли форма автоматически создаваемой или нет, так как эта характеристика относится не к классу формы в целом, а только к одной ссылке на него, автоматически добавляемой в модуль этой формы при добавлении формы в проект.
18.2.3. Отображение формы Окна приложений могут отображаться на экране в модальном режиме — когда пользователь не имеет возможности переключаться между окнами одной программы, или немодальном — когда переключение между окнами допустимо. Для вывода формы на экран используются соответственно методы Show (англ. Show — показать) и ShowModal (англ. Show Modal — показать в модальном режиме) без параметров: procedure Show; function ShowModal:
450
Integer;
Глава 18. Формы
Немодальные формы Процедура Show, отображающая форму в немодальном режиме, может применяться следующим образом: unit Unitl;
Заголовок модуля главной формы
Implementation Uses Unit2;
Начало описательной секции модуля Подключение модуля вспомогательной формы
procedure
TForml.ButtonlClick(Sender: T O b j e c t ) ; Обработчик нажатия на кнопку Buttonl главной формы
Var AForm: TForm2; Описание ссылки на экземпляр вспомогательной формы
Begin Application.CreateForm(TForm2, AForm); Создание экземпляра вспомогательной формы с помощью метода CreateForm глобальной переменной A p p l i c a t i o n . Ссылка на экземпляр заносится в ссылочную переменную AForm AForm.Show; Отображение окна (экземпляра формы TForm2, на который указывает ссылка A F o r m ) на экране в немодальном, режиме
Формы в немодальном режиме могут применяться для организации окон, доступ к которым должен осуществляться в любой момент времени работы программы наравне с другими. Например, панели инструментов, реализованные в виде отдельных окон, должны быть немодальными. Модальные формы Процедура ShowModal, отображающая форму в модальном режиме, может применяться следующим образом: unit U n i t l ; Implementation Uses Unit2; procedure
Var AForm:
Заголовок модуля главной формы Начало описательной секции модуля Подключение модуля вспомогательной формы
TForml.ButtonlClick(Sender: T O b j e c t ) ; Обработчик нажатия на кнопку Buttonl главной формы
TForm2;
Описание ссылки на экземпляр вспомогательной формы 451
Часть IV. Компоненты и их использование Begin Application.CreateForm(TForm2, AForm); Создание экземпляра вспомогательной формы с - помощью метода CreateForm глобальной переменной A p p l i c a t i o n . Ссылка на экземпляр заносится в ссылочную переменную AForm AForm.ShowModal; Отображение окна (экземпляра формы TForm2, на который указывает ссылка A F o r m ) на экране в модальном режиме. Выполнение процедуры B u t t o n l c i j c k приостанавливается до завершения метода ShowModal формы AForm Вызов модального окна на экран приостанавливает выполнение приложения до закрытия формы. Соответственно, такие окна используются для запроса от пользователя данных, необходимых для продолжения выполнения программы. Классическим примером применения модальных окон в программе являются стандартные диалоги, например, диалог открытия файла. Поскольку обычно пользователю предоставляется возможность отказаться от работы с диалогом, в классе TForm предусмотрено целочисленное свойство Modal'Result, в которое может заноситься некоторое значение, определяющее результат работы: property M o d a l R e s u l t : TModa'lResult ; Type TModalResult = L o w ( I n t e g e r ) . . H i g h ( I n t e g e r ) ; Возможные значения переменных типа TModalResult приведены64 в табл. 18.1. Возможные значения свойства ModalResult форм Константа mrNone mrOk mrCancel
Значение
0 idOK idCancel
Таблица 18.1 Описание
Устанавливается при отображении формы и означает незавершенность работы Пользователь закончил работу с формой нажатием кнопки «Ok» Пользователь закончил работу с формой нажатием кнопки «Cancel» Пользователь закончил работу с формой нажатием кнопки «Abort»
mrAbort
idAbort
mrRetry
id Retry
Пользователь закончил работу с формой нажатием кнопки «Retry»
mrlgnore
idlgnore
Пользователь закончил работу с формой нажатием кнопки «Ignore»
mrYes
idYes
Пользователь закончил работу с формой нажатием кнопки «Yes»
mrNo
idNo
Пользователь закончил работу с формой нажатием кнопки «No»
mrAII
mrNo + 1
Пользователь закончил работу с формой нажатием кнопки «АИ»
mrNoToAII
mrAII + 1
Пользователь закончил работу с формой нажатием кнопки «No to All»
mrYesToAII
mrNoToAII + 1
Пользователь закончил работу с формой нажатием кнопки «Yes to All»
64
Константы, приведенные в столбце «Константы», описаны в модуле Controls на основе констант (столбец «Значение»), описанных в модуле Windows.
452
Глава 18. Формы
Приведенные константы и их значения могут использоваться на усмотрение разработчика и представляют собой всего лишь некоторое соглашение, в соответствии с которым рекомендуется ^присвоение каких-либо значений свойству M o d a l R e s u l t . Единственное правило состоит в том, что модальная форма отображается на экране, пока свойство ModalResult имеет нулевое значение. При установке значения, отличного от нуля (то ест-ь любой константы, кроме mrNone), форма будет автоматически закрыта, а выполнение фрагмента программы, из которого вызван метод ShowModal, продолжится. Для большего удобства работы со свойством ModalResult формы в классе TButton, а, следовательно, и в TBitBtn, как унаследованном от него, присутствует одноименное свойство такого же типа, имеющее по умолчанию значение mrNone: . property M o d a l R e s u l t :
TModalResult ;
Если данное свойство имеет значение, отличное от mrNone, то при нажатии пользователем на кнопку оно заносится в свойство ModalResult формы, в результате чего форма закрывается. Таким образом, при использовании кнопок типа Button и BitBtn разработчику прикладной программы не приходится реализовывать обработчики событий их нажатия, так как результат — закрытие формы — достигается автоматически. Заметим, что свойство ModalResult компонентов-кнопок никак не связано со свойством Kind компонента BitBtn. В соответствии со свойством Kind кнопка может иметь подпись «Ok», при этом значение, которое заносится в свойство ModalResult формы, может быть не mrOk, а, например, mrNo.
18.2.4. Управление доступом к форме Для управления доступом к форме можно использовать свойство Enabled, общее для визуальных компонентов Delphi, и запрещающее пользователю взаимодействие с элементами управления, расположенными на форме65. Таким образом, формы по отношению к свойству Enabled ведут себя аналогично обычным компонентам-контейнерам. Однако формы являются не просто визуальными компонентами, а еще и отдельными программными окнами, в результате чего для .них предусмотрены дополнительные возможности управления доступом. Скрытие формы с экрана Для скрытия формы с экрана применяется метод Hide, не имеющий параметров: procedure Hide; При этом допустимо перемещение формы по экрану и изменение ее размеров. 453
Часть IV. Компоненты и их использование
Вызов метода Hide эквивалентен установке свойству visible значения False. Заметим, что данный метод не закрывает форму, а просто запрещает ее отображение. Поэтому в случае использования модальных форм программа не сможет нормально функционировать, так как модальность скрытой формы не даст пользователю переключиться в другое окно программы, а сама форма не сможет быть закрыта пользователем, так как не видна ему. По поводу свойства v i s i b l e заметим, что установка ему значения True может применяться в качестве замены метода Show, который и реализован на основе этого свойства. Однако в методе Show, описанном в классе TForm, выполняется еще одно важное действие — форма не просто отображается на экране, но и перемещается относительно других окон программы ближе к пользователю (становится самым верхним окном). Разрушение формы Для форм, которые созданы автоматически в основной части программы, или при прямом создании которых во время выполнения приложения было установлено свойство принадлежности66, не требуется прямого указания о разрушении. Так как формы являются компонентами, то их разрушение производится компонентами-владельцами, обычно объектом Application. Однако форма, как и любой оконный компонент, требует некоторое" количество системных ресурсов, особенно, если на ней расположено множество других компонентов. Поэтому разрушение форм может быть целесообразно для снижения нагрузки на систему, в случаях, когда в существовании экземпляра формы нет необходимости. Для разрушения формы используется метод Release, а не Free, как для остальных Delphi-компонентов: procedure R e l e a s e ;
Наличие специального метода разрушения для форм связано с существованием оконной функции, которой обладает каждое Windows-окно. При вызове метода Free компонент немедленно разрушается, и сообщения, которые находятся'в очереди сообщений, не могут быть обработаны. Использование метода Release гарантирует, что перед разрушением формы будут завершены обработчики сообщений самой формы и подчиненных ей компонентов, а очередь сообщений будет полностью разобрана. Таким образом, уничтожение формы методом Release возможно прямо из какого-либо ее метода: Имеется в виду создание формы с помощью вызова ее конструктора, так как при использовании метода CreateForm объекта Application свойство принадлежности устанавливается автоматически на этот объект, что гарантирует разрушение формы при завершении приложения.
454
Глава 18. Формы Procedure TForml.ButtonlClick(Sender: TObject); Обработчик нажатия на кнопку Buttonl, принадлежащую форме begin Release;
Разрушение формы методом Release. Форма будет разрушена только после завершения данного метода, а до его завершения ссылка на форму остается доступной, так же, как и ссылки на все компоненты, расположенные на этой форме
end;
После вызова метода формы R e l e a s e , и до окончания метода ButtonlClick разрешено использование ссылки на форму и ссылок на компоненты, расположенные в ней. Если бы вместо Release был использован метод Free, то разрушение формы произошло бы немедленно, и обращение к компонентам формы могло бы вызвать ошибку нарушения доступа к памяти 'Access violation ...'. -, _
Закрытие формы Разрушение формы выполняется в программе в случаях, когда в дальнейшем использовании формы нет необходимости. Также форма, как окно программы, может быть скрыта только на некоторое время, причем сам момент исчезновения формы с экрана обычно представляет собой результат каких-либо действий, обычно действий пользователя. Например, модальный диалог открытия файла OpenFile разрушается в момент выбора пользователем имени файла или отказа от работы с диалогом. Таким образом, программа обычно должна иметь возможность определить факт скрытия окна, вне зависимости от того, разрушаются ли структуры, связанные с окном, либо просто изменяется его видимость. Поскольку с точки зрения пользователя разрушение формы и изменение ее видимости не отличаются друг от друга, для форм введено понятие закрытия. Процедура закрытия формы инициируется методом Close класса TForm: procedure
Close;
Так как форма часто является логически законченной единицей программы, то она сама может определять действия, выполняемые при ее закрытии, и возможность собственного закрытия вообще. По умолчанию вызов метода Close приводит к вызову метода Hide, и форма просто 455
Часть IV. Компоненты и их использование
скрывается с экрана. При этом остается доступной ссылка на нее и компоненты, которыми она владеет. Для. указания действий, которые должны быть выполнены с формой в качестве ее закрытия, предусмотрено событие OnClose (от англ. On Close — При закрытии), описанное в классе TForm следующим образом: property O n C l o s e : T C l o s e E v e n t ; Type T C l o s e E v e n t = procedure(Sender: T C l o s e A c t i o n ) of object;
TObject;
var
Action
Параметр-переменная Action перечислимого типа TCloseAction, передаваемый обработчику, по умолчанию имеет значение caHide, что означает, что для формы должен быть вызван метод Hide. Обработчик события OnClose может изменить значение параметра, если поведение формы должно быть другим. Возможные значения переменных типа TCloseAction перечислены в табл. 18.2. Константы типа TCloseAction Константа
Вариант закрытия формы
caNone
Форма не допускает закрытия
caHide
Форма скрывается с экрана
caFree
Форма разрушается
caMinimize
Таблица 18.2
Форма минимизируется (сворачивается)
Таким образом, если в обработчике события OnClose в параметр Action заносится значение caNone, то форма не сможет быть закрыта ни программно, с помощью метода Close, ни пользователем (с помощью соответствующего элемента управления в правой части заголовка окна). При этом предусмотрен второй вариант запрещения закрытия формы, построенный на обработчике события OnCloseQuery (от англ. On Close Query — Запрос на закрытие): property OnCloseQuery: T C l o s e Q u e r y E v e n t ; Type T C l o s e Q u e r y E v e n t = procedure(Sender: T O b j e c t ; var CanClose: Boolean) of object;
Обработчику сообщения OnCloseQuery передается параметр-переменная, имеющая по умолчанию значение True, которое означает, что форма может быть закрыта. Если обработчик события устанавливает данному параметру значение False, то закрытие формы запрещается. Обработчик OnCloseQuery вызывается перед обработчиком OnClose. Таким образом, если при обработке OnCloseQuery параметру CanClose при456
Глава 18. Формы
своено значение False, то обработчик OnClose вызван не будет, так как в определении способа закрытия формы нет необходимости. procedure T F o r m l . F o r m C l o s e Q u e r y ( S e n d e r : CanClose: Boolean); begin C a n C l o s e := F a l s e ; end;
TObject;
var
18.3. Организация многооконных приложений Виды приложений с точки зрения организации окон Все приложения, выполняемые в операционной системе Windows, с точки зрения организации оконной структуры можно разделить на две группы: l.MDI-приложения (от англ. Multi Document Interface -- Интерфейс множества документов), окна которых расположены в одном окне, называемом главным. Концепция такого подхода состоит в том, что в программе могут одновременно обрабатываться несколько документов, по одному в каждом окне. Также могут присутствовать окна, предназначенные для вспомогательных операций, например, диалоги и панели инструментов. На рис. 18.3 представлено окно графического редактора Adobe Photoshop, организованного в форме MDI. Для каждого документа — графического изображения — открывается отдельное окно, в котором производится редактирование. Все остальные окна являются различными панелями инструментов, которые отражают состояние активного документа. 2. SDI-приложения (от англ. Single Document Interface — Интерфейс одного документа), окна которых перемещаются по экрану независимо друг от друга. Приложения такого рода подразумевают обработку одного документа в каждый момент времени, а окна, используемые приложением, рассматривают отдельные аспекты этого документа. В качестве примера SDI-организации многооконного приложения можно привести среду разработчика Delphi. Под «документом», с которым работает программный продукт, может подразумеваться не только текстовый или графический документ, но и любой другой набор данных. Например, в среде разработчика Delphi, организованной в виде SDI-приложения, документом считается группа проектов. Все окна, которые входят в состав среды, ориентированы на работу либо с группой проектов в целом, либо с отдельным проектом из группы. При этом окно Инспектора объектов рассматривает данный документ с точки зрения компонентов, расположенных на формах, а, например, редактор исходного текста — с точки зрения программного кода.
I
457
Часть IV. Компоненты и их использование
Нарнеслаи прлмеягопьнс* выддониг или п*р*м«стиТ¥ iuft* лени* кон tjp* Исполните Sbilt, АН,
Рис. 78.3. Adobe Photoshop организован в виде MDI-приложения
SDI-приложения По умолчанию в Delphi создаются SDI-приложения. Все формы проекта при выводе на экран получают одинаковые права с точки зрения пользователя, то есть каждая форма проекта может быть развернута на весь экран или минимизирована, а также перемещена в любое место экрана, вне зависимости от положения других форм, в том числе и главной. Главная форма, тем не менее, имеет особые свойства, а именно: » Если главная форма минимизируется, то минимизируются и все формы приложения. Исключением является какая-либо форма, не являющаяся главной, но выведенная в модальном режиме — при ее минимизации, также минимизируется все приложение полностью. * При закрытии главной формы, закрывается приложение, следовательно, и все его формы. В остальном же все формы приложения являются равнозначными. Заметим, что применение SDI-приложений должно быть ограниченным, так как работа с ними может вызвать затруднения у неподготовленного пользователя.
458
Глава 18. Формы MDI-приложения MDI-приложения состоят из одного главного окна, в пределах которого могут находиться другие (дочерние) окна (см. рис. 18.4). Организация MDI-приложений более сложна, чем построение SDI-систем, и подразумевает некоторую настройку форм, участвующих в программе. Все формы MDI-приложения должны иметь специальный стиль, определяемый перечислимым property-свойством FormStyle67: property FormStyle: TFormStyle; Одна из форм приложения должна быть выбрана в качестве главной, о чем свидетельствует значение f sMDIForm свойства FormStyle. Все остальные (дочерние) формы должны иметь в этом свойстве значение f sMDlChild. При отображении дочерней формы на экране она будет выведена в пределы главной формы и не может быть перемещена за ее пределы.
ГЛАВНАЯ
' : : ; ^ЗШ^ВНЗВНМНВИ
IMIfTP ' : ' • ' • - '
Дочерняя форма
.^BL?i
: Дочерняя форма
Дочерняя форма Ш£ШР>:'"-'- !• :
:
..,.|.а1д |
ШЯЯЯЯЯИЮ?'
. -Щ|-: -Дочерняя форма
^ШШ) Ll
Рис. 18.4. MDI-приложение По умолчанию свойство FormStyle имеет значение fsNormal, свидетельствующее о том, что форма является частью SDI-приложения. MDI-приложения имеют ряд особенностей: 1. При закрытии дочерней формы вместо вызова команды Hide по умолчанию происходит минимизация окна. При этом оно сворачивается в «иконку» на внутреннюю область родительского окна (см. рис. 18.5). 2. Визуальные компоненты-наследники класса TGraphicControl, то есть неоконные элементы управления, не могут быть отображены68 на главной форме MDI-приложения. 67
68
Свойство FormStyle принадлежит к немногочисленной группе свойств, которые могут быть изменены только в Инспекторе объектов во время разработки программы. Изменение значения данного свойства во время работы программы недопустимо. Так написано в справочной системе Delphi версии 7. Однако, это не совсем верно. Неоконные компоненты, например, текстовые метки Label, могут располагаться на главной форме MDI-приложения, они будут нормально отображены, но взаимодействие пользователя с такими элементами управления будет невозможно. Например, невозможно выполнить нажатие на кнопку SpeedButton, расположенную на форме с атрибутом fsMDIForm. 459
Часть IV. Компоненты и их использование
-lOtxt
Дочерняя форма
Дочерняя форма
Еще одна...
jgjai*i| Ж Рис. 18.5. Дочерние формы могут быть «свернуты»
Приоритетные окна Свойство FormStyle может иметь еще одно значение — fsStayOnTop, показывающее, что данное окно должно быть самым верхним (близким к пользователю) на экране. Использование такого поведения формы возможно только для SDI-приложений, так как в MDI-организации программы формы должны иметь стили fsMDIForm и fsMDIChild. Если для какого-либо главного окна SDI-приложения установлен признак f sStayOnTop, то это окно будет самым верхним, даже если приложение, которое владеет этим окном, неактивно. Окна других приложений будут находиться под этим окном, даже если они в этот момент активны и имеют фокус ввода. Если атрибут f sStayOnTop назначен дочернему SDI-окну, то оно всегда будет находиться выше своего родительского, но может быть перекрыто окнами других приложений, которые имеют в данный момент фокус ввода. При установке активности главному окну приложения такая форма вновь займет самое высокое положение в иерархии окон. Если дочернему окну MDI-приложения установлен атрибут f sStayOnTop, то такое окно не принадлежит главному окну, находится выше его и сворачивается не на его внутреннюю область, а на рабочий стол Windows. Заметим, что такое поведение многооконного приложения противоречит MDI-концепции. С помощью «приоритетных» окон могут быть организованы глобальные панели инструментов (приложения, управляющие группой других приложений), как, например, панель быстрого запуска приложений Microsoft Office.
460
Глава 18. Формы
Организация форм в многооконных приложениях Если программный продукт построен по принципу SDI, то в нем, скорее всего, заранее известен состав окон, их примерное расположение и размеры. Далее, пользователь может сконфигурировать интерфейс по> собственному усмотрению, и программно изменять заданное расположение окон не имеет смысла. Единственное, чем может разработчик SDIприложения помочь пользователю, это установка атрибута f sStayOnTop для некоторых дочерних окон, доступ к которым осуществляется наиболее часто. Интерфейс в стиле MDI обычно подразумевает неизвестное заранее количество окон с «документами» на экране, поэтому имеет смысл программно изменять расположение этих окон с помощью какого-либо их автоматического выравнивания в пределах главного окна. Подходы к автоматическому выравниванию окон условно можно разделить на две группы: » прямая установка свойств форм; * автоматическое выравнивание с помощью реализованных в классе TForm методов. Автоматическое выравнивание может быть выполнено методами Tile (см. рис. 18.6) и Cascade (см. рис. 18.7), применяемыми к развернутым окнам, или методом Arrangelcons, применяемым к минимизированным окнам: procedure T i l e ; procedure C a s c a d e ; procedure A r r a n g e l c o n s ;
Заметим, что выравнивание минимизированных окон происходит и при вызове методов Tile и Cascade. Cascade
Дочерняя форма
p» Arrange i coo; I.
' •" •
Дочерняя форма 1БЗ
Дочерняя форма
Дочерняя форма
-ainUC" '
:
;
Рис. 18.6. Выравнивание дочерних форм MDI-приложения по методу Tile
461
Часть IV. Компоненты и их использование
Дочерняя форма
Рис. 18.7. Выравнивание дочерних форм MDI-приложения по методу Cascade
Прямое управление положением и размерами форм может осуществляться с помощью свойств Left, Top, width и Height, обычных для всех визуальных компонентов Delphi, в том числе и форм. Для изменения значений этих свойств следует получить доступ к ссылкам на формы, либо храня их в какой-либо подходящей для этого структуре, либо с помощью свойства MDichildren, представляющего собой интерфейс к одномерному массиву дочерних форм в MDI-приложении: property M D I C h i l d r e n [ I :
Integer]:
TForm;
Количество дочерних форм хранится в целочисленном свойстве MDIChildCount: property M D I C h i l d C o u n t :
Integer;
Таким образом, выравнивание, аналогичное выполняемому методом Cascade, может быть реализовано следующим образом: unit Unitl; Implementation procedure TForml.ButtonlClick(Sender: TObject);
Var i: Integer; begin For i := 0 To MDIChildCount-1 Do {Организация цикла по количеству дочерних форм} begin MDIChildren[i].Left := i * 20; MDIChildren[i].Top := i * 20;
462
Глава 18. Формы end;
{Изменение положения очередной формы, ссылка на которую берется из свойства-массива MDIChildren. Положение по вертикали и горизонтали пропорционально номеру формы i}
end;
18.4. Свойства и события класса TForm 18.4.1. События форм Создание, разрушение и активность Формы имеют множество событий, позволяющих прикладной программе контролировать их работу. Каждому обработчику передается ссылка Sender на объект, который инициировал событие, а некоторым из обработчиков передаются дополнительные параметры. Описание событий сведено в табл. 18.3. События формы Событие
Таблица 18.3 Дополнительные параметры обработчика
Описание
OnCreate
Вызывается при создании формы после выполнения конструктора
OnShow
Вызывается при отображении формы методами Show и ShowModal
OnActivate
Вызывается при получении формой фокуса ввода (форма становится активной). Автоматически вызывается после отображения формы методами Show и ShowModal, так как после отображения форма активна
OnPaint
Вызывается при необходимости прорисовки формы, например, при затирании области формы другим окном
OnHide
Вызывается при скрытии формы с экрана методом Hide или установкой свойству Visible значения False
OnDeactivate
Вызывается при потере формой фокуса ввода, например, при переключении на другую форму, а также сворачивании формы или приложения. Автоматически вызывается после скрытия формы с экрана методом Hide
OnCloseQuery
var CanClose: Boolean
Вызывается для проверки возможности закрытия, вне зависимости от действий, которые следует произвести при закрытии. Критерий возможности закрытия возвращается в параметре CanClose
OnClose
var Action: TCIoseAction
Вызывается для определения действий, которые следует произвести при закрытии формы. Указание на вид закрытия возвращается в параметре Action
OnDestroy
Вызывается при разрушении формы перед вызовом деструктора
463
Часть IV. Компоненты и их использование
События создания и разрушения предназначены для выполнения какихлибо специфических действий при переходе формы из одного состояния в другое. Большинство таких событий образуют пары, например, при получении фокуса ввода вызывается обработчик события OnActivate, а при его потере — OnDeactivate. Такая структура событий позволяет гибко управлять ресурсами, выделяемыми и освобождаемыми при смене состояния формы. Также имеют место пары OnCreate-OnDestroy и OnShow-OnHide. События O n P a i n t , OnCloseQuery и OnClose парными не являются, так как предназначены для настройки поведения формы, а не для реакции на ее изменения. При создании формы, ее отображении на экране (вне зависимости от используемого метода), и последующем закрытии, события инициируются в следующей последовательности: 1. OnCreate. 2. OnShow. 3. OnActivate. 4. OnCloseQuery. 5. OnClose. 6. OnHide. 7. OnDeactivate. 8. OnDestroy. Перемещение и изменение размеров При перемещении формы и изменении ее размеров последовательно инициируются два события: OncanResize И OnConstrainedResize: property OnCanResize: TCanResize Event; property O n C o n s t r a i n e d R e s i z e : T C o n s t r a i n e d R e s i z e E v e n t ; Type T C a n R e s i z e E v e n t = procedure(Sender: T O b j e c t ; var NewWidth, NewHeight: Integer; var R e s i z e : Boolean ) of object; T C o n s t r a i n e d R e s i z e E v e n t = procedure (Sender: TObje-ct; var M i n W i d t h , M i n H e i g h t , MaxWidth, MaxHeight: Integer ) of object;
Обработчик события OnCanResize вызывается при попытке пользователя изменить размеры окна, либо при программной установке свойств width и Height. В качестве параметров данному обработчику передают464
Глава 18. Формы
ся новые значения ширины (NewWidth) и высоты (NewHeight) формы, изменив которые обработчик может повлиять на размеры, которые будут реально установлены. Параметр Resize определяет, следует ли вообще изменять размеры формы. Если данный параметр после завершения обработчика будет иметь значение False, то размеры окна не изменятся, и остальные события, связанные с изменением размеров, вызваны не будут. Вне зависимости от установленных в обработчике события OnCanResize значений параметров NewWidth и NewHeight, размеры формы, которые будут установлены, ограничены свойством C o n s t r a i n t s . Также учитываются результаты работы обработчика второго события — OnConstrainedResize. Параметры MinWidth, MinHeight, MaxWidth и MaxHeight, передаваемые в этот обработчик, при вызове содержат значения, хранимые в свойстве Constraints, однако при изменении значений этих параметров ограничения, установленные свойством Constraints, обходятся. При этом? если новые размеры формы выходят за пределы старых ограничений C o n s t r a i n t s , то ограничения обновляются в соответствии с размерами формы, а не в соответствии со значениями параметров M i n W i d t h , MinHeight, MaxWidth и MaxHeight. Заметим, что вне зависимости от установленных пределов, размеры клиентской части окна (доступной для расположения элементов управления) не могут превышать размеров рабочего стола Windows. Данное ограничение определяется операционной системой. После вызова обработчиков событий OnCanResize и OnConstrainedResize размеры формы могут либо остаться без изменения, либо измениться. Во втором случае будет инициировано событие OnResize, уведомляющее о факте изменения размеров. property O n R e s i z e :
TNotifyEvent ;
В большинстве случаев для эффективного управления размерами формы достаточно использования свойства Constraints и обработчика события OnResize. Обработка событий OncanResize и ConstrainedResize необходима только в случаях, когда концепция изменения размеров формы зависит от хода выполнения программы. И, наконец, отметим, что описанные события возникают не только при изменении размеров формы, но и при ее перемещении по экрану. Естественно, что событие OnResize при этом инициируется, так как изменения размеров формы не происходит. Никаких других событий, которые можно было бы использовать для определения перемещения формы, не предусмотрено. 465
Часть IV. Компоненты и их использование
Манипуляции с мышью в области формы Сообщения о манипуляциях пользователя с мышью, производимых в пределах окна программы, унаследованы классом TForm от его родителя TControl, и являются общими для всех элементов управления Delphi. Данные события позволяют определить факт перемещения мыши в пределах компонента (событие OnMouseMove), нажатия или отпускания какой-либо кнопки мыши (события OnMouseDown и OnMouseUp), а также нажатие кнопки мыши (событие Onclick) и двойное нажатие (событие OnDblClick): property property property property property
OnMouseMove: TMouseMoveEvent; OnMouseDown: TMouseEvent; OnMouseUp: TMouseEvent; OnClick: TNotifyEvent; OnDblClick: TNotifyEvent;
Процедурные типы для обработчиков событий описаны следующим образом: Туре TMouseMoveEvent = procedure(Sender: T O b j e c t ; Shift: TShiftState; X , Y : Integer) o f object; TMouseEvent = procedure(Sender: T O b j e c t ; B u t t o n : TMouseButton; Shift: TShiftState; X , Y : I n t e g e r ) o f object;
Параметр Button определяет, какая именно кнопка мыши была нажата: левая (значение mbLeft), правая (значение mbRight) или центральная (значение mbMiddle). В параметре s h i f t указывается, удерживалась ли в момент перемещения мыши какая-либо управляющая клавиша на клавиатуре. Данный параметр может иметь значения s s S h i f t , s s A l t и ssctrl, что означает соответственно нажатие клавиш s h i f t , Alt и Ctrl во время манипуляций с мышью. Помимо перечисленных событий, общих для всех визуальных компонентов, для форм доступны события, возникающие при вращении колеса мыши (англ. Wheel — Колесо): property OnMouseWheel: T M o u s e W h e e l E v e n t ; property O n M o u s e W h e e l D o w n : TMouseWheelUpDownEvent; property OnMouseWheelUp: TMouseWheelUpDownEvent;
Процедурные типы для обработчиков таких событий описаны следующим образом: 466
Глава 18. Формы Туре TMouseWheelEvent = procedure(Sender: TObject; Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint; . var Handled: Boolean ) of object; TMouseWheelUpDownEvent = procedure(Sender: T O b j e c t ; Shift: TShiftState; MousePos: TPoint; var Handled: Boolean ) of object; При вращении пользователем колеса мыши инициируется сообщение OnMouseWheel, в которое передается параметр-переменная Handled, содержащая значение False. Если значение данного параметра не изменится, или событию вообще не назначен обработчик, то данное событие считается необработанным, и будет инициировано одно из двух событий OnMouseWheelUp или OnMousewheelDown, в зависимости от направления вращения колеса (соответственно, от себя или к себе). Следует заметить, что события O n M o u s e W h e e l , O n M o u s e W h e e l U p и OnMousewheelDown описаны в классе TControl, и, соответственно, могли бы быть доступны вообще всем элементам управления. Однако в секцию published они перенесены только в классе TForm, а для поддержки их в других визуальных компонентах следует создать наследника соответствующего компонента, в котором ссылки на обработчики событий будут иметь более широкую область видимости69. Вернемся к механизму обработки сообщений. Итак, если не обработано событие OnMouseWheel, то инициируются события OnMouseWheelUp и O n M o u s e w h e e l D o w n . Если же и эти события не обработаны (параметр Handled не получил значение True, либо обработчик не реализован), то обработка передается контейнеру, в котором находится данный компонент. Таким образом, если форма является дочерним MDI-окном, то обработка вращения колеса будет передана родительской форме. Если же окно является частью SDI-приложения, то событие останется не обработанным. Клавиатурные события И еще одна группа событий унаследована формами от визуальных компонентов-оболочек, наследников класса T W i n C o n t r o l . Данные события позволяют установить факт нажатия пользователем кнопок клавиатуры, причем определяются три раздельных процесса: нажатие на клавишу без " Например, компоненты Memo и RichEdit используют события вращения колеса мыши для прокрутки текста.
467
Часть IV. Компоненты и их использование
ее отпускания (событие OnKeyDown), отпускание клавиши (событие 70 OnKeyUp) и нажатие на кнопку (событие OnKeyPress): property O n K e y D o w n :
TKeyEvent;
property OnKeyUp: T K e y E v e n t ; property O n K e y P r e s s : T K e y P r e s s E v e n t ;
Процедурные типы обработчиков данных событий имеют следующие описания: Туре TKeyEvent = procedure
(Sender: T O b j e c t ; var K e y : W o r d ; S h i f t : T S h i f t S t a t e ) o f object;
T K e y P r e s s E v e n t = procedure
(Sender: TObject; var K e y : C h a r ) of object;
В обработчиках OnKeyDown и OnKeyUp имеется возможность проверки нажатия на клавишу совместно с некоторой управляющей клавишей (параметр shift). Сама клавиша, нажатие которой инициировало событие, задается параметром Key, в котором передается так называемый виртуальный код клавиши. Виртуальный код используется для идентификации клавиш-стрелок, управляющих клавиш и некоторых других, не представленных в таблице символов в явном виде. Например, при нажатии клавиши F12 значение параметра Key будет равно константе VK_F12, описанной в модуле Windows 71 . Еще одно событие, связанное с клавиатурой, реализовано только для форм: property O n S h o r t C u t :
TShortCutEvent;
Type T S h o r t C u t E v e n t = procedure Boolean) of object;
(var M s g :
TWMKey;
var H a n d l e d :
Данное событие предназначено для просмотра нажатой клавиши до обработки этого события другими системами компонента, в том числе и до вызова обработчиков OnKeyDown, OnKeyUp и OnKeyPress. С помощью OnShortCut можно изменить обычное поведение формы по отношению к стандартным комбинациям клавиш, используемым, например, для быстрого доступа к пунктам меню.
Данное событие возникает, когда значение, соответствующее нажатой клавише? попадает в буфер клавиатуры, и, обычно, появляется сразу же после OnKeyDown. Список констант можно найти в разделе справочной системы «Virtual key codes». Для этого необходимо запустить справочную систему с помощью пункта главного меню среды разработчика Help->Delphi Help, и в появившемся окне на закладке «Предметный указатель» ввести словосочетание «Virtual key codes», после чего нажать на кнопку «Показать».
468
Глава 18. Формы
Заметим, что, если на форме расположен какой-либо компонент, который имеет фокус ввода, то все перечисленные обработчики, кроме OnShortCut, вызываться не будут. Однако если существует такая необходимость, форма может получить доступ к потоку ввода раньше, чем ее компоненты. Для этого следует свойству KeyPreview установить значение True: property KeyPreview:
Boolean;
Такая возможность позволяет фильтровать входной поток символов. Например, обработчик события OnKeyPress, заменяющий символ 'S' на символ 'Ф' («русификатор»), может выглядеть следующим образом: procedure TForml.FormKeyPress(Sender: TObject; var Key: Char); begin if Key = 'A' Then Key := 'Ф' ; end;
Для того, чтобы данный обработчик всегда получал управление при нажатии клавиши, вне зависимости от наличия на форме компонентов, имеющих фокус ввода, следует установить значение True свойству KeyPreview.
18.4.2. Свойства форм Расположение и размеры Расположение и размеры форм задаются обычными для визуальных компонентов свойствами L e f t , Top, W i d t h и Height. При этом могут быть использованы свойства ограничения минимальных и максимальных размеров окна Constraints, а также свойство A l i g n , выравнивающее окно формы по заданной стороне контейнера, в котором она находится. Для MDI-окон таким контейнером является родительская форма, а для SDIформ — все поле экрана. Помимо свойств width и Height, определяющих размер окна с учетом всех его атрибутов (заголовка и рамки), возможно использование свойств Clientwidth и ClientHeight, указывающих размер клиентской (доступной для размещения компонентов) области окна: • property C l i e n t w i d t h : I n t e g e r ; property C l i e n t H e i g h t :
Integer;
Для повышения удобства пользователя при размещении окна на экране предусмотрено свойство автоматического выравнивания («прилипания») окна относительного краев экрана. При приближении окна к границе
469
Часть IV. Компоненты и их использование
экрана, оно будет расположено вплотную к ней, если свойство ScreenSnap формы имеет значение True: property
ScreenSnap:
Boolean;
Свойство SnapBuf fer указывает, на каком расстоянии (в точках экрана) должна находиться форма от края экрана, чтобы быть выровненной по этому краю: property SnapBuffer:
Integer;
При выводе окна на экран оно может принять положение и размеры, которые рекомендованы операционной системой для текущего состояния рабочего стола. Такое поведение окон установлено для дочерних MDIокон по умолчанию, однако в большинстве случаев это приводит к непредсказуемым последствиям для пользовательского интерфейса. Начальным расположением окна и его размерами управляет свойство Position: property Position:
TPosition;
Возможные значения свойства Position перечислены в табл. 18.4/ И, наконец, последний параметр, управляющий положением и размерами окна на экране, называется windowstate и определяет, в каком состоянии находится окно: property WindowState: TWindowState;
Возможные значения свойства Position формы Значение
Таблица 18.4
Положение и размеры окна
poDesigned
Такие же, как в процессе визуальной разработки. При таком способе расположения могут возникнуть проблемы, связанные с разницей в разрешениях экрана разработчика и пользователя, вплоть до того, что форма полностью будет находиться за пределами экрана
poDefault
Устанавливаются автоматически операционной системой
poDefaultPosOnly
Размеры соответствуют свойствам Width и Height, положение определяется автоматически
poDefaultSizeOnly
Положение соответствует свойствам Left и Тор, размеры определяются автоматически
poScreenCenter
Размеры соответствуют свойствам Width и Height, положение устанавливается таким образом, чтобы форма находилась по центру экрана. Данная возможность удобна для создания так называемых Splash-Window - приветственных окон, отображаемых во время загрузки основной части приложения
poDesktopCenter
Аналогично poScreenCenter, однако используется другой механизм расположения при работе в системе, имеющей вывод на несколько мониторов
poMainFormCenter
Аналогично poDesktopCenter, но дочерние MDI-формы располагаются по центру родительского окна, а не по центру экрана
poOwnerFormCenter
Аналогично poMainFormCenter, но расположение определяется формой, указанной в свойстве Owner, если в нем действительно задана ссылка на форму
470
Глава 18. Формы
Данное перечислимое свойство может иметь следующие значения: l.wsNormal — положение и размеры определяются свойствами формы Left, Top, Width, Height; 2. wsMinimized — форма минимизирована; 3. wsMaximized — форма развернута на весь экран. Управление атрибутами окна Каждое окно в Windows может иметь определенный набор атрибутов. Рассмотрим некоторые из них с точки зрения возможностей управления этими атрибутами: 1. Заголовок предназначен для идентификации окна с помощью подписи, выводимом на нем (подпись хранится в свойстве Caption формы), а также для перемещения окна по экрану. 2. Кнопки управления окном «Свернуть», «Развернуть на весь экран» и «Помощь» отображаются в заголовке окна в соответствии с набором элементов множественного свойства Bordericons типа TBordericon. Для каждой кнопки управления окном предусмотрен свой элемент данного множества'(biMinimize, biMaximize и biHelp). 3. Системное меню окна располагается в левой части заголовка и дублирует кнопки управления окном, отображение иконки системного меню определяется в соответствии с наличием элемента biSystemMenu в свойстве Bordericons. Изображение иконки хранится в свойстве icon типа TlCOn. При отсутствии системного меню не выводятся кнопки управления окном и кнопка закрытия окна. Подробного рассмотрения заслуживает такой атрибут окна, как рамка (бордюр), определяющий не только вид самой рамки и возможности, которые,она предоставляет пользователю, но и наличие заголовка окна. Рамка предназначена для визуального отделения окна от других окон, расположенных на экране, и для изменения размеров окна.. Вид бордюра задается перечислимым свойством B o r d e r S t y l e типа TFormBorderStyle, возможные значения которого представлены в табл. 18.5. property BorderStyle: TFormBorderStyle;. Закончим обсуждение атрибутов окна рассмотрением целочисленного свойства, указывающего величину отступа клиентской части окна от рамки: property B o r d e r W i d t h : O . . M a x I n t ; Прозрачность окна Windows, начиная с версии 2000? поддерживает организацию полупрозрачных программных окон (см. рис. 18.8). Delphi, начиная с версии 6.0 471
Часть IV. Компоненты и их использование Виды рамки окна в соответствии со свойством BorderStyle
Таблица 18.5
Вид рамки
Наличие и вид заголовка
bsDialog
Трехмерная рамка без возможности изменения размеров окна. Данный вид окна является стандартным для использования в диалогах, в которых количество и расположение элементов управления обычно заранее известно
Присутствует, размер обычный
bsSingle
Рамка без эффекта трехмерности и возможности изменения размеров окна
Присутствует, размер обычный
bsNone
Рамка не выводится
Отсутствует
bsSizeabte
Трехмерная рамка с возможностью изменения размеров окна. Используется по умолчанию
Присутствует, размер обычный
bsToolWindow
Аналогично bsSingle
Присутствует, уменьшенного размера
bsSizeToolWin
Аналогично bsSizeable
Присутствует, уменьшенного размера
Значение
! Размер шрифта
Щ
/ -Ц
S
Цвет: Н Black
1,2.3 Отступ слева, 8
-Н Справа. |0
Сохранение и печать. Формат; *
Загрузить
-
Первая.етрока
Add to Repository, в результате выбора которого на экране будет отображен диалог, показанный на рис. 18.14. Данный диалог вызывается для текущей формы и предназначен для ввода характеристик, идентифицирующих форму в депозитарии. Форма будет отображена иконкой, выбранной с помощью кнопки Browse (на рисунке не видна, так как скрыта выпадающим списком). Страница депозитария, на которой будет располагаться форма, задается в выпадающем спис16 Зак. 867
481
Часть IV. Компоненты и их использование
I • JMy form template I Description: j (Заготовка для Форм в моих приложениях 1
Page:
.
...
1
Forms
\
\
Author
d
Projects Data Modules InlraWeb
2 I Рис.
j Michael
Cancel
Help
|
18.14. Диалог добавления формы в депозитарий
ке Page. Заголовок Title, описание формы (примечание) Description и указание автора формы A u t h o r используются для получения информации о форме при выборе ее в депозитарии. Построение формы на основе хранимой в депозитарии Для добавления в проект формы, хранимой в депозитарии, следует выбрать пункт главного меню среды разработчика File->New->Other, и в появившемся диалоге открыть страницу депозитария, на которой расположена искомая форма (см. рис. 18.15). Ъ New Items Projects
I
Data Modules j
IntraWeb j
WebServices
Business j WebSnap | V/eb Documents | Coiba , New
| ActiveX | МиШег j Project!
About box
£opy
Dual list box
inherit
Forms : I Dialogs
Tabbed pages
f Use
OK.
Help
Рис. 18.15. Депозитарий Для добавления формы в проект следует выбрать ее иконку и нажать кнопку Ok, предварительно установив переключатель «Copy-lnherit-Use», расположенный в нижней части диалогового окна, в необходимое положение. Данный переключатель определяет степень связи формы в проекте с ее шаблоном, хранящимся в депозитарии: 1. Сору (копирование). При таком положении переключателя форма из депозитария копируется в проект, и связи между ними не устанавливаются. Изменения формы в депозитарии не влияют на форму проекта, а изменение формы в проекте не изменяет форму депозитария. 482
Глава 18. Формы 2. Inherit (наследование). Форма из депозитария копируется в проект, и между ними устанавливается односторонняя связь. Изменение формы в депозитарии влияет на форму проекта, но изменение формы в проекте не изменяет форму депозитария. 3. Use (использование). Форма из депозитария копируется в проект, и между ними устанавливается двухсторонняя связь. Изменение формы в депозитарии влияет на форму проекта, и изменение формы в проекте влияет на форму, хранимую в депозитарии, что сказывается на всех формах во всех проектах, которые используют данную форму не в режиме Сору.
Вопросы с ответами для повторения по главе 18 Поясните понятие формы и основы использования форм в Delphi Ответ: Форма является визуальным контейнерным компонентом Delphi, предназначенным для самостоятельного использования в виде отдельного окна на экране. Форма является элементом проекта и для нее создается два файла: 1. Модуль формы, содержащий описание класса формы, в виде модуля (Unit) приложения. 2. Файл описания формы на языке XML, содержащий описание компонентов, добавленных на форму в процессе ее визуальной разработки. Формы реализованы классом TForm, но он не используется в программных продуктах в явном виде. Вместо этого при добавлении формы в проект создается класс-наследник формы, в который в процессе визуальной разработки автоматически добавляются ссылки на компоненты формы и методы, которые являются обработчиками событий формы и ее компонентов. Формы также часто используются в качестве носителей функциональности программного продукта, то есть в них размещается программный код, связанный с работой не только данной формы, но и всего программного приложения в целом. • Опишите жизненный цикл формы Ответ: Форма является экземпляром класса TForm, поэтому ее жизненный цикл во многом аналогичен жизненному циклу обычного объекта. Форма создается конструктором Create или методом CreateForm глобального объекта Application (более предпочтительный способ).
Далее, форма может быть отображена на экране с помощью метода show (немодальное отображение) или ShowModal (модальный режим). Временное скрытие формы осуществляется методом Hide, либо установкой свойству Visible значения False. 483
Часть IV. Компоненты и их использование Для формы введено понятие «закрытия». Закрытие формы производится методом Close, во время которого форма может быть либо скрыта методом Hide, либо разрушена. Способ закрытия формы определяется обработчиком события OnClose, вызываемом перед выполнением Close. Разрушение формы производится методом Release, который помимо очистки памяти, занятой под структуры формы, ждет завершения очереди сообщений и обработчиков событий зависимых компонентов, что обеспечивает корректное закрытие формы без ошибок нарушения доступа к памяти. Расскажите об основах организации многооконных приложений в Delphi Ответ: Для создания многооконного приложения в Delphi следует добавить одну или более дополнительных форм в проект и отобразить их во время выполнения программы. Все формы такого приложения будут иметь возможность независимо друг от друга перемещаться по экрану. Организация многооконного интерфейса в таком стиле носит название SDI — Single Document Interface — Интерфейс одного документа. При закрытии формы, которая указана в качестве главной в диалоге Project Options интегрированной среды разработчика, будут автоматически закрыты все формы приложения. Второй подход к организации многооконных приложений называется MDI — Multi Document Interface — Интерфейс нескольких документов. Окна такого приложения визуально принадлежат главному окну, имеющему стиль f sMDiForm, устанавливаемый свойством FormStyie. Остальные формы имеют стиль f sMDlChild. Дочерние окна не могут выходить за пределы главного окна, за исключением инструментальных окон, автоматически создаваемых при откреплении элементов управления от форм. Приложения, организованные в соответствии с концепцией SDI, могут содержать окна со стилем f sStayOnTop, которые всегда находятся выше остальных окон. Поясните понятие фрейма Ответ: Фреймом называется специальный элемент проекта Delphi, представляющий собой контейнерный компонент-наследник TFrame, в который, аналогично формам, можно добавлять компоненты. Экземпляр фрейма может быть создан во время визуального проектирования приложения с помощью Палитры компонентов, либо во время выполнения программы. При использовании фреймов создаются не экземпляры класса TFrame, a экземпляры наследников этого класса, которые подготавливаются с помощью визуального построителя среды разработчика. Таким образом, технология использования фреймов в этом смысле аналогична использованию форм.
484
Глава 18. Формы Экземпляры фреймов поддерживают связь со своими классами по значениям свойств и методам-обработчикам сообщений. При изменении значения какого-либо свойства в описании фрейма, это изменение отразится на экземплярах фрейма, расположенных в формах или других контейнерах. Связь между экземпляром класса и его описанием по какому-либо свойству нарушается, если значение этого свойства изменено в экземпляре фрейма. !
Расскажите о депозитарии форм Ответ: Депозитарий форм предназначен для обмена формами между проектами с возможностью сохранения связей между ними, аналогично тому, как это реализовано для фреймов. В депозитарий можно добавить любую форму из любого проекта, а затем скопировать ее в этот же или другой проект. При копировании формы из депозитария можно задать вариант наследования свойств: простое копирование без возможности наследования, копирование с поддержкой изменений формы депозитария, копирование с сохранением двухсторонней связи, то есть с возможностью изменить форму депозитария, изменяя форму-копию в проекте.
485
ЧАСТЬ V
ВЗАИМОДЕЙСТВИЕ ПРИЛОЖЕНИЯ С ОПЕРАЦИОННОЙ СИСТЕМОЙ
ГИБКОЕ УПРАВЛЕНИЕ ОКРУЖЕНИЕМ
20
$А г/
ВЫВОД ИНФОРМАЦИИ ЗА ПРЕДЕЛЫ ПРОГРАММЫ. ТЕХНОЛОГИЯ СОМ УПРАВЛЕНИЕ ВЫПОЛНЕНИЕМ ПРИЛОЖЕНИЯ
ОСНОВЫ DELPHI. Профессиональный noj
Гибкое управление окружением
19.1. Введение Программа, выполняемая в операционной среде Windows, тесно взаимодействует с ней через вызов API-функций для реализации собственной функциональности. Характеристики, используемые системой пользователя, могут отличаться от тех, которые были установлены при разработке приложения, что вызывает непредвиденные эффекты поведения программы. Настройки различных аспектов операционной системы описывают понятием окружения программы, которое имеет следующие группы характеристик: » характеристики самого приложения, построенные на некоторых настройках операционной системы; , » характеристики экрана, установленные пользователем; • параметры мыши; » параметры принтера. Для перечисленных групп параметров окружения программы в Delphi реализованы классы, предоставляющие интерфейс к ним. Так, для работы с настройками приложения предусмотрен класс TApplication, а для доступа к характеристикам экрана и мыши — классы TScreen и TMouse. Поскольку в системе в каждый момент времени работы программы может использоваться только одна мышь и один экран, то на старте программы автоматически создаются экземпляры классов TMouse и TScreen — объекты Mouse и Screen. Также программа имеет доступ к автоматически создаваемому экземпляру класса T A p p l i c a t i o n по ссылке Application. Описанные объекты доступны в любом фрагменте програм487
Часть V. Взаимодействие приложения с операционной системой
ме, создаются автоматически, и могут существовать только в единственном экземпляре, поэтому их называют глобальными объектами или глобальными переменными. Все, что необходимо сделать фрагменту программы для доступа к глобальным переменным — подключить модули, в которых эти переменные описаны (модуль Forms для доступа к Application и Screen, и модуль Controls для доступа к Mouse). В модулях форм, создаваемых визуальным построителем, все эти модули подключаются автоматически.
19.2. Глобальная переменная Application Глобальный объект Application содержит некоторые характеристики приложения, применяемые во всех его частях, и позволяет изменять некоторые из них. Данный объект интенсивно используется в основной части Delphi-программы, в частности, для создания форм приложения, указанных в списке Auto-create Forms диалога Project Options. Далее рассмотрены основные возможности объекта Application. Управление быстрыми подсказками Для быстрых подсказок, всплывающих при наведении курсора мыши на элемент управления, с помощью свойств объекта Application можно установить такие характеристики, как: • цвет фона подсказки - свойство Hintcolor; » время от наведения мыши на элемент управления до всплытия подсказки — свойство HintPause; » длительность подсказки — свойство HintHidePause; * возможность отображения подсказок вообще — свойство ShowHint. Описаны эти свойства следующим образом: property property property property
HintColor: TColor; HintPause: Integer; HintHidePause: Integer; S h o w H i n t : Boolean;
Все перечисленные параметры подсказок влияют на все подсказки приложения и могут изменяться в процессе выполнения программы. Время присутствия подсказок на экране и время до их появления указывается в миллисекундах. Если по какой-либо причине следует прекратить демонстрацию подсказки, выведенной в данный момент, можно воспользоваться методом CancelHint: procedure C a n c e l H i n t ;
488
Глава 19. Гибкое управление окружением
Взаимодействие с пользователем через простейшие диалоги Большинство диалогов с пользователем в программных продуктах строится по принципу «Вопрос — ответ», когда на экран выводится некоторое сообщение, а пользователь должен отреагировать на него, выбрав нужный вариант ответа с помощью одной из кнопок диалога. Набор вариантов ответа также достаточно ограничен и практически одинаков для приложений различной направленности. Для реализации подобных диалогов в объекте Application предусмотрен метод MessageBox, описанный следующим образом: function MessageBox(const T e x t , C a p t i o n : PChar; F l a g s : L o n g i n t = MB_OK): I n t e g e r ;
Диалоговое окно, выводимое на экран при выборе данной команды, имеет заголовок, указанный в качестве параметра Caption и текст вопроса, заданный параметром Text. Параметр Flags определяет состав кнопок диалога для выбора пользователем ответа и может иметь одно из значений, перечисленных в табл. 19.1. Флаги наличия кнопок в диалоге MessageBox
Таблица 19.1 Набор кнопок
Значение MB_ABORTRETRYIGNORE
«Abort", «Retry», и «Ignore»
МВ_ОК
«OK» (такое значение имеет параметр по угюлчанию)
MB_OKCANCEL
«ОК" и «Cancel»
MB_RETRYCANCEL
«Retry" и «Cancel»
MB_YESNO
«Yes" и «No»
MB_YESNOCANCEL
«Yes», «No», и «Cancel»
Диалог MessageBox является модальным и приостанавливает выполнение фрагмента программы, из которого он вызван. Значение, возвращаемое функцией MessageBox, определяет, какая кнопка нажата пользователем для закрытия диалога (см. табл. 19.2). Таблица 19.2 Значения, возвращаемые функцией MessageBox Значение функции
Нажатая кнопка
IDOK
«ОК»
IDCANCEL
«Cancel»
IDABORT
«Abort»
IDRETRY
«Retry»
IDIGNORE
«Ignore»
IDYES
«Yes»
IDNO
«No»
489
Часть V. Взаимодействие приложения с операционной системой Константы, представленные в обеих таблицах, описаны в стандартном модуле Windows. Для отображения диалога, представленного на рис. 19.1, следует вызвать функцию MessageBox следующим образом: Application.MessageBox( Л Файл не перемещен, повторить операцию?', л Ошибка перемещения ф а й л а ! ' , MB YESNOCANCEL Если ответ пользователя имеет значение, то следует сохранить значение, которое вернула функция MessageBox и проанализировать его с помощью констант, представленных в табл. Ошибка перемещения файна! j
Файл ие перемещен, повторить операцию? Да
Ивт
I
Отмена
Рис. 19.1. Диалог MessageBox Идентификация приложения в системе В каждый момент времени системой может выпЪлняться несколько приложений, одно из которых активно. Пользователь имеет возможность переключаться между приложениями с помощью комбинации клавиш Alt-Tab или через Панель задач. При этом пользователь ориентируется на название программы и иконку, которая ей сопоставлена. Для этих двух атрибутов приложения предусмотрены свойства T i t l e и icon объекта Application: .
property T i t l e : s t r i n g ; property Icon: TIcon; Свойства Title и icon доступны во время выполнения программы и при их изменении сразу же обновляются соответствующие атрибуты приложения. Таким образом, с помощью этих свойств можно организовать простейшую анимацию значка приложения и, например, прокрутку названия приложения. Изменения свойств можно произвести в обработчике события OnTimer компонента Timer: i procedure TForml.TimerlTimer(Sender : TObject); begin Application.Title := AnsiMidStr(Application.Title, 2, Length(Application.Title) - 1) + Application.Title[1]; end; 490
Глава 19. Гибкое управление окружением В приведенном обработчике в свойство Title объекта Application заносится его же значение, начиная со второго символа, с помощью функции AnsiMidStr модуля StrUtils: function AnsiMidStr(const A T e x t : A n s i S t r i n g ; const A S t a r t , ACount: Integer ) : AnsiString; Далее, в конец строки добавляется первый символ предыдущего значения свойства Title с помощью доступа к данному свойству через индекс: Application.Title[I] Таким образом, название приложения в панели задач Windows будет циклически прокручиваться влево. Информация об исполняемом файле Для определения каталога, из которого запущена программа, и имени исполняемого файла приложения, в объекте Application предусмотрено свойство ExeName, содержащее полное имя файла, с помощью которого запущена программа: property ExeName: s t r i n g ; Поскольку разбор такой строки может представлять некоторые трудности, в Delphi реализовано множество функций, упрощающих работу с именами файлов. Так, функции ExtractFilePath и ExtractFileName, описанные в модуле SysUtils, возвращают строки, содержащие соответственно, путь к файлу и его имя на основе полного имени: function ExtractFileName(const FileName: s t r i n g ) : s t r i n g ; function ExtractFilePath(const FileName: s t r i n g ) : s t r i n g ; Определение названия исполняемого файла может использоваться при построении защиты программы для запрета выполнения приложения, запущенного из ехе-файла с измененным именем. Такая проверка может быть вставлена в модуль проекта после вызова метода i n i t i a l i z e объекта Application, но до создания форм приложения. Также следует добавить ссылку на модуль SysUtils, в котором описана функция ExtractFileName в список подключаемых модулей. Код модуля приведен в листинге 19.1. Листинг 19.1. Обработка информации об исполняемом файле program P r o j e c t l ; uses Forms, Unitl in 'Unitl.pas' {Forml}, SysUtils; // Ссылка на модуль добавлена разработчиком 491
Часть V. Взаимодействие приложения с операционной системой {$R * . r e s } begin Application.Initialize; // Проверка названия файла, из которого запущено приложение if ExtractFileName(Application.ExeName) 1 AMTAutomate.exe' Then begin Application.MessageBox('Изменено название приложения. Продолжение работы невозможно', Л Ошибка в системе защиты'); Application.Terminate; end; Application.CreateForm (TForml, Forml); Application.Run; end.
В случае запуска такой программы из файла, название которого отлично от «AMTAutomate.exe», на экран будет выведен диалог MessageBox с предупреждением о невозможности продолжения работы. Появление диалога приостановит работы программы до нажатия пользователем на кнопку Ok диалога, после чего будет вызван метод T e r m i n a t e объекта Application, и приложение завершится. Управление состоянием приложения С помощью методов объекта Application можно изменить остояние приложения, например, свернуть его и сделать неактивным (метод Minimize), развернуть и сделать активным (метод Restore), переместить выше относительно других окон (метод BringToFront), а также закрыть приложение (метод Terminate, использованный в примере выше): procedure procedure procedure procedure
Minimize; Restore; BringToFront; Terminate;
Особенности обработки событий в Windows и Delphi Обработка сообщений в Delphi-программах устроена таким образом, что до завершения обработки какого-либо события разбор очереди сообщений прекращается. При этом подавляющее большинство методов, которые работают в приложении, являются обработчиками событий. С другой стороны, изменение свойств компонентов часто бывает построено на использовании сообщений, например, изменение положения элемента управления выполняется не прямо, а уведомлением его об изменении через очередь сообщений. 492
Глава 19. Гибкое управление окружением
Таким образом, многократное изменение свойства компонента внутри одного метода, который является обработчиком события, может не иметь смысла. Обусловлено это тем, что до завершения данного метода компонент не сможет получить доступ к очереди сообщений и отреагировать на изменение значения свойства. Рассмотрим пример, в котором обработчик сообщения Onclick кнопки Buttonl изменяет подпись на текстовой метке Labell в цикле 100000 раз: procedure T F o r m l . B u t t o n l C l i c k ( S e n d e r : Var i: I n t e g e r ; begin For i := 0 To 1 0 0 0 0 0 Do L a b e l l . C a p t i o n := I n t T o S t r ( i ) ; end;
TObject);
При нажатии на кнопку в такой программе будет вызван обработчик нажатия B u t t o n l C l i c k , который приостановит нормальное выполнение программы на некоторое время, так как внутри него выполняется длительная операция -- многошаговый цикл. При этом подпись на метке Labell изменяться в процессе выполнения обработчика не б у д е т 7 2 , а после его завершения примет последнее установленное значение ('100000'). Вообще, такой организации программы следует избегать, так как во время выполнения длительной процедуры невозможно не только обновление подписи на метке, но и манипуляции с окном, например, его перемещение. Частично решить проблему длительных обработчиков позволяет метод ProcessMessages объекта Application, прерывающий на время выполнение текущего обработчика, и позволяющий разобрать очередь сообщений. После того, как в очереди не останется сообщений, управление снова вернется в прерванный обработчик. procedure
ProcessMessages;
На самом деле, значение свойства Caption будет изменяться, так как сообщение об изменении подписи посылается в обход очереди сообщений Windows с помощью команды Perform. Однако для отображения измененной подписи форма должна обработать сообщение WM_PAINT о перерисовке окна (этому сообщению соответствует событие OnPaint формы), а это может произойти только после завершения обработчика нажатия на кнопку.
493
Часть V. Взаимодействие приложения с операционной системой
Исправленный обработчик будет выглядеть следующим образом: procedure T F o r m l . B u t t o n l C l i c k ( S e n d e r : Var i: I n t e g e r ; begin For i := 0 To 1 0 0 0 0 0 Do begin
TObject);
Labell.Caption := IntToStr(i); Application.ProcessMessages; end; end;
При вызове метода ProcessMessages выполнение обработчика будет приостановлено до полного разбора очереди сообщений, что обеспечит корректную перерисовку компонента Labell. Заметим, что если во время выполнения такого обработчика пользователь нажал на кнопку Buttonl еще раз, то будет выполнен сначала обработчик второго нажатия, затем управление вернется к обработке первого нажатия73. Однако при использовании второго варианта обработчика в приложении также присутствует один недостаток, который заключается в том, что форма не сможет быть закрыта до завершения всех обработчиков. Более того, даже после нажатия на системную кнопку 'Закрыть' в правой части заголовка окна, приложение может работать сколь угодно долго, если пользователь периодически нажимает на кнопку, запускающую снова длительный обработчик. Это происходит несмотря на то, что событие закрытия стоит в очереди раньше, чем очередные нажатия на кнопку. Такое поведение программы связано с функциональностью метода Release, используемого для разрушения формы, и ожидающего завершения очереди сообщений. Пока пользователь поддерживает наличие сообщений в очереди, метод Release не сможет разрушить форму, и приложение не завершится. Описанный недостаток можно исправить, воспользовавшись логической переменной, описанной в классе, и устанавливаемой в значение True при вызове обработчика события OnClose. Обработчик события Onclick, в свою очередь, должен проверять состояние данного флага, и в случае обнаружения значения True прерывать свою работу:
Таким образом, методы-обработчики событий в Delphi являются реентерабельными, что следует учитывать при разработке приложений. Если обработчик должен выполняться последовательно, а именно это обычно и требуется, в его начале можно запретить взаимодействие пользователя с элементом управления, который вызывает обработчик. После завершения обработчика состояние элемента управления можно восстановить, если это требуется.
494
Глава 19. Гибкое управление окружением
TForml = class(TForm) private CloseFlag: Boolean;
// описание переменной, которая // может использоваться во всех // методах формы
end; ' procedure TForml.ButtonlClick(Sender: TObject); Var i: Integer; begin For i := 0 To 10000 Do begin Labell.Caption := IntToStr(i); Application.ProcessMessages; If CloseFlag Then Exit; // Проверка состояния флага и выход end; end; procedure TForml.FormClose(Sender: TObject; var Action: TCloseAction); begin CloseFlag := True; // Взводим флаг end;
Распределение вычислительной нагрузки на приложение Несмотря на то, что проблемы длительных обработчиков решаются тем или иным способом, такая организация программы крайне не рекомендуется. Для реализации длительных вычислений в операционной системе предусмотрены различные механизмы, одним из которых является обработка события Onldle, описанного в классе TApplication: property Onldle: T I d l e E v e n t ; Type TIdleEvent = procedure (Sender: T O b j e c t ; var Done: Boolean)
of object;
Событие Onldle возникает всегда, когда приложение не выполняет какой-либо фрагмент кода. Таким образом, обработчик данного события выполняется в так называемом фоновом режиме и не снижает производительность основного приложения.
495
Часть V. Взаимодействие приложения с операционной системой Так как событие описано не в классе формы или. какого-либо другого компонента, участвующего в процессе визуальной разработки, обработчик события Onidle устанавливается во время выполнения программы. Для таких действий подходит любое место программы, например, обработчик события OnCreate формы, в котором данный обработчик реализован (обычно для этого используется главная форма, как первая из создаваемых): TForfcil = class(TForm) procedure FormCreate(Sender: TObject); private {Заголовок метода-обработчика, добавлен вручную} Procedure GlobalOnldle(Sender: TObject; var Done: Boolean); end;
{Обработчик события формы OnCreate, создан визуальным построителем} procedure TForml.FormCreate(Sender: TObject); begin Application.Onidle := GlobalOnldle; (Назначение обработчика Onidle} end; {Обработчик события приложения Onidle, создан разработчиком} Procedure TForml.GlobalOnldle(Sender: TObject; var Done: Boolean); begin end;
С момента назначения обработчика событию On idle данный обработчик будет вызываться с некоторой периодичностью, в зависимости от общей нагрузки на приложение и систему в целом. Например, при перемещении формы по экрану Onidle не происходит. Обратим внимание на параметр Done обработчика, который указывает системе, завершены ли действия, которые следует выполнить в фоновом режиме. Значение, которое содержится в данном параметре после завершения обработчика, в существенной мере определяет интервал времени, который пройдет до следующего вызова обработчика. Также, значение
496
Глава 19. Гибкое управление окружением
параметра Done определяет, следует ли инициировать событие Onldle, когда приложение неактивно. Таким образом, если обработчик события Onldle заносит в параметр Done значение True, то следующий вызов обработчика происходит через некоторое время (порядка половины секунды), а, если приложение неактивно, то не происходит вообще. Если же параметр имеет значение False, то обработчик будет вызван и для неактивного приложения с частотой 74 порядка пятьсот раз в секунду . Событие Onldle ничем не отличается от других событий в Windows. В частности, сообщение о данном событии приходит в приложение через очередь сообщений, и следующее сообщение очереди будет обработано только после завершения обработчика Onldle. В результате обработчики таких сообщений должны следовать обычным правилам реализации, а именно, не использовать длительных циклов, приостанавливающих обработку очереди сообщений. Распространенным подходом к снижению вычислительной нагрузки в методах-обработчиках Onldle является разбиение долговременных операций на несколько шагов. Выполнение одного такого шага происходит при обработке одного события Onldle. Допустим, имеется обработчик следующего вида: Procedure T F o r m l . G l o b a l O n l d l e ( S e n d e r : T O b j e c t ; var Done: B o o l e a n ) ; Var i: Integer; begin for i := 0 To 100000 do Labell.Caption := IntToStr(i); end;
Такой обработчик приостанавливает разбор очереди сообщений и делает пользовательский интерфейс крайне неудобным для работы. Одним логическим шагом данной длительной процедуры является установка свойства Caption компонента Labell: Labell.Caption
:= I n t T o S t r ( i ) ;
Для правильной организации обработчика следует модифицировать программу, убрав из нее использование цикла. Для этого вынесем описание переменной i из обработчика в описание класса формы, и реализуем обработчик следующим образом: 74
Следует отметить, что эти значения могут существенно отличаться в зависимости от различных условий, причем необходимость вызова обработчика для неактивного приложения также определяется системой.
497
Часть V. Взаимодействие приложения с операционной системой Procedure T F o r m l . G l o b a l O n l d l e ( S e n d e r : T O b j e c t ; var Done: B o o l e a n ) ; begin L a b e l l . C a p t i o n := I n t T o S t r ( i ) ; ., i := i + 1; if, i > 1 0 0 0 0 0 then i := 0; end;
В результате предложенного подхода длительные вычисления в программе управляются системой автоматически и распределяются по отрезкам времени наименьшей нагрузки на систему. Для увеличения частоты вызовов обработчика, если это допустимо с точки зрения системы, можно установить параметру Done значение False, указывающее на незавершенность длительной операции: Procedure
T F o r m l . G l o b a l O n l d l e ( S e n d e r : TObject; var Done: B o o l e a n ) ;
begin Labell.Caption := I n t T o S t r ( i ) ; i := i + 1; if i > 1 0 0 0 0 0 then i := 0; Done := False; // "Операция не завершена" end;
Событие необработанного исключения Если в каком-либо фрагменте программы возникает исключение, система пытается найти обработчик данного класса исключений в этом фрагменте, либо в вызывающих его фрагментах вверх по стеку вызовов подпрограмм. Если исключение не обработано нм в одной из этих подпрограмм, то сообщение о нем выдается пользователю. С помощью объекта Application можно изменить поведение программы, обработав событие OnException: property O n E x c e p t i o n : TExceptionEvent;
Процедурный тип, определяющий сигнатуру обработчика события OnException, описан следующим образом: Туре TExceptipnEvent = procedure (Sender: T O b j e c t ; E: E x c e p t i o n ) of object;
Как видно из описания, обработчику передается ссылка на объект, в котором произошло исключение, и ссылка на объект исключения. Если
498
Глава 19. Гибкое управление окружением
обработчик события OnException назначен, то сообщение пользователю не выдается, а действия, выполняемые при появлении «не пойманного» исключения, определяются самим обработчиком. После завершения обработчика объект исключения автоматически разрушается.
19.3. Глобальная переменная Screen Переменная Screen является экземпляром класса TScreen, создается при старте приложения и содержит информацию о следующих параметрах окружения: • оконная структура приложения (количество форм, отображенных на экране, и ссылки на них); » ссылка на активную форму и компонент, имеющий фокус ввода в данный момент времени выполнения программы; » размеры экрана и его цветность; » набор курсоров и шрифтов, доступных в системе; • список мониторов, используемых в системе, и их характеристики. Мы не будем подробно останавливаться на данном объекте, а рассмотрим основную возможность, предоставляемую приложению — возможность определения размеров экрана для более эффективного управления размером окон приложения и их расположением. Характеристики экрана представлены свойствами width и Height, определяющими, соответственно ширину и высоту экрана. Данные свойства являются целочисленными и доступны только для чтения, как и большинство свойств объекта Screen. Таким образом, управление экраном с помощью данного объекта производить невозможно. property W i d t h : Integer; property H e i g h t : Integer; Заметим, что рабочий стол Windows может быть больше размеров экрана, однако в свойствах Width и Height содержатся именно размеры рабочего стола. Приложение не может получить доступ к размерам экрана, и в этом нет необходимости. Также в объекте Screen нет возможности получения цветности экрана, однако операционная система автоматически преобразует цвета, указанные программой, под любую цветность, заданную в системе, поэтому в определении этой характеристики также нет необходимости. Параметры представления рабочего стола, на котором выполняются приложения в Windows, устанавливаются пользователем, и он сам несет отиоо ЧУУ
Часть V. Взаимодействие приложения с операционной системой
ветственность за некорректные настройки операционной системы, не позволяющие ему воспользоваться всеми возможностями, которые предоставляет программный продукт.
19.4. Глобальная переменная Mouse Глобальный объект Mouse — экземпляр класса TMouse — содержит информацию о характеристиках мыши. Рассмотрим основные возможности данного объекта. Положение указателя мыши на экране Положение указателя мыши находится в свойстве CursorPos объекта Mouse и доступно как для чтения с целью определения положения, так и для изменения, с целью перемещения указателя в другую точку экрана. property CursorPos:
TPoint;
Для присвоения значения свойству CusorPos можно использовать функцию Point, описанную в модуле Types: function Point (AX, A Y : I n t e g e r ) : T P o i n t ; Для перемещения мыши можно использовать конструкцию такого вида: Mouse.CursorPos
:= P o i n t ( R a n d o m ( 8 0 0 ) ,
Random(600));
Захват мыши окном приложения Окно приложения начинает реагировать на сообщения от мыши, когда указатель находится в пределах его клиентской части. Однако некоторым формам необходимо получать полную информацию о перемещении мыши. Например, выпадающие списки должны иметь возможность определить факт нажатия кнопки мыши за пределами списка, чтобы закрыть свою выпадающую часть. В любой момент времени выполнения программы можно получить ссылку на окно, монополизирующее получение сообщений мыши, с помощью свойства Capture объекта Mouse: '
property Capture:
HWND;
С другой стороны, если в это свойство занести ссылку на какое-либо окно, то все сообщения мыши будут приходить именно в него. Напомним, что ссылка на окно хранится в свойстве Handle каждого оконного (являющегося наследником класса TWinControl) компонента: Mouse.Capture := Forml.Handle; Заметим, что при изменении значения свойства Capture окно, которое монопольно владело мышью, не получает никаких сообщений, что мо500
Глава 19. Гибкое управление окружением
жет нарушить его нормальную работу. Таким образом, при использовании захвата мыши следует учитывать возможные потребности других окон, или, даже, других приложений. За основное правило можно принять то, что захват мыши возможен в обработчиках событий мыши, так как, возникновение таких событий для какого-либо окна возможно только, если 75 мышь не монополизирована другим окном . Исключением из правила являются события вращения колеса мыши, так как они могут передаваться средствами Delphi в родительские окна без учета свойства Capture. Заметим, что такой подход реализован не только в Delphi. Во многих программных продуктах вращение колеса мыши приводит к прокрутке какой-либо информации в окне, находящемся под выпадающими списками, которые всегда захватывают сообщения мыши. Настройки колеса мыши Характеристики прокручивания колеса мыши полезно использовать при организации прикладной программы. Объект Mouse предлагает два свойства, определяющие такие характеристики: » факт наличия колеса мыши вообще - свойство wheelPresent; * количество «строк», прокручиваемых за один шаг колеса — свойство WheelScrollLines. Описание свойств выглядит следующим образом: property i- r i WheelPresent: Boolean; property W h e e l S c r o l l L i n e s : Integer;
Скорость вращения колеса WheelScrollLines устанавливается в операционной системе и должна учитываться в прикладных программах для соответствия предпочтениям пользователя.
. 75
Монополизировать события мыши могут не только программные окна, то есть формы, но и любые оконные компоненты (наследники класса TWinControl). 501
20 Вывод информации за пределы программы, Технология СОМ
В результате работы прикладного программного продукта, рано или поздно, должен появиться какой-либо документ, представленный в некоторой переносимой форме. Наиболее распространенными "документами являются файлы и так называемые «твердые копии», то есть напечатанные документы. В настоящем разделе мы рассмотрим возможности печати в Delphi, а также такой интересный процесс, как передача информации в другие приложения, на примере Microsoft Word и Excel.
20.1. Вывод информации на печать Вывод информации на печатающее устройство производится в Delphi с помощью переменной типа TPrinter (описана в модуле Printers), экземпляр которой можно получить с помощью функции Printer, также описанной в модуле Printers: function P r i n t e r :
TPrinter;
Объект, возвращаемый функцией Printer, соответствует текущему принтеру и содержит всю необходимую информацию о его параметрах. Выбор текущего приложения для принтера и его характеристик осуществляется с помощью диалогов настройки печати, описанных выше, при этом в анализе введенной пользователем информации нет необходимости, так как все настройки производятся автоматически. Если перед получением объекта TPrinter диалоги настройки печати не вызывались, то текущим принтером считается принтер, установленный в системе по умолчанию. 502
Глава 20. Вывод информации за пределы программы. Технология СОМ
Формирование задания для печати является многошаговым процессом, выполняемым с использованием свойств и методов экземпляра TPrinter, и включающим в себя следующие этапы: 1. Открытие задания. Новое задание содержит одну страницу, имеющую характеристики (размеры, ориентацию, и т.д.), соответствующие настройкам принтера. 2. Вывод информации на текущую страницу. 3. Открытие новой страницы, если это необходимо, и возврат к пункту 2. 4. Закрытие задания. В этот момент документ будет отправлен на принтер. На каждом этапе формирования задания программа имеет возможность контролировать процесс печати, а также может прервать формирование задания без передачи его на печатающее устройство, в случае обнаружения, например, каких-либо внутренних ошибок. Управление структурой документа Под управлением структурой документа подразумевается создание документа для печати, определение количества страниц в нем, и пересылка документа печатающему устройству. Для открытия нового задания на печать используется метод BeginDoc без параметров. Если в момент вызова метода BeginDoc задание на печать уже открыто, то возбуждается исключительная ситуация с сообщением "Printing in progress" — "Задание на печать уже открыто". procedure BeginDoc; После вызова метода BeginDoc задание на печать сразу же отображается в окне свойств принтера с пометкой «Постановка». После завершения формирования документа следует вызвать метод EndDoc, который запустит процесс печати: procedure EndDoc; При вызове метода EndDoc в отсутствие открытого задания возбуждается исключительная ситуация с сообщением "Printer is not currently printing"— "Отсутствует задание для печати". Если формирование очередной страницы закончено, и требуется добавить в документ еще одну страницу, следует вызвать метод NewPage: procedure NewPage; Напомним, что после создания документа методом BeginDoc в него автоматически добавляется одна страница. Для идентификации задания в списке заданий печати Windows используется одна строка, которая хранится в свойстве Title объекта TPrinter: property T i t l e :
string; 503
Часть V. Взаимодействие приложения с операционной системой
Таким образом, формирование структуры задания для печати выглядит как показано в листинге 20.1. Листинг 20.1. Формирование структуры задания для печати Uses Printers;
{Подключение модуля P r i n t e r s }
Var ThePrinter: TPrinter; {Описание ссылки на экземпляр TPrinter} ThePrinter := Printer;
{Получение объекта -- текущего принтера} ThePrinter.Title := 'Печать из Delphi'; {Строка, идентифицирующая задание в очереди заданий) ThePrinter.BeginDoc; {Открытие задания печати} {Формирование первой страницы} ThePrinter.NewPage; {Добавление дополнительной страницы и ее формирование. Может выполн я т ь с я несколько раз} ThePrinter.EndDoc; {Закрытие задания и вывод на принтер}
Разрушение экземпляров TPrinter производится системой автоматически и не может быть вызвано приложением. В противном случае, при попытке разрушения возникнет исключительная ситуация нарушения доступа к памяти. Вывод информации Формирование каждой страницы печатаемого документа производится с помощью свойства Canvas типа TCanvas: property Canvas:
TCanvas;
В каждый момент времени имеется доступ только к одной странице печатаемого документа, и свойство Canvas отражает именно эту страницу. После добавления очередной страницы доступ к ранее изменяемой невозможен. Использование свойств типа TCanvas подробно рассмотрено в разделе, посвященном областям отображения. С помощью свойства Canvas можно вывести на печатающее устройство самую разнообразную графическую информацию. Параметры, необходимые для вывода, можно определить с помощью свойств используемого экземпляра TPrinter. 504
Глава 20. Вывод информации за пределы программы. Технология СОМ
Размеры страницы, на которую выводится изображение, содержатся в свойствах PageWidth и PageHeight, (размеры страницы задаются в точках): property P a g e W i d t h : Integer; property PageHeight: Integer;
Вообще область отображения представляет ту часть страницы, на которую действительно может быть выведена информация. Соответственно, размеры области существенно зависят не только от выбранного формата носителя, но еще и от характеристик печатающего устройства. Ориентация страницы (книжная или альбомная) может быть установлена пользователем в диалоге печати, и определена программой с помощью свойства Orientation, аналогичного одноименному свойству диалогов настройки печати. Однако свойства P a g e W i d t h и PageHeight учитывают текущую ориентацию, например, при альбомном расположении страницы, ее ширина больше высоты. Свойство Orientation, таким образом, используется в основном для изменения с целью вывода страницы соответствующим образом. property
Orientation:
TPrinterOrientation;
V
Контроль за процессом формирования документа Программа, использующая экземпляр класса TPrinter, может: » определить наличие незавершенного задания с помощью свойства Printing; » прервать процесс построения документа вне зависимости от стадии с помощью метода Abort; » определить, является ли задание прерванным пользователем или методом Abort с помощью свойства Aborted. Описание перечисленных свойств таково: property P r i n t i n g : Boolean; procedure A b o r t ; property A b o r t e d : Boolean;
-
Пример вывода информации на принтер В качестве примера приведем обработчик нажатия на кнопку Button, в котором на принтер выводятся две страницы, каждая из которых обведена рамкой. На первой странице рамка выводится красным цветом, а на втором — синим. Код обработчика приведен в листинге 20.2.
505
Часть V. Взаимодействие приложения с операционной системой Листинг 20.2. Вывод на печать графической информации procedure TForml.ButtonlClick(Sender: TObject); Var ThePrinter: TPrinter; {Описание ссылки на объект текущего принтера} Begin ThePrinter := Printer; {Получение ссылки на объект текущего принтера} ThePrinter.Title := лПечать из Delphi'; {Строка, идентифицирующая задание в очереди заданий} ThePrinter.BeginDoc; {Открытие задания на печать} ThePrinter.Canvas.Pen.Color := clRed; ThePrinter. Canvas . Pen .-Width : = 5; {Настройка цвета и толщины обводки} ThePrinter.Canvas.Rectangle(Rect(0, 0 , ThePrinter.PageWidth, ThePrinter.PageHeight )) ; {Вывод рамки} ThePrinter.NewPage; {Создание новой страницы} ThePrinter.Canvas.Pen.Color := clBlue; ThePrinter.Canvas.Pen.Width := 5; ( {Настройка цвета и толщины обводки} ThePrinter.Canvas.Rectangle(Rect(0, 0 , ThePrinter.PageWidth, ThePrinter.PageHeight )) ; {Вывод рамки} ThePrinter.EndDoc; {Закрытие задания и отправка на принтер} end; Рамки выводятся таким образом, что охватывают всю область страницы, на которой может быть расположена информация. Функции печати стандартных компонентов Некоторые стандартные компоненты поддерживают возможность печати в своей изначальной реализации. Например, компонент RichEdit имеет метод Print, с помощью которого можно напечатать документ, редактируемый в этом компоненте. procedure Print(const Caption: string); В качестве параметра caption передается строка, которая будет идентифицировать печатаемый документ в очереди заданий на печать Windows.
506
Глава 20. Вывод информации за пределы программы. Технология СОМ
Формы также имеют возможность печати своего изображения с помощью метода Print: • procedure P r i n t ;
Размеры изображения формы на бумаге определяются свойством PrintScale класса TForm:
property PrintScale: TPrintScale; Свойство PrintScale может иметь одно трех значений: » poNone — размеры формы при печати не изменяются, результат печати непредсказуем; » poProportional — размеры напечатанной формы соответствуют размерам формы на экране; » poPrintToFit — форма растягивается, чтобы занять лист полностью. Приведенные возможности вряд ли могут быть использованы при построении качественных программных продуктов в связи с полным отсутствием каких-либо параметров автоматической печати из данных компонентов, позволяющих настроить атрибуты внешнего вида получаемых печатных документов.
20.2. Вывод информации в другие приложения. Основы технологии СОМ 20.2.1. Общие положения Отображение информации на принтере с помощью объекта TPrinter обладает высокой гибкостью, однако большинство задач печати сводится к выводу текста или таблиц. При этом, с одной стороны, в программах приходится реализовывать сложные алгоритмы форматирования текста при выводе, а с другой стороны, такие задачи дублируют широко распространенные пакеты, в которых данные возможности уже реализованы. Более тогр, вывод информации на печатающее устройство жестко определяет вид получаемых документов, на который пользователь не может повлиять. При этом с развитием компьютерной техники печатные документы становятся все менее востребованными в связи с широким использованием для обмена информацией электронной почты и факсов. В таких условиях целесообразно наделять программные продукты возможностями сохранения информации в общепринятых форматах, таких, например, как формат текстовых документов RTF, тем более, что Delphi частично поддерживает такие возможности. Однако работа с такими форматами все-таки не проста, и требует от разработчика дополнитель-
507
Часть V. Взаимодействие приложения с операционной системой
ных усилий, особенно в условиях быстрого обновления стандартов на форматы представления данных. Второй подход к повышению гибкости вывода информации поддерживается наличием в Windows технологии OLE (Object Linking and Embedding— связывание и внедрение объектов), позволяющей запускать сторонние приложения и передавать им данные, вызывая методы этих приложений. Технология OLE является частью технологии СОМ — признанного стандарта для выполнения подобных операций, и все больше программных продуктов поддерживают такие возможности. Исключением не является и группа продуктов Microsoft o f f i c e , каждое приложение которой может быть использовано из Delphi-приложений для передачи в них данных. В настоящем разделе мы кратко рассмотрим основы технологии СОМ на примере автоматического создания документов в Microsoft Word и Excel из программы, реализованной в Delphi.
20.2.2. Основы СОМ-технологии Основные понятия СОМ. СОМ-объекты СОМ — Component Object Model — Модель многокомпонентных объектов является объектно-ориентированной концепцией, описывающей правила построения классов, называемых СОМ-объектами, с целью обеспечения взаимодействия между ними вне зависимости от языка, на котором реализованы данные классы, и среды, в которой выполняются приложения, содержащие их. Объекты СОМ реализуются в специальном двоичном формате, являющимся фактически описанием класса, и не зависящем от языка разработки, и предоставляют свою функциональность (позволяют вызывать свои методы) через набор интерфейсов76. Каждый СОМ-объект реализует как минимум один интерфейс lUnknown, аналогичный рассмотренному выше интерфейсу iinterface, и выполняющий следующие действия: 1. Поддержка счетчика экземпляров объекта с помощью методов AddRef, вызываемого при создании экземпляра класса для увеличения значения счетчика, и Release, вызываемого при разрушении экземпляра для уменьшения значения счетчика; 2. Предоставление ссылки на интерфейс, заданный его уникальным идентификатором, и поддерживаемый СОМ-объектом, с помощью метода Query Inter face. 76
Речь идет об объектных интерфейсах, рассмотренных нами выше.
508
Глава 20. Вывод информации за пределы программы. Технология СОМ
Для идентификации СОМ-объектов применяются глобальные уникальные идентификаторы, представляющие собой 128-разрядные целые числа, записываемые в следующем формате, где вместо символа 'х' указывается шестнадцатеричная цифра (от 0 до F): ['{хххххххх-хххх-хххх-хххх-хххххххххххх}']
Например, главное приложение графического редактора Adobe Photoshop имеет такой идентификатор: {43191d98-5d34-4103-be42-226a55c2312a}
Вызов методов СОМ-объектов. Интерфейс IDispatch СОМ-объекты могут использоваться либо внутри приложения (такие СОМ-объекты называются внутрипроцессными), либо в виде исполняемых модулей (внешние СОМ-объекты). На рис. 20.1 представлена схема работы приложения с разными видами СОМ-объектов.
а)
Приложение-Клиент Вызов
__
СОМ
^ Результат
б)
Приложение-Клиент Вызов Результат
Рис. 20.1. Взаимодействие приложения с внутрипроцессными (а) и внешними (б) СОМ-объектами
Приложение (приложение-клиент), использующее СОМ-объект, вызывает метод, заявленный в каком-либо интерфейсе этого СОМ-объекта (объекта-сервера). Вызов производится по имени с передачей необходимых параметров, вне зависимости от типа используемого СОМ-объекта (внешний или внутрипроцессный). Связывание вызова метода с его адресом может быть выполнено двумя способами:
1. Через интерфейс IDispatch. 2. Через таблицу виртуальных методов. Таблица виртуальных методов строится для СОМ-объекта на этапе компиляции программы, которая его использует, и обладает такими достоинствами, как высокая скорость работы и отсутствие ошибок в передаче параметров, так как синтаксис вызова проверяется компилятором. Такой 509
Часть V. Взаимодействие приложения с операционной системой
вид связывания называется ранним, по аналогии с ранним связыванием вызовов методов для экземпляров обычных классов Delphi. Для раннего связывания необходимо наличие библиотеки типов, содержащей информацию о сигнатуре методов. На рис. приложение-клиент «Приложение 1» обращается к методу «Метод 2» СОМ-объекта напрямую, используя механизм раннего связывания. Для этого адрес метода должен быть внесен в таблицу виртуальных методов приложения. Второй вариант связывания — позднее связывание — осуществляется с помощью использования интерфейса IDispatch. СОМ-объекты, реализующие интерфейс IDispatch, называются серверами автоматизации. Именно серверы автоматизации и интересуют нас в контексте вывода информации за пределы программы. Заметим, что принадлежность СОМ-объекта к серверам автоматизации не запрещает возможность раннего связывания, наоборот, интерфейс IDispatch предоставляет необходимую приложению информацию о типах. Интерфейс IDispatch предназначен для использования сервера автоматизации в условиях позднего связывания и имеет несколько методов для определения информации о типах. Также в интерфейсе IDispatch заявлен метод Invoke, которому передается имя метода для вызова и список необходимых параметров. На этом методе и построен механизм использования серверов автоматизации из Delphi-программ. Механизм вызова метода объекта через метод Invoke интерфейса IDispatch, реализованного этим объектом, показан на рис. Приложение-клиент Приложение 2 обращается к методу invoke, который определяет метод («Метод 2»), запрошенный приложением, и вызывает его. Метод invoke имеет множество сложных параметров, однако он не используется напрямую разработчиком прикладного программного обеспечения. Вызовы данного метода добавляются в программу автоматически во время компиляции.
Рис. 20.2. Различные механизмы вызова методов СОМ-объекта
510
Глава 20. Вывод информации за пределы программы. Технология СОМ
Создание и использование экземпляров серверов автоматизации Для создания сервера автоматизации используется функция CfeateOleObject, описанная в модуле Comobj следующим образом: function CreateOleObject(const ClassName:
string):
IDispatch;
Функция выдает ссылку на интерфейс IDispatch объекта, зарегистрированного в реестре Windows под именем ClassName. Для определения названия класса следует изучить документацию к программному продукту, предоставляющему сервер автоматизации. Для приложения Microsoft Word таким именем является «Word.Application», а для Microsoft Excel — «Excel.Application». Аналогичные названия классов имеют и другие компоненты Microsoft O f f i c e . Если сервер автоматизации уже запущен, то ссылку на него можно получить с помощью функции GetActiveOleObject: function GetActiveOleObject(const ClassName:
string):
IDispatch;
Если при вызове метода GetActiveOleObject система не может обнаружить запущенную версию заданного сервера автоматизации, то будет возбуждена исключительная ситуация класса EOleError. Ссылки, которые возвращают функции G r e a t e O l e O b j e c t и GetActiveOleObject, следует сохранять в переменных для дальнейшего доступа к созданному или полученному объекту. Несмотря на то, что run ссылки определен как IDispatch, переменная, в которую эта ссылка сохраняется, должна иметь тип Variant. Это связано с тем, что из данной переменной будут вызываться методы сервера автоматизации, которые не описаны в интерфейсе IDispatch. 77 Var
Object: Variant; Object := CreateOleObject('Word.Application'); Использование экземпляра сервера автоматизации, то есть вызов его методов, осуществляется с помощью конструкций, обычных для вызова методов в Delphi: .(); Однако механизм, используемый для реального вызова, существенно отличается от вызова методов Delphi-классов. Название метода и список 77
Присвоение ссылки на интерфейс IDispatch корректно, так как в вариантной переменной может храниться ссылка на такой интерфейс. Тип вариантной переменной, возвращаемый функцией VarType, будет в таком случае соответствовать константе varDispatch.
511
Часть V. Взаимодействие приложения с операционной системой
его параметров запаковываются в специальную структуру, которая затем передается методу invoke СОМ-объекта через ссылку, полученную при вызове функции CreateOleObject. Метод invoke определяет, какой именно его метод должен быть вызван, выполняет его, запаковывает результат и возвращает его в вызвавшую программу. Объекты автоматизации поддерживают также и доступ к свойствам через специальным образом описанные методы. Работа со свойствами объектов автоматизации, с точки зрения разработчика прикладного программного обеспечения, аналогична использованию property-свойств объектов Delphi, хотя и отличается в корне по механизму. Для разрушения структур данных, связанных с использованием СОМобъекта в программе, следует присвоить ссылке на него значение Unassigned. Данная операция не закрывает запущенный сервер автоматизации: Var
Object: Variant; Object := CreateOleObject('Word.Application'); Object := Unassigned;
// Разрушение программных структур
20.2.3. Экспорт информации в приложения Microsoft Office Приложения, входящие в состав Microsoft o f f i c e , имеют иерархическую объектную структуру. Структура каждого приложения сложна и существенно отличается от структуры других приложений, что обусловлено различной направленностью самих приложений. Иерархическая структура объектов отражает представление данных, редактируемых в том или ином приложении. Например, структура Microsoft Word включает в себя объект верхнего'уровня Application, управляющий непосредственно приложением, и вложенные в него объекты типа Document, предоставляющие доступ к документам, открытым в данный момент. При добавлении очередного объекта Document сторонним приложением с помощью методов сервера автоматизации Microsoft Word открывает очередной документ. Аналогично объекты типа Document могут содержать в себе объекты типа Paragraph, представляющие собой ссылки на абзацы текста. Доступ к объектам различного уровня из программы-клиента, реализованной, например, на Delphi, осуществляется единообразно, через ссыл512
Глава 20. Вывод информации за пределы программы. Технология СОМ
ки на вышестоящие в иерархии объекты. Объекты одного уровня и назначения, например, объекты типа Document, объединяются в одно свойство, так называемое семейство, которое имеет название типа объектов во множественном числе. Таким образом, объекты типа Document объединяются в свойство Documents. Такие свойства являются, фактически списками, для которых можно определить количество элементов в каждом из них (свойство Count) и получить доступ к элементу с помощью функции Item, получающей в качестве параметра номер объекта в списке. Элементы нумеруются от единицы. Интересной особенностью семейств есть то, что они поддерживает те же свойства и методы, которые поддерживаются объектами, входящими в семейство. Обращение к семейству эквивалентно обращению ко всем элементам семейства по очереди. Для передачи параметров в методы объектов автоматизации и присвоения значений их свойствам используется специальный тип данных OleVariant, основное отличие которого от типа Variant состоит в его совместимости с операционной системой, которая и поддерживает технологию СОМ. Таким образом, чтобы получить доступ к документу с номером 1, открытому в данный момент в редакторе Microsoft Word, следует выполнить следующие действия: Var
0V: OLEVariant; WordApp: Variant; WordApp := GetActiveOleObject('Word.Application'); 0V := 1;
Documents.Item(0V); Заметим, что обычно такой подход не применяется, так как сканирование объектов семейства может привести к непредвиденным результатам, если во время такого сканирования пользователь, имеющий доступ к открытому окну Microsoft Word, вносит в документы какие-либо изменения. Поэтому обычно работа с объектами автоматизации Microsoft o f f i c e строится на основе жестких ссылок, возвращаемых при создании объектов документной иерархии. Объекты иерархий Microsoft office содержат в себе методы, количество которых приблизительно равно количеству действий, поддерживаемых при редактировании документов, поэтому мы будем рассматривать только их небольшую часть, относящуюся к возможностям экспорта информации из Delphi-программы. 17 Зак. 867
513
Часть V. Взаимодействие приложения с операционной системой
20.2.4. Экспорт информации в Microsoft Word Итак, для экспорта информации в Microsoft Word с использованием сервера автоматизации следует: 1. Создать экземпляр сервера автоматизации «Word.Application» (запустить Word) или получить ссылку на уже запущенный экземпляр. 2. Создать новый документ. 3. Вывести информацию в созданный документ. 4. Разрушить структуры, связанные с экземпляром сервера автоматизации в программе, а также ссылки на элементы его объектной иерархии. Запуск сервера Создание или получение ссылки на экземпляр сервера автоматизации выполняется с помощью функций CreateOleObject или GetActiveOleObject соответственно. Создание нового объекта необходимо, если сервер автоматизации еще не запущен, а получение ссылки на запущенный сервер существенно ускоряет процесс экспорта информации, так как системе не требуется запускать еще одно приложение. Таким образом, в некоторых случаях целесообразно сначала пытаться подключиться к существующему серверу, а в случае неудачи запустить собственную версию:
var WordApp:
Variant;
try
// Получаем копию Word'а, если он уже запущен WordApp := GetActiveOleObject('Word.Application'); except // Произошло исключение, значит Word не запущен try
// Пробуем запустить Word WordApp := CreateOleObject('Word.Application'); except // Произошло исключение, значит Word'а нет вообще Application.MessageBox('Microsoft Word не установлен.', 'Внимание!'
); Exit; end; end;
514
Глава 20. Вывод информации за пределы программы. Технология СОМ // Ссылка на объект автоматизации W o r d получена тем // или иным образом, значит можно начинать процесс // непосредственного вывода информации try // Вывод информации finally WordApp := UnAssigned; end;
Заметим, что если Microsoft Word не запущен а данный момент и ссылка на объект автоматизации не может быть получена методом GetActiveOleObject, то будет выброшена исключительная ситуация eOleSysError, которая прервет выполнение программы на переходе в первый блок Except. После возобновления программы произойдет обращение к функции CreateOleObject. При запуске программы не из пределов среды разработчика, пользователь не увидит какого-либо сообщения об ошибке, так как исключение обработано в защищенном блоке Try..Except. Однако отладка такой программы неудобна. В качестве решения проблемы можно отключить обработку ошибок типа eOleSysError в диалоге Tools->Debugger Options (см. рис. 20.3) на закладке Language Exceptions. Для этого следует нажать на кнопку Add данного диалога и в появившейся строке ввода набрать название исключения («eOleSysError»). После этого исключение eOleSysError появится в списке Exception Types to ignore («игнорировать следующие типы исключений»). Разработчик должен проконтролировать наличие признака «включено» напротив нужного
Senaal | Event Log Language Exceptions j OS Exceptions j г Exception Types to Ignofe ~ У; VCL EAbort Exceptions Щ Indy EIDComClosedGracefully Exception & Microsoft DAO Exceptions !«S VisBrokw Internal Exceptions [*] COHBA System Exceptions [J CDRBA User Exceptions D EOteSysEtroi
Stop on Delphi Exceptions
!