М.В. Сухарев
основы
Профессиональный подход
иТ Наука и Техника, Санкт-Петербург 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. Простейшая программа, подключающая модуль '• . " • . • ' . . . " ' • . : . . ' .
•
....<. •'.'• '•
:
<.' ; • . ; • ' . : "
:'.
••"•.•'.:...
•<
.
'...:.
:
•
•',':.••
:'•'• •
•
:
i. ";W•..,
:
Program Program!; {Заголовок программы, название программы — Programl} Uses {Раздел подключения модулей} Unitl; {Подключение модуля Unitl} Begin {Начало раздела описания логики программы} End. {Конец раздела описания логики программы}
1.4. Проект Важным нововведением в Delphi является понятие проекта — описания частей, на которые разбит исходный текст программного продукта. Кроме файлов с исходными текстами, в проект могут входить файлы ресурсов, подключаемых к программе (например, изображения или тексты). Проект предназначен для повышения удобства разработки программных продуктов, особенно крупных, так как среда разработчика имеет возможность контролировать элементы проекта, например, для поддержания актуальности откомпилированных частей программы. Описание проекта находится в основной части программы, генерируется средой автоматически, и, в большинстве случаев, не требует вмешательства программиста. Основная часть программы находится в файле с расширением dpr и содержит перечисление всех модулей, входящих в проект, а также инициализационную часть программы и некоторую вспомогательную информацию для более тонкой настройки среды под конкретный проект. 4
Здесь и далее в этой главе мы приводим листинги программ, которые не должны компилироваться в Delphi, а просто служат примером использования тех или иных конструкций языка. 23
Структуры данных в Delphi
2.1. Стандартные типы данных 2.1.1. Числовые типы Некоторые типы данных, стандартные для языка Pascal, немного изменились в сторону расширения диапазона возможных значений и, соответственно, увеличения занимаемого места. Это связано с тем, что создаваемые в Delphi приложения ориентированы на работу в операционной системе Windows, а не DOS, что накладывает на них некоторые требования. Программы, выполняемые в Windows, широко используют системные функции операционной системы, передавая им множество параметров. Таким образом, представление информации в Delphi должно соответствовать представлению аналогичной информации в Windows. Рассмотрим сравнительные таблицы типов данных в Pascal и Delphi (см. табл. 2.1 и 2.2), в которых указаны сведения о типах данных в Pascal и Delphi. В нижних частях таблиц приводятся сведения о новых типах данных, которых не было в языке Pascal. Из сравнительных таблиц видно, что среди целочисленных типов данных не произошло существенных изменений, за исключением увеличения диапазона типа Integer. Это связано с частым использованием данного типа при передаче параметров системным функциям Windows. Также добавлены новые типы Cardinal, Int64 и Longword для более удобной организации структур данных в программе. Новый целочисленный тип данных Smallint соответствует типу Integer языка Pascal и добавлен в Delphi для упрощения переноса фрагментов текста из старых программ, работающих в операционной системе DOS, которые используют переменные типа integer и чувствительны к возможному изменению размеров этих переменных. При переносе таких фрагментов ссылки на тип integer следует заменять на Smallint. 24
Глава 2. Структуры данных в Delphi
Среди вещественных типов данных изменены возможности типов Real, Extended и Comp, а также, для совместимости с программами, написанными на Turbo Pascal, добавлен новый тип Real48, совпадающий с типом Real языка Pascal. Особого внимания заслуживает новый вещественный тип данных Currency, предназначенный для использования в финансовых вычислениях и призванный исключить ошибки искажения значений, присущие переменным с плавающей точкой. Переменные типа Currency на самом деле хранятся не как вещественные переменные, а как целочисленные переменные типа Int64, а при использовании их в выражениях автоматически делятся на 10000. Таким образом, значения переменных типа Currency являются числами не с плавающей точкой, а с фиксированной. Таблица 2.1
Сравнительная таблица целочисленных типов данных в Pascal и Delphi Pascal Название типа
Delphi Память, байт
Диапазон
Shortint
-128.. 127
1
Integer
-32768..32767
2
Longint
-2147483648..2147483647
4
так же так же
Byte
0..255
1
Word
0..65535
2
Память, байт
Диапазон также
.
-2147483648. .2147483647
4
так же
Cardinal
0..4294967295
4
Smallint
-32768..32767
2
63
Int64 Longword
63
-2 ..2 -1
8
0..4294967295
4
Таблица 2.2
Сравнительная таблица вещественных типов данных в Pascal и Delphi Pascal
Название типа
Delphi
ra Ю
Диапазон
A
-
В
a
a -•fr 1.1 о о
С
£8
Real
±2.9x10-39..±1.7x1038
6
11-12
Single
±1.5x10-45.,±3.4x1038
4
7-8
8
15-16
Double Extended Comp Real48 Currency
324
308
±5.0x10- ..±1.7x10 4932
±3.4x10"
4932
..±1.1x10
l8
18
-9.2x10 ..9.2x10
10
8
19-20 19-20
n
0
Диапазон
£ v
±5.0x10-324..±1.7x10M8
a л-*
| a
0 0
С
1- Ш
8
15-16
10
19-20
также так же 4851
±3.6x10-
..±1.1x104S32
63
-2 +1 .. 263-1
8
19-20
2.9x10-39. .1.7x1038
6
11-12
-922337203685477.5808 .. 922337203685477.5807
8
19-20
25
Часть I. Структурное программирование в Delphi
2.1.2. Строковые типы данных В языке программирования Pascal для работы со строками был предусмотрен тип string, представляющий собой последовательность (массив) символов, размером до 255-ти байт, в начале которой находилось однобайтовое число, содержащее длину строки. С другой стороны, в языке С, на который, в основном, ориентируется среда Windows, строки хранятся в так называемом ASCIIZ-формате. Каждая строка в формате ASCIIZ представляет собой указатель на массив символов, который заканчивается нулевым символом. Такое представление строки имеет свои преимущества — например, практически неограниченная длина ASCIIZ-строк, и свои недостатки — связанные с неудобством использования указателей (ссылочных переменных). В Delphi предлагается компромисс между двумя подходами к хранению строк. Внутреннее представление строки реализовано динамически, в ASCIIZ-формате, тогда как с точки зрения программиста строка является массивом символов известной длины (символы в массиве пронумерованы от единицы). В связи с этим отменена возможность доступа к нулевому элементу строки с целью получения ее длины. Длину строки в Delphi можно определить с помощью функции Length: Length(<Строка>) : Integer; Установить длину строки можно с помощью процедуры SetLength: SetLength(<Строка>, <Новая длина>); Еще одной особенностью работы в среде Windows является возможность использования кодировки символов Unicode, в которой каждый символ представляется не одним, а двумя байтами. Кодировка Unicode предназначена для индексации символов нескольких алфавитов одновременно. Строковые типы данных, доступные в Delphi, описаны в табл. 2.3. Тип ShortString введен для обратной совместимости с Pascal под DOS и не рекомендуется для использования. Переменной, описанной с использованием устаревшего типа string, назначается тип ShortString, если при компиляции директива Н находится в выключенном состоянии: {$н-}, или Ansistring, если при компиляции директива н находится во включенном состоянии: {$н+}. Строковые типы данных в Delphi Название типа
Таблица 2.3
Максимальная длина, символов
Занимаемая память
Размер символа, байт
1
ShortString
255
От 2 до 256 байт
AnsiString
_2з'
От 4 байт до 2 Гб
1
WideString
~230
От 4 байт до 2 Гб
2
26
Глава 2. Структуры данных в Delphi
Для большинства случаев (не связанных с использованием кодировки Unicode) для описания строковых переменных рекомендуется использовать тип A n s i S t r i n g .
2.1.3. Другие стандартные типы данных Работа с другими типами данных, предусмотренными в Delphi, не отличается от использования аналогичных типов данных в Pascal. Например, вместо одного логического типа данных в Pascal, в Delphi существует целых четыре: Boolean, ByteBool, WordBool и LongBool. Такое разнообразие типов предусмотрено для обеспечения совместимости с другими языками программирования и средами выполнения. Как видно из. их названий, они отличаются друг от друга только размером, и без особой необходимости нет смысла в использовании для описания логических переменных какого-либо другого типа вместо обычного Boolean.
2.2. Описание структур данных 2.2.1. Описание и использование переменных Описание переменных Описание переменных производится в разделе описания переменных (после ключевого слова V a r ) основной части программы, интерфейсных или описательных частях модулей в следующем виде: Var
{Начало раздела описания переменных} <Название переменной 1>: <Тип данных>; {Описание переменной} <Название переменной N>: <Тип данных>; {Описание переменной}
При необходимости допускается описание нескольких переменных одного типа в одной строке. В этом случае названия переменных указываются через запятую: <Название переменной 1>, . .., <Название переменной М>: <Тип данных>;
27
Часть I. Структурное программирование в Delphi
Оператор присваивания Для присвоения значения переменной используется оператор присваивания: <Название переменной> := <3начение>; Пример использования оператора присваивания приведен в листинге 2.1. Листинг 2.1. Использование оператора присваивания Program UsingVariables ; Var {Начало раздела описания переменных} Int: Integer; {Описание переменной типа Integer с именем I n t } Begin
Int := 56; End.
{Присвоение переменной Int значения 56}
2.2.2. Описание констант Константами называются идентификаторы, вместо которых на этапе компиляции программы подставляются заданные при описании этих констант значения. Соответственно, использование констант не сказывается на непосредственной работе приложения, зато существенно упрощает процесс программирования, делая текст программы более наглядным. Раздел описания констант начинается ключевым словом Const, a сами описания выглядят следующим образом: Const
<Название константы> = Оначение константы>; Приведем пример описания констант разных типов: Const DeveloperName = 'Michael Suharev'; {Описание строковой константы} DelphiVersion = 7; {Описание целочисленной константы} IncludeOOP = True; {Описание логической к о н с т а н т ы (типа B o o l e a n ) }
Как и в Pascal под DOS, в Delphi существует возможность описания типизированной константы, которая на самом деле будет являться переменной, причем ее значение будет установлено уже на старте программы. Описание типизированных констант выглядит следующим образом: Const <Название константы>: <Тип> = <3начение константы>; 28
Глава 2. Структуры данных в Delphi Приведем пример описания типизированных констант: Const DelphiVersi.on: Byte = 7 ; {Описание целочисленной типизированной константы, значение которой может быть изменено .в процессе выполнения программы. Начальное значение константы 7} IncludeOOP: Boolean = True; (Описание логической константы (типа Boolean), значение которой может быть изменено в процессе выполнения программы. Начальное значение константы True}
2.2.3. Описание нестандартных типов данных Delphi, как и Pascal, позволяет описывать дополнительные типы данных («нестандартные»), отличные от встроенных (стандартных). При описании могут использоваться как стандартные типы данных, так и нестандартные, то есть описанные ранее в основной части программы или в модуле. После создания нестандартные типы данных могут использоваться для описания переменных на их основе. Само создание нестандартных типов производится в разделах описания типов (после ключевого слова Туре) основной части программы, интерфейсных или описательных частях модулей в следующем виде: Туре {Начало раздела описания типов данных} <Название типа 1> = <0писание типа данных>; {Описание типа} <Название типа N> = <0писание типа данных>; {Описание типа} В качестве примера можно привести описание нестандартного типа данных Mylnteger, эквивалентного стандартному типу Integer. Program UsingCustomTypesl; Type . {Начдло раздела описания типов данных} Mylnteger = Integer; {Описание типа M y l n t e g e r } Begin End.
Аналогичным образом можно описать тип данных Otherlnteger, эквивалентный нестандартному типу Mylnteger. Более сложные примеры мы рассмотрим при обсуждении сложных типов данных. 29
Часть I. Структурное программирование в Delphi Program UsingCustomTypes2; Туре Mylnteger = Integer; Otherlnteger = Mylnteger; Begin
{Начало раздела описания типов данных} {Описание типа Mylnteger} {Описание типа Otherlnteger}
End.
2.3. Сложные типы данных Сложные типы данных в Delphi представлены массивами (в том числе многомерными), записями, интервальными и перечислимыми типами, а также множествами. Средства работы с переменными этих типов не отличаются от аналогичных, используемых в Pascal под DOS, поэтому мы не будем подробно останавливаться на этом вопросе.
2.3.1.
Интервальные типы данных
Интервальные типы данных (типы-диапазоны) представляют собой подмножества возможных значений стандартных типов данных integer и Char и применяются для более удобного представления информации в программе. Описание интервального типа данных выглядит следующим образом: <Название переменной> = <начальное знач.>..<конечное. знач.> Использование интервального типа данных аналогично использованию любого другого типа данных. Рассмотрим пример, в котором описывается переменная интервального типа, значениями которой могут быть строчные латинские символы от 'с' до 'у' (см. листинг 2.2).
Листинг 2.2. Использование интервального типа Program UsingSubranges; Type TInterval = ' с ' . . ' у ' ; {Описание интервального типа — подмножества типа Char; допустимые значения переменных данного типа находятся в диапазоне от символа хс' до символа 'у' } Var
Interval: TInterval; {Описание переменной интервального типа TInterval} '!
30
Глава 2. Структуры данных в Delphi Begin Interval := ' е ' ; {Присвоение переменной Interval значения
'е'}
End.
Если переменной интервального типа данных присваивается значение, не соответствующее диапазону данного типа, то программа прерывается и выдается ошибка времени выполнения программы (ошибка проверки диапазона возможных значений). Данное свойство интервального типа используется для ограничения возможных значений переменной, так как в некоторых случаях удобнее прекратить программу с ошибкой, чем продолжать ее выполнение с некорректным значением некоторой переменной.
2.3.2. Перечислимые типы данных Перечислимый тип данных представляет собой некоторый набор констант. Сответственно переменная данного типа может принимать в качестве своего значения только одну из них. Константы задаются именами, их значения неизвестны и определяются компилятором во время построения программы. Описание перечислимых типов имеет следующий вид: <Тип> = (<Имя константы 1>,
. . - , <Имя константы п>) ;
Перечислимые типы используются для описания множества значений какого-либо свойства, например, цвета некоторого объекта. Описание и использование такого свойства может выглядеть так, как показано в листинге 2.3. Листинг 2.3. Использование перечислимого типа Program UsingEnumerateds; Type , TColor = (Black, White, Red); {Описание перечислимого типа T C o l o r s . Диапазоном значений переменных этого типа являются три константы — Black, W h i t e , Red; значения этих констант не известны, но их можно использовать по именам} Var Color: TColor; {Описание перечислимого типа данных} Begin Color := White; {Присвоение переменной Color значение White} End. -f
31
Часть I. Структурное программирование в Delphi
Перечислимые типы данных используются, в основном, для повышения читабельности программы и привлечения внимания к возможным значениям какого-либо параметра программы. Обратим внимание на то, что в стандартных компонентах и подпрограммах Delphi крайне широко используются перечислимые типы.
2.3.3. Множества Типы данных «множества» аналогичны перечислимым и интервальным типам данных, однако переменные множественных типов могут иметь несколько значений из описанного перечисления в каждый момент времени работы программы, причем каждое значение не может присутствовать в множестве дважды в одно и то же время. Описание переменной множественного типа выглядит следующим образом: <Переменная>:
Set Of <Перечислимый тип>;
Аналогично можно описать множество на основе интервального типа: <Переменная>:
Set Of <Интервальный тип>;
Для присвоения значения переменной множественного типа используется специальный оператор — конструктор множества [ ]: <Переменная> :=
[Оначение 1>,
...,
Оначение п>] ;
В качестве Значений может быть использовано любое значение из множества, на основе которого создан данный тип. Рассмотрим пример описания и использования переменной-множества, основанной на перечислимом типе данных (см. листинг 2.4). Листинг 2.4. Пример описания и использования переменной-множества Program UsingSets; Type TColors = Set Of (Black, White, R e d ) ; {описание множественного типа данных T C o l o r s . В качестве значений переменных данного типа в любой момент времени выполнения программы может быть любое сочетание констант Black, W h i t e и Red} Var Colors: TColors; {описание переменной множественного типа данных T C o l o r s }
32
Глава 2. Структуры данных в Delphi Begin Colors := [Black, Red]; {присвоение переменной C o l o r s множества, с о с т о я щ е г о из двух - элементов — Black и Red} Colors := [] ; {присвоение переменной C o l o r s п у с т о г о множества} Colors := [White]; {присвоение переменной C o l o r s множества, состоящего из одного элемента — W h i t e } End.
При работе с множествами используются следующие понятия: » Объединением двух множеств называется множество, содержащее элементы, как первого, так и второго множеств. Для объединения множеств используется оператор «+». Например, после выполнения команды Colors := [Black] + [Red], в переменной Colors будет содержаться множество [Black, Red]. » Пересечением двух множеств называется множество, содержащее элементы, включенные в оба множества. Для пересечения множеств используется оператор «*». Например, после выполнения команды Colors := [Black, Red] * [Black, W h i t e ] , в переменной Colors будет содержаться множество из одного элемента Black. » Разностью двух множеств называется множество, содержащее элементы первого множества (уменьшаемого), не содержащиеся во втором множестве. Для определения разности множеств используется оператор «-». Например, после выполнения команды Colors : = [Black, Red] - [Black, W h i t e ] , в переменной Colors будет содержаться множество из одного элемента Red. » Пустым множеством называется множество, не содержащее элементов. Для присвоения переменной множественного типа пустого множества может использоваться обычный конструктор множества. Например, после выполнения команды Colors := [] в переменной Colors не будет содержаться ни одного элемента. Множество станет пустым, если вычесть из него множество, содержащее все возможные элементы. Например, после выполнения команды Colors := Colors - [Black, W h i t e , Red] множество Colors будет пустым вне зависимости от своего начального состояния.
2 Зак. 867
33
Часть I. Структурное программирование в Delphi
2.3.4. Записи 5
Запись является типом данных и описывается в разделе описания типов данных Туре. После описания типа данных его можно использовать для создания переменных этого типа в обычном порядке. Описание записей как типов данных имеет следующий вид: Туре <Имя >гипа> = Record <Имя
<Имя end;
{начало раздела описания типов данных} {заголовок описания записи) переменной 1>: <Тип>; {описание "поля» записи} переменной п>: <Тип>; {описание "'поля» записи} {окончание описания типа}
Таким образом, запись, как переменная, состоит из набора разнородных переменных, называемых полями записи, каждая из которых имеет свое уникальное имя в пределах записи. В качестве Типов переменных, описываемых внутри записи, могут использоваться любые типы данных, встроенные в 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), с помощью которой можно отделить код типа данных от дополнительных признаков: <Переменная типа V a r i a n t > 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 является массивом целочисленных значений, каждая пара которых определяет пределы изменения индексов. Количество измерений массива, соответственно, определяется количеством пар значений в этом массиве. Вместо массива, заданного переменной, можно использовать конструкцию следующего вида: [<А1, В1>, < А 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 <Команда 1>; <Команда N>; 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
•
<Команда 1>; <Команда N>; Until <Условие>; Зацикленный фрагмент программы выполняется оператором Repeat. .Until до тех пор, пока не станет истинным Условие. Закончим обсуждение условных операторов цикла описанием двух фрагментов программы, содержащих бесконечные циклы. Первый вариант бесконечного цикла реализуем с помощью оператора while: While 2=2 Do; {Так как условие 2=2 истинно и не изменится в процессе выполнения программы, то зацикленный фрагмент программы (пустой оператор ;) будет выполняться бесконечно} И аналогичный пример с использованием оператора Repeat. .Until: Repeat
Until 2<>2;
{Так как условие 2<>2 ложно и не изменится в процессе выполнения программы, то зацикленный фрагмент- программы (команды в этом фрагменте отсутствуют) будет выполняться бесконечно}
Заметим, что использование бесконечных циклов при программировании под операционную систему 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 <Имя функции>:
<Тип возвращаемого значения>;
Для указания компилятору, какое именно значение должна возвращать функция, в разделе описания логики функции должно использоваться присвоение следующего вида: <Имя функции> := <3начение>;
В Delphi предусмотрен еще одцн механизм возврата значения функции в вызывающую подпрограмму — ключевое слово Result: Result
:= <3начение>;
Приведем пример модуля, в котором описана одна процедура и одна функция (листинг 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 <Имя> (<Параметр 1>: <Тип параметра 1>; . . . ; <Параметр 2>: <Тип параметра 2>); 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 ;
Как и переменные любого другого типа, процедурные переменные после описания не определены. Для инициализации процедурной переменной необходимо присвоить ей адрес подпрограммы, доступной текущему фрагменту программы, в котором выполняется присвоение. Для получения адреса подпрограммы используется ее имя: <Название перемённой> = <Название подпрограммы>;
После инициализации переменная процедурного типа используется так же, как и вызов обычной подпрограммы, то есть вместо названия подпрограммы указывается название процедурной переменной: <Название переменной>(<Список параметров?») ;
Рассмотрим пример описания процедурного типа данных, переменной на его основе, ее инициализации и использования (листинг 4.10). Листинг 4.10. Пример использования процедурного типа данных Unit Procedural; Interface Type fMathFunct = Function (Arg: Double): Double; {Описание процедурного типа fMathFunct — указателя на функцию с одним вещественным аргументом, возвращающую вещественное значение} Implementation Function S i n 2 ( X : D o u b l e ) : Double; begin Result := S i n ( X ) * S i n ( X ) ; end; {Описание функции Sin2, совместимой с процедурным типом f M a t h F u n c t , и построенной на основе стандартной функции S i n } Procedure UseProcedural; {Описание процедуры, использующей процедурные типы}
80
Вопросы с ответами для .повторения по части
Var F: fMathFunct; {Описание процедурной переменной F} Res: Double; begin F := Sin2; {Присвоение процедурной переменной ссылки на функцию S i n 2 } F(l); {Вызов функции Sin2 через процедурную переменную F}
Res := F ( 3 ) ; end; End. Для описания процедурного типа, который указывает не просто на подпрограмму, а на метод экземпляра класса, после описания процедурного типа используется конструкция Of Object, например:
Var ProcTypel = P r o c e d u r e ( S : A n s i S t r i n g )
Of Object;
На использовании ссылок на методы объектов построена система сообщений компонентов Delphi.
Вопросы с ответами для повторения по части I Опишите структуру исходных текстов программы на языке Object Pascal в Delphi Ответ: программа на Object Pascal состоит из основной части и нескольких модулей (библиотек), подключаемых на этапе компиляции к основной части программы. Наличие этих модулей не обязательно. Основная часть программы состоит из одного или нескольких разделов, каждый из которых начинается с определенного ключевого слова (Program, uses, const, Label, Type и Var). Если в существовании раздела нет необходимости, то ключевое слово, открывающее его, не указывается. Основная часть программы содержится в отдельном файле и имеет расширение dpr и ее содержание, в большинстве случаев, не требует вмешательства программиста. Создание и обновление основной части программы берет на себя среда разработки. Исполняемый ехе-файл, получаемый в результате компиляции программы, имеет имя, указанное в заголовке основной части программы. Модуль (библиотека), так же, как и основная часть программы, содержится в отдельном файле и состоит из двух основных секций — интерфейсной (начинается ключевым словом interface), в которой описываются типы данных, константы и переменные, доступные внешним частям программы, и описательной (начинается ключевым словом implementation), в которой описываются идентификаторы, доступные только этому модулю. Еще две секции необязательны,
81
Часть I. Структурное программирование в Delphi и включаются в модуль для выполнения инициализационных (initialization) и деинициализационных (f inalization) действий. Подключение модулей к основной части программы или к другим модулям производится в разделе подключения модулей (Uses) по имени, указанном в заголовке модуля после ключевого слова Unit. Приведите пример описания констант в программе на Object Pascal Ответ: Константы — это идентификаторы, вместо которых на этапе компиляции программы подставляются реальные значения. Для описания констант предназначена секция, которая начинается ключевым словом Const.
Const MyName = 'Michael Suharev'; {Описание строковой константы} {Описание числовой константы}
MyAge = 25;
Допустимо описание константы с указанием ее типа. В этом случае константа рассматривается компилятором как переменная, то есть возможно изменение ее значения в процессе выполнения программы. Типизированные константы могут использоваться вместо переменных в случаях, когда в программе необходима инициализация переменной в момент ее описания. Const ProcessorType:
Integer = 4; {Описание целочисленной константы с начальным значением 4} X
Приведите пример описания переменных Ответ: Переменные — это идентификаторы, вместо которых на этапе компиляции подставляются адреса хранимой в памяти информации, доступной для чтения или записи. Важным свойством переменной является ее тип, указывающий на то, каким образом следует работать с данной переменной. Для описания переменных предназначена секция, которая начинается ключевым словом Var.
Var MyName: A n s i S t r i n g ; MyAge:
Shortlnt;
{Описание строковой переменной типа A n s i S t r i n g } {Описание числовой целочисленной переменной типа S h o r t l n t } i
Приведите пример описания пользовательских типов данных Ответ: Для повышения читабельности программ и упрощения изменений, вносимых в исходные тексты, предусмотрено использование нестандартных типов данных (их еще называют пользовательскими). Описание пользо-
82
Вопросы с ответами для повторения по части I вательских типов данных производится в секции, которая начинается ключевым словом Туре. Также в этой секции описываются записи, как типы данных, и классы. Туре . Mylnteger: Integer;
{Описание типа данных, эквивалентного типу Integer} THuman = Record {Описание типа данных Thuman, представляющего собой запись} MyName: A-nsiString; {Поле записи типа AnsiString} MyAge:\ Shortlnt; {Поле записи типа Shortlnt} end;
Приведите пример описания переменных интервального типа, перечислимого типа и множеств Ответ: Интервальные типы данных (типы-диапазоны) представляют собой подмножества возможных значений стандартных типов данных integer и Char. Применяются они для более удобного представления информации в программе. Var Interval:
' a ' . . ' z ' ; {Описание переменной, значениями которой могут я в л я т ь с я латинские символы от а до z}
Перечислимый тип данных представляет собой некоторый набор констант, одна из которых является значением переменной данного типа. Константы задаются именами, их значения неизвестны и определяются компилятором во время построения программы. Var Color:
(Black, W h i t e , R e d ) ; (Описание переменной, значениями которой могут я в л я т ь с я значения, скрытые под идентификаторами Black, W h i t e и Red}
Множества аналогичны перечислимым и интервальным типам данных, однако переменные множественных типов могут иметь несколько значений из описанного перечисления. Причем каждое значение не может присутствовать в множестве дважды в одно и то же время. Var ColorSet: Set Of
(Black, White, Red); (Описание переменной, значениями которой могут являться значения, скрытые под идентификаторами Black, White и Red в любом сочетании}
83
Часть I. Структурное программирование в Delphi Расскажите о массивах в Object Pascal Ответ: Массив — поименованная область памяти, доступ к отдельным частям которой осуществляется по общему имени и индексу соответствующей части. Все части (элементы) массива имеют один и тот же тип, причем любой из стандартных или нестандартных, описанных к моменту объявления массива. В частности, элементы массива также могут являться массивами. В этом случае мы имеем дело с многомерными массивами. Описание массива производится в разделе описания типов данных (после ключевого слова Туре) или непосредственно при описании переменной такого типа. Туре I n t A r r a y = A r r a y [ 1 . .100] Of Char; (Описание типа данных — массива из 100 элементов символьного типа} Var N u m b e r s : A r r a y [ 1 . . 2 0 ] Of Integer; (Описание переменной-массива из 20-ти целочисленных элементов} Допускается описание массивов без указания размерностей и, соответственно, пределов изменения индексов: Var I n t A r r a y : A r r a y Of Integer; (Описание массива целочисленных элементов без указания размерности}
Такие массивы являются динамическими и изначально имеют нулевую длину. Установка размерности и определения ее во время выполнения программы производится так же, как и для строк, с помощью функций SetLength и Length соответственно. Элементы в данном случае нумеруются от нуля. Для определения возможных значений индексов в динамических массивах используются функции Low (возвращает минимальный индекс заданного массива) и High (наибольший индекс). Расскажите об использовании вариантных переменных в Delphi Ответ: Вариантные переменные не имеют конкретного типа данных на этапе компиляции, а в процессе выполнения программы могут хранить данные разных типов. Описываются такие переменные в обычном порядке с типом данных Variant. Во время выполнения программы в вариантных переменных могут храниться данные любого типа, за исключением: » структурных: записей и статических массивов, однако поддерживаются специальные вариантные массивы;
84
Вопросы с ответами для повторения по части I » ссылочных: типизированных и нетипизированных указателей, а также указателей на экземпляры классов; » множественных типов данных. Опишите преимущества и недостатки вариантных переменных по сравнению с обычными типами данных Ответ: Преимуществом использования вариантных переменных является удобство работы, которое проявляется в случаях, когда на этапе разработки программы неизвестны типы некоторых переменных. Еще одним плюсом является множество вспомогательных операций, выполняемых системой с вариантными переменными, например, автоматическое приведение типов. Недостатками вариантов являются больший объем к занимаемой памяти и медленное выполнение операций с участием таких переменных. В качестве еще одного недостатка можно назвать отсутствие во время компиляции проверки.на совместимость типов, что может вызывать ошибки времени выполнения программы. Причем такие ошибки носят динамический характер — они могут проявляться редко и при трудно определяемых условиях, что еще больше усугубляется оптимизатором компиляции Delphi. J '. ' ; ' - - , ' ' Расскажите об использовании вариантных массивов Ответ: Вариантный массив — это один из видов вариантной переменной, информация в которой представляется в виде набора элементов и имеет индексный механизм доступа. Для создания вариантного массива используется функция-конструктор VarArrayCreate, в которой задается размерность массива, интервалы индексации и тип элементов. Элементы массива могут иметь любой вариантный тип, в том числе и являться вариантными массивами. Таким образом, в Delphi возможно построение многомерных массивов непрямоугольной формы и даже, неоднородных массивов, элементы которых имеют разные типы данных. Расскажите об использовании процедурных переменных Ответ: Процедурные переменные — это переменные, являющиеся ссылками на подпрограммы. . .•
Описание процедурного типа определяет: * процедурой или функцией является подпрограмма, на которую указывает переменная; » является ли подпрограмма, на которую указывает ссылка, методом объекта; » сколько параметров должно быть указано при вызове подпрограммы из процедурной переменной, и какого они типа; » какое значение возвращает подпрограмма, если она является функцией.
85
Часть I. Структурное программирование в Delphi
Соответственно, процедурной переменной можно присвоить ссылку только на ту подпрограмму, которая соответствует типу переменной по всем этим параметрам. Назовите структурные операторы, применяемые в Object Pascal, и их особенности Ответ. В Object Pascal применяются следующие структурные операторы (операторы, управляющие структурой программы): * условный оператор if. .Then. .Else (возможно использование сокращенной формы if. .Then), предназначенный для выполнения фрагмента программы при истинности условия, или другого фрагмента программы в противном случае; « условный оператор множественного выбора Case. .Of. .Else (с сокращенной формой без секции Else), предназначенный для выполнения фрагмента программы из заданного набора при истинности одного из условий из заданного набора условий, или какого-либо фрагмента программы в случае ложности всех условий набора. Условиями в данном случае совпадение значения заданной в заголовке переменной с одним из значений заданного списка; • оператор зацикливания со счетчиком циклов For. . То. . Do (с декрементной модификацией For. .DownTo. .Do), предназначенный для многократного повторения фрагмента программы, расположенного после ключевого слова DO; » условный оператор зацикливания с предусловием while. . Do, предназначенный для многократного повторения фрагмента программы, указанного после ключевого слова DO, пока является истинным условие, указанное в заголовке оператора; » условный оператор зацикливания с постусловием Repeat. .Until; предназначенный для многократного повторения фрагмента программы, пока не станет истинным условие, указанное после ключевого слова until; В качестве фрагментов программы, зацикливаемых при использовании операторов зацикливания, или выполняемых при истинности условий в условных операторах и условных операторах зацикливания, может применяться одна команда языка или вызов одной подпрограммы. Для выполнения нескольких команд применяются составные операторы — набор команд, заключенных в ключевые слова begin, .end. Для досрочного прерывания зацикленного фрагмента программы используется оператор break. В этом случае управление передается команде, следующей за прерванным оператором зацикливания. Расскажите об использовании подпрограмм в Object Pascal и их разновидностях Ответ: Подпрограмма — это логически законченная часть программы, описываемая отдельно от раздела описания логики программы и вызываемая по имени, уникальному в некоторой области видимости. Подпрограммы, так же как и программы, имеют сложную структуру, включающую в себя разде86
Вопросы с ответами для повторения по части I лы описания переменных, констант, типов и так далее (все они называются локальными, так как их область видимости ограничена процедурой). Подпрограммы в Object Pascal, представлены процедурами и функциями. Процедура является обычной подпрограммой, то есть фрагментом программы, имеющим параметры и выполняющим некоторый набор команд языка. Функция, в отличие от процедуры, может возвращать значение (результат работы) в подпрограмму, вызвавшую ее. Опишите правила (синтаксис) описания параметров в подпрограммах Ответ: Описание параметров подпрограмм происходит в их заголовках после имени и имеет следующий вид: Procedure <Имя> (<Параметр 1>: <Тип параметра 1>; <Параметр 2>: <Тип параметра 2>);
. . .;
или для функций: Function <Имя> (<Список параметров>): <Тип значения>; Расскажите об использовании параметров по умолчанию Ответ: В Object Pascal предусмотрена возможность использования параметров по умолчанию. Такие параметры находятся в конце списка параметров и их описания имеют следующий вид: <Параметр>: <Тип параметра> = Оначение по умолчанию> Подпрограммы, использующие параметры по умолчанию вызываются либо с полным набором параметров, либо набором обязательных параметров и одним или несколькими параметрами, имеющими значения по умолчанию. Для того, чтобы передать некоторый.параметр, задаваемый умолчанием, следует передать все параметры, находящиеся в списке параметров заголовка подпрограммы перед ним. Расскажите -о различных механизмах передачи параметров в подпрограммы Delphi Ответ: Параметры в подпрограммы могут передаваться по значению и по ссылке. В первом случае, с точки зрения подпрограммы, параметр является ее локальной переменной, и его значение может изменяться, не влияя на переменную, заданную в качестве параметра при вызове этой подпрограммы. В качестве таких параметров могут также передаваться не переменные, а константы. Если изменение параметра внутри подпрограммы не требуется, то можно указать перед параметром модификатор Const. Таким образом может быть выполнена оптимизация вызова подпрограммы при использовании строковых параметров и параметров, имеющих сложные типы данных. Второй механизм передачи параметров в подпрограммы — передача по ссылке, когда в подпрограмму передается не значение параметра, а ад87
Часть I. Структурное программирование в Delphi pec переменной, заданной в качестве параметра. В данном случае, с точки зрения подпрограммы, параметр является глобальной переменной, и изменение значения параметра приводит к изменению значения переменной, заданной в качестве параметра. Для описания параметров, передаваемых по ссылке, указывается: » модификатор var, если значение параметра может использоваться в подпрограмме и для чтения, и для изменения; * модификатор Out, если параметр предназначен только для изменения. Если модификатор параметра не использован, то параметр передается по значению, и называется параметр-значение. При указании модификатора Const параметр называют параметром-константой, при использовании модификатора Var — параметром-переменной, при использовании модификатора out — параметром для заполнения. Объясните, что такое перегружаемые подпрограммы Ответ: В Delphi существует возможность описания нескольких подпрограмм с одинаковыми именами в одной и той же области видимости. Такие подпрограммы называются перегружаемыми. Для того, чтобы компилятор мог выбрать правильную подпрограмму из нескольких перегруженных, они должны отличаться так называемой сигнатурой — последовательностью типов данных в списке параметров. Перегружаемые подпрограммы должны быть помечены ключевым словом overload.
88
ЧАСТЬ II
ОБЪЕКТНООРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В DELPHI
ОСНОВНЫЕ МЕХАНИЗМЫ И ПОЛОЖЕНИЯ ОБЪЕКТНООРИЕНТИРОВАННОГО ПРОГРАММИРОВАНИЯ ОСОБЕННОСТИ АРХИТЕКТУРЫ ПРОГРАММЫ В ОС WINDOWS 7
ОБЪЕКТНО-ОРИЕНТИРОВАННЫЙ ПОДХОД К ОБРАБОТКЕ ОШИБОК ОБЪЕКТНО-ОРИЕНТИРОВАННЫЙ ПОДХОД К ХРАНЕНИЮ ИНФОРМАЦИИ
Т««£л 9
W
ОБЪЕКТНО-ОРИЕНТИРОВАННЫЙ ПОДХОД К ВВОДУ/ВЫВОДУ ИНФОРМАЦИИ ИСПОЛЬЗОВАНИЕ ГРАФИЧЕСКОЙ ИНФОРМАЦИИ В DELPHI
ОСНОВЫ DELPHI. Профессиональный подход
Основные механизмы и положения объектноориентированного программирования
Объектно-ориентированное программирование является стандартом в технологии современного программирования и подавляющее большинство новейших средств разработки программных продуктов построены на основе ООП. Delphi не является исключением и обладает всеми необходимыми возможностями современного объектно-ориентированного языка программирования.
5.1. Инкапсуляция 5.1.1. Понятие класса и объекта Классом называется описание некоторой структуры программы, обладающей набором внутренних переменных — свойств, и функций (процедур), имеющих доступ к свойствам — методов. Процесс объединения переменных и методов, в результате которого и получается класс, называется инкапсуляцией. Итак, класс — это всего лишь описание, аналогичное описанию типа данных и недоступное для прямого использования в программе. Для получения доступа к свойствам и методам класса (за исключением методов класса, описанных ниже) необходимо создать экземпляр класса, называемый также объектом. Отметим различие в терминологии Turbo Pascal и Delphi. В Pascal объектом называлась структура, получаемая в результате инкапсуляции. В Delphi такие структуры называются классами, а объекты — это экземпляры классов. 90
Глава 5. Основные механизмы и положения ООП
5.1.2. Структура класса Описание класса. Свойства и методы Описание классов разделено на две части — интерфейсную («заголовочную») и описательную. В интерфейсной части располагается заголовок класса, в котором указываются название класса, идентифицирующее его в программе, а также описания свойств и заголовков методов. В описательной части располагается программный код (реализация) методов, заголовки которых указаны в интерфейсной части описания класса. Интерфейсная часть описания класса располагается в разделах описания типов данных модулей и основных частей программ. Выглядит она следующим образом: Туре <Имя класса> = class {Заголовок описания} <Имя свойства 1>: <Тип свойства 1>; {Описание свойства 1} <Имя свойства М>: <Тип свойства N>; {Описание свойства N} Оаголовок метода 1>; {Описание метода 1} Оаголовок метода М>; End;
{Описание метода М}
Описательная часть класса находится в разделе описания локальных подпрограмм. Методы, заявленные в интерфейсной части реализуются по обычным правилам описания процедур и функций. Для связи подпрограмм с классом, методами которого они являются, название класса указывается перед именем самой подпрограммы: Procedure <Имя класса>.<Имя метода>(<Список параметров>); или для методов-функций: Function <Имя класса>.<Имя метода>(<Список параметров>): <Тип значения>; Отличие методов от обычных процедур и функций состоит в том, что они могут обращаться к свойствам классов по имени без указания объектов. На этапе выполнения программы такие обращения будут перенаправлены к свойствам объектов, из которых вызываются соответствующие методы. В листинге 5.1 приведен пример описания модуля, содержащего описание класса.
.
.. 91
Часть П. Объектно-ориентированное программирование в Delphi
Листинг 5.1. Описания модуля, содержащего описание класса Unit UsingClasses; Interface Type Tear = class
{Заголовок класса содержит его название Tear} Mark: AnsiString; {Описание текстового свойства Mark для хранения марки автомобиля} EngineVolume: Double; {Описание вещественного свойства для хранения объема двигателя} Fuel: Double; {Описание вещественного свойства для хранения объема топлива} Function StartEngine: Boolean; {Заголовок функции-метода "Запустить двигатель"} Procedure StopEngine; {Заголовок процедуры-метода "Остановить двигатель"} Procedure AddFuel(NewFuel: Double); {Заголовок процедуры-метода "Заправить топливом"} Implementation Function TCar.StartEngine: Boolean; begin Result := (Fuel>0); end; {Описание метода StartEngine. В качестве результата возвращается значение логического выражения (Fuel>0) } Function TCar.StopEngine: begin end; {Описание метода StopEngine, не выполняющего никаких действий) Procedure AddFuel(NewFuel: Double); {Описание метода AddFuel} begin Fuel := Fuel + NewFuel; {Увеличение значения свойства Fuel на величину, переданную в качестве параметра NewFuel при вызове метода} end; End. {Окончание модуля}
Обратим внимание на использование свойства Fuel в методе AddFuel. Метод AddFuel, который является частью описания класса TCar, обращается к данному свойству по имени, хотя на этапе создания класса и неизвестно, какому именно экземпляру будет принадлежать изменяемое свой92
Глава 5. Основные механизмы и положения ООП
ство. Однако точно известно, что каждый экземпляр обладает свойством Fuel, причем объект, из которого вызван метод AddFuel, также может быть определен системой. При вызове метода AddFuelns какого-либо экземпляра класса тсаг в качестве изменяемого свойства Fuel автоматически будет выбрано то, которое принадлежит этому же экземпляру. Возможность такого абстрактного подхода к описанию классов без указания их конкретных -экземпляров и отличает объектно-ориентированный подход к созданию программы от модульного, несмотря на их некоторое сходство.
.
Методы класса
Интересным нововведением в Delphi являются так называемые методы класса, особенность которых состоит в том, что для их вызова нет необходимости создавать объект (экземпляр класса), а можно использовать их как обычные подпрограммы. Для описания таких методов используется ключевое слово class перед заголовком метода: Туре <Имя класса> = class class Оаголовок метода>;
{Описание метода класса}
End;
При реализации методов класса в описательной части класса ключевое слово class также указывается: class <Имя класса>.Оаголовок метода>; Методы класса применяются для создания библиотек специализированных методов, не требующих доступа к каким-либо данным, которые могут храниться в пределах объекта. В качестве примера приведем описание класса MthdClass, метод Gets которого является методом класса (листинг 5.2). Листинг 5.2. Пример описания методов класса unit DeclaringClassMethods; Interface Type MthdClass = class class Function GetS: Double;
End; Implementation 93
Часть II. Объектно-ориентированное программирование в Delphi class Function MthdClass.GetS: Integer; {Описание метода GetS класса M t h d C l a s s } Begin Result := 5; end; end. Для вызова метода класса необходимо указать название класса и название метода со списком необходимых параметров. Название класса и его метода разделяются точкой: <Класс>.<Метод>(<Список параметров?-) ;
Использование метода класса отражено в следующем листинге 5.3.
Листинг 5.3. Использование метода класса unit UsingClassMethods ; Uses DeclaringClassMethods; (Подключаем модуль DeclaringClassMethods, в котором описан класс MthdClass} Interface Implementation Procedure UsingClass; (Описание процедуры UsingClass} Var A: Integer; (Описание целочисленной переменной А} Begin A := MthdClass.GetS; (Вызов метода класса по имени класса и метода, в переменную А заносится значение 5} end; end.
Отметим, что методы класса иногда называют статическими методами.
Перегружаемые методы Перегружаемые методы аналогичны перегружаемым процедурам и имеют такой же механизм работы. Пример, возможного описания класса с перегружаемыми методами приведен в листинге 5.4.
94
Глава 5. Основные механизмы и положения ООП Листинг 5.4. Описание класса с перегружаемыми методами Туре AClass = class Function Mult(A, B:
Integer): Integer; overload; {Заголовок метода Mult с двумя целочисленными параметрами, возвращающий целочисленное значение} Function Mult(A, В: D o u b l e ) : Double; overload; {Заголовок метода Mult с двумя вещественными параметрами, возвращающий вещественное значение}
end;
Function AClass.Mult(A, B: Integer): Integer; begin Result := A * B; end; {Описание первого варианта метода Mult) Function AClass.Mult(A, B: Double): Double; begin Result := A * B; end; {Описание второго варианта метода Mult)
Области видимости элементов класса Для разграничения доступа к свойствам и методам экземпляров классов между различными частями программы предусмотрены модификаторы доступа («видимости»), приведенные в табл. 5.1. Модификатор доступа в Delphi (как и в Pascal, но в отличие от некоторых других языков программирования) относится не к конкретному свойству или методу класса, а ко всем элементам класса (свойствам и методам), описание которых располагается после указания модификатора. Один и тот же модификатор может указываться в описании класса более одного раза. Сравнительная таблица модификаторов доступа в Pascal и Delphi Модификатор
private protected public published
Таблица 5.1
Область видимости свойства или метода, помеченного модификатором Pascal
Delphi
описание класса и модуль, в котором описан класс, в том числе и для других классов
также
не используется
как у private + в классах, унаследованных от этого класса
любая часть программы
так же
не используется
как у public + в Инспекторе объектов Delphi для визуального построения программ
95
Часть II. Объектно-ориентированное программирование в Delphi
Стандартное описание класса содержит свойства и методы, расположенные в порядке возрастания их видимости, но это правило не является обязательным. Туре <Имя класса> = class private <Имя свойства 1>: <Тип свойства 1>; (Описание свойств класса, имеющих область видимости private} <Имя свойства N>: <Тип свойства N>; Оаголовок метода 1>; {Описание методов класса, имеющих область видимости private} Оаголовок метода М>; protected <Имя свойства 1>: <Тип свойства 1>; {Описание свойств класса, имеющих область видимости protected} <Имя свойства N>: <Тип свойства Jsl>; <3аголовок метода 1>; {Описание методов класса, имеющих область видимости protected} Оаголовок метода М>; public <Имя свойства 1>: <Тип свойства 1>; {Описание свойств класса, имеющих область видимости public) <Имя свойства N>: <Тип свойства N>; Оаголовок метода 1>; {Описание методов класса, имеющих область видимости public} Л... . Оаголовок метода М>; published (Описание специальных свойств класса (property), имеющих область видимости published} End; 96
Глава 5. Основные механизмы и положения ООП Различные области видимости свойств и методов объекта предназначены для упрощения поддержания целостности информации в объектах. Рассмотрим класс, в котором содержатся три свойства — а, Ь и с, причем с является произведением а и Ь. Если свойства а, Ь и с будут иметь широкую область видимости public, то есть будут доступны из любого фрагмента программы, то объект — экземпляр такого класса может содержать некорректные данные, так как существует возможность изменения свойств а и Ь без пересчета свойства с. Для выхода из такой ситуации можно предложить два подхода, и оба они связаны с использованием областей видимости свойств объекта. Первый подход (см. листинг 5.5) состоит в том, чтобы скрыть свойства а и Ь от вызывающих подпрограмм, назначив им узкие области видимости, например private или protected. Выбор того или иного модификатора определяется необходимостью использования скрываемых свойств в классах-потомках данного класса. Если такой необходимости нет, то назначается область видимости private, если объекты-потомки должны иметь прямой доступ к свойствам, то назначается область видимости protected. В любом случае, внешние подпрограммы не смогут обратиться к свойствам а и Ь, поэтому придется предусмотреть методы, которые их устанавливают. В этих методах, помимо установки значений свойств, должен производиться пересчет свойства с. Это будет гарантировать целостность данных объекта при изменении свойств а и Ь. Заметим, что методы установки должны иметь широкую область видимости, чтобы к ним можно было обратиться из любого места программы. Листинг 5.5. Задание областей видимости. Первый подход Unit UsingPrivatel; Interface
{Заголовок модуля} {Начало интерфейсной ч а с т и )
Туре ABC = class private a, b: Double;
{Заголовок класса ABC) {Начало области видимости p r i v a t e } {Свойства а и Ь имеют узкую область видимости} public (Начало области видимости p u b l i c } . с: Double; {Свойство с имеет широкую область видимости) Procedure SetA(NewA: Double); {Метод SetA имеет широкую область видимости} Procedure SetB(NewB: Double); {Метод SetB имеет широкую область видимости} end;
4 Зак. 867
97
Часть II. Объектно-ориентированное программирование в Delphi Implementation (Начало описательной части) Procedure ABC.SetA(NewA: Double); {Описание метода. SetA класса ABC} Begin a := NewA; (Установка значения свойства а} с := a * b; (Обновление значения свойства с — поддержание целостности данных} end; Procedure ABC.SetB(NewB: Double); {Описание метода SetB класса ABC} Begin b := NewB; {Установка значения свойства b} с := a * Ь; {Обновление значения свойства с — поддержание целостности данных} end; end. (Окончание модуля} При такой конструкции класса вызывающая подпрограмма не сможет изменить значения свойств а и Ь, не повлияв тем самым на значение свойства с, поэтому целостность данных не будет нарушена. Однако свойство с доступно для изменения, даже если его значение пересчитано при установке значений а и Ь. В итоге оно может измениться, вызвав несоответствие данных. Таким образом, лучше было бы скрыть свойство с, реализовав специальный метод доступа к его значению (листинг 5.6).
'
Листинг 5.6. Задание областей видимости. Второй подход Unit XJsingPrivate2; Interface Туре АВС2 = class Private с: Double; Public a, b: Double; Function GetC:
end; Implementation Function ABC2.GetC:
&8
{Заголовок модуля} (Начало интерфейсной части} (Заголовок класса ABC} (Начало области видимости private} (Свойство с имеет узкую область видимости} (Начало области видимости public} (Свойства а и b имеют широкую область видимости) Double; (Метод GetC имеет широкую область видимости} {Начало описательной части) Double; (Описание метода GetC класса АВС2}
Глава 5. Основные механизмы и положения ООП
Begin с := а * 'Ь; Result := с;
{Обновляем значение свойства с) {Возвращаем значение с вызывающей подпрограмме}
end; end.
{Окончание модуля}.
При такой конструкции класса целостность данных не может быть нарушена вызывающими подпрограммами, если только они не находятся в одном модуле с описываемым классом, то есть, в данном случае, в модуле UsingPrivate2. Единственным недостатком такого подхода является постоянный пересчет значения с при каждом обращении к методу GetC. Поэтому наиболее оптимальным вариантом решения нашей задачи будет сокрытие всех свойств класса и реализация методов доступа к ним через методы (см. листинг 5.7). Листинг 5.7. Задание областей видимости. Третий подход (оптимальный) unit UsingPrivateS; Interface Туре АВСЗ = class private a, b, с: Double; public Procedure
{Заголовок модуля}
{Все свойства имеют узкую область видимости}
SetA(NewA:
Double);
{Все методы имеют широкую область видимости}
Procedure SetB(NewB: Double); Function GetC: Double; end; Implementation Procedure ABC3.SetA(NewA: Double); {Описание метода SetA класса А В С З } Begin a := NewA; с := a * b; end; Procedure АВСЗ.SetB(NewB: D o u b l e ) ; {Описание метода SetB класса А В С З } Begin b := NewB; с := a * b; end;
99
Часть II. Объектно-ориентированное программирование в Delphi Function ABC3.GetC: Double; {Описание метода GetC класса А В С З ) Begin Result := с; {Просто возвращаем значение с} end; end.
Property-свойства Свойства property аналогичны свойствам объекта в смысле их использования. Однако внутренний механизм их работы намного более сложен. Такие свойства не являются простым отображением памяти, доступным для чтения и изменения, а подразумевают вызов методов объекта. Подробнее на свойствах property мы остановимся при рассмотрении создания компонентов, а пока просто приведем сокращенный формат описания таких свойств с краткими пояснениями. Итак, описание свойств property в простейшем виде выглядит следующим образом: property <Имя свойства>: <Тип> read <Функция чтения значения> write <Процедура установки значения>;
Когда вызывающая подпрограмма обращается к свойству property для получения его значения, вместо конкретного значения возвращается результат функции, указанной по имени после модификатора read. Аналогично производится установка значения свойства — вместо прямой записи значения вызывается процедура, указанная после модификатора w r i t e . Соответственно, данная процедура должна принимать один параметр, причем его тип должен быть таким же, как тип самого свойства. Модификаторы read и write могут не присутствовать в описании propertyсвойства одновременно. Если для свойства задана функция чтения, но не задана процедура записи, то такое свойство может быть использовано только для получения значения. Если задана процедура установки значения, но не задана функция чтения, то property-свойство можно использовать только в левой части оператора присваивания. В рассматриваемом нами примере свойства а и b должны быть доступны вызывающему фрагменту и для чтения и для записи, тогда как свойство с может быть доступно только для чтения. Пример описания и использования property-свойства для поддержания корректности данных приведен в листинге 5.8.
100
Глава 5. Основные механизмы и положения ООП
Листинг 5.8. Пример описания и использования property-свойства Dnit Properties; Interface Type ABC4 = class private fa, Bo, fc: Double
(Заголовок
модуля}
{Все свойства имеют узкую область видимости} protected {Все методы имеют область видимости protected} Procedure SetA(NewA: Double) {Процедура установки значения свойства fa} Procedure SetB(NewB: Double); {Процедура установки значения свойства fb} Function GetA: Double; {Функция получения значения •• свойства fa} Function GetB: Double; {Функция получения значения свойства fb} Function GetC: Double; {Функция получения значения свойства fc} {Раздел описания property-свойств, доступных published вызывающему фрагменту программы} property A Double read GetA write SetA; {Описание property-свойства А, для которого задана функция чтения GetA и процедура установки SetA. Свойство будет доступно вызывающему фрагменту программы для чтения и записи}' property В: Double read GetB write SetB; {Описание property-свойства В, для которого задана функция чтения GetB и процедура установки SetB. Свойство будет доступно вызывающему фрагменту программы для чтения и записи} property С: Double read GetC; {Описание property-свойства В, для которого задана функция чтения GetC, но не задана процедура установки. Свойство будет доступно вызывающему фрагменту программы только для чтения} end; Implementation Procedure ABC4.SetA(NewA: Double); {Описание метода SetA}
101
Часть II. Объектно-ориентированное программирование в Delphi Begin fa := NewA; fc
:= fa *
fb;
{Занесение нового значения в private-свойство f a } {Пересчет произведения и занесение результата в свойство f c }
end; Procedure ABC4.SetB(NewB: Double); {Описание метода SetB} Begin fb := NewB; {Занесение нового значения в private-свойство f b } fc := fa * fb; {Пересчет произведения и занесение р е з у л ь т а т а в свойство f c } end; Function ABC4.GetA: Double; {Описание метода GetA} Begin Result := fa; { Р е з у л ь т а т функции — значение private-свойства f a } end; Function A B C 4 . G e t B : Double; {Описание метода GetB}
Begin Result
:= fb; ( Р е з у л ь т а т функции — значение private-свойства f b }
end; Function ABC4.6etC: Double; {Описание метода GetC} Begin Result := fc; { Р е з у л ь т а т функции — значение private-свойства f c } end; end. {Окончание модуля}
Таким образом, property-свойства предоставляют возможность переноса информационных свойств в области класса с узкой видимостью, и обеспечивают доступ к их значениям через специальные методы, но в значительно более удобной форме. Для вызывающего фрагмента программы не имеет значения, к обычному свойству он обращается, или к propertyсвойству, тогда как класс имеет возможность подержания внутренней целостности. Заметим, что вместо функции чтения и процедуры установки может быть указано одно из обычных свойств, вне зависимости от его области видимости. В рассмотренном примере такую возможность следует применить при описании property-свойств а, Ь и с, указав вместо функций чтения свойства fa, fb и fc, соответственно. Модифицированный текст модуля приведен в листинге 5.9.
102
Глава 5. Основные механизмы и положения ООП Листинг 5.9. Пример описания и использования property-свойства (модифицированный) Unit Properties; Interface Туре АВС4 = class private fa, fb, fc: Double;
{Заголовок модуля}
{Все свойства имеют узкую область видимости} protected {Все методы имеют область видимости protected} Procedure SetA(NewA: Double); {Процедура установки значения свойства fa} Procedure SetB(NewB: Double); {Процедура установки значения свойства fb} published {Раздел описания property-свойств, доступных вызывающему фрагменту программы} property A: Double read fa write SetA; {Для установки значения property-свойства используется метод SetA, а для чтения private-свойство fa} property В: Double read fb write SetB; {Для установки значения property-свойства используется метод SetB, а для чтения private-свойство fb} property С: Double read fc; (Для чтения property-свойства используется значение private-свойства fc} , end; Implementation Procedure ABC4.SetA(NewA: Double); {Описание метода SetA} Begin fa := NewA; fc := fa * fb; end; Procedure ABC4.SetB(NewB: Double); {Описание метода SetB} Begin fb := NewB; fc end;
end.
:= fa * fb;
{Окончание модуля}
Такой подход также не дает возможности нарушить целостность данных вызывающим фрагментом, так как не обеспечивается прямой доступ к установке значений свойств fa, fb и fc.
103
Часть II. Объектно-ориентированное программирование в Delphi
5.2. Наследование 5.2.1. Основы наследования Один из основных механизмов объектно-ориентированного программирования -- наследование -- построение нового класса на основе ранее описанного класса. Полученные в результате наследования классы называются классами-наследниками (или дочерними классами), а классы, на основе которых они построены — классами-родителями (или родительскими классами). При наследовании дочерний класс приобретает все свойства и методы родительского класса и имеет доступ к любому его элементу, за исключением описанных с областью видимости private. При описании дочернего класса с использованием наследования имя родительского класса указывается в скобках после ключевого слова class в заголовке интерфейсной части описываемого класса: Туре <Имя класса> = class (<Имя родительского класса>) {Заголовок описания} {Описание собственных свойств и методов} End; Класс-наследник может быть описан на основе любого другого класса, вне зависимости от того, является ли родительский класс в свою очередь дочерним. Более того, все классы в Delphi являются наследниками от класса TObject, даже если это явно не указывается13. Класс T O b j e c t инкапсулирует основные методы и свойства, связанные с общим поведением объектов. К ним относятся такие как создание, инициализация и разрушение объектов; поддержка идентификации их принадлежности к классу во время выполнения программы и многие другие, связанные с интеграцией объектов, созданных в Delphi, в среду выполнения программы, то есть операционную систему. Таким образом, экземпляры любого класса в Delphi автоматически приобретают все эти возможности.
" Кстати, явное указание класса TObject в качестве родительского рекомендуется фирмойразработчиком Delphi для повышения читабельности текстов программ (см. раздел справочной системы «TObject and TClass»).
104
Глава 5. Основные механизмы и положения ООП
5.2.2. Переопределение методов Часто встречается ситуация, когда один или несколько методов дочернего класса должны работать не так, как в родительском классе. В каче.стве примера можно привести класс TGeomFigure, представляющий собой абстрактную геометрическую фигуру, и имеющий метод Draw для ее рисования. Наследниками этого класса могли бы быть классы TCircle для работы с окружностями, TLine для работы с линиями, и так далее. Очевидно, что все дочерние классы в такой ситуации должны изменить метод Draw, чтобы корректно отображать фигуры, которым они соответствуют. Изменение функциональности унаследованных методов в дочерних классах называется переопределением методов. • Механизм переопределения Для переопределения метода, реализованного в объекте-родителе, следует: » указать его заголовок в интерфейсной части описания дочернего класса без изменения имени, списка параметров и возвращаемого значения (если метод является функцией); » указать после заголовка метода ключевое слово override. Если ключевое слово override не указано, то метод не переопределяется. • реализовать метод (создать программный код) в описательной части объекта по обычным правилам. При этом в заголовке метода ключевое слово override не указывается. Чтобы метод мог быть переопределен в дочерних классах, он должен быть помечен ключевыми словами virtual или dynamic в интерфейсной части класса-родителя. Ключевое слово v i r t u a l указывает на то, что метод должен быть занесен в так называемую таблицу виртуальных методов (ТВМ), а ключевое слово dynamic на то, что метод должен быть найден по имени в дереве родительских объектов. Разница между использованием v i r t u a l и dynamic заключается в направлении оптимизации компилятором вызовов переопределяемых методов. Методы, помеченные v i r t u a l , оптимизируются по скорости, а dynamic-методы по размеру программного кода. В большинстве случаев рекомендуется использование виртуальных методов, а использование динамических методов целесообразно при высоких степенях вложенности связей родитель-наследник. Приведем описание класса TwoNums с двумя свойствами а и Ь, и методом GetResult, возвращающим сумму свойств. Далее, от этого класса опишем наследника ThreeNums, имеющего уже три свойства — а, Ь и с, и переопределяющего метод GetResult таким образом, чтобы возвращать сумму не двух, а трех чисел (листинг 5.10).
105
Часть I I . Объектно-ориентированное программирование в Delphi
Листинг 5.10. Использование переопределения Unit Overridingl; Interface Type TwoNums == class public {Заголовок класса TwoNums} a, b: Integer; (Описание двух свойств} function GetResult: Integer; virtual; {Описание заголовка метода; после описания указано ключевое слово virtual, то есть' этот метод может быть переопределен в дочернем классе} End; ThreeNums = class (TwoNums) public {Заголовок класса ThreeNums, в скобках после ключевого слова class указан класс-родитель} с: Integer; {Описание свойства с. Свойства а и Ь наследуются от класса-родителя TwoNums} function GetResult: Integer; override; {Описание заголовка метода, идентичного заголовку объекта-родителя; после описания указано ключевое слово override, указывающее на переопределение функциональности родительского метода} End; Implementation function TwoNums.GetResult: Integer; {Описание метода GetResult класса TwoNums} Begin Result := a + b; {Результат функции — сумма двух свойств} end; function ThreeNums.GetResult: Integer; {Описание переопределенного метода GetResult класса ThreeNums} Begin Result := a + b + c; {Результат функции — сумма трех свойств} end; End.
106
Глава 5. Основные механизмы и положения ООП
Переопределение методов с сохранением функциональности В приведенном примере метод GetResult класса Two'Nums полностью переопределен в классе ThreeNums, то есть его изначальная функциональность полностью утеряна, но включена в функциональность замещающего метода с помощью копирования. Это практически всегда возможно, если разработчик класса имеет доступ к исходному тексту класса-родителя, но не всегда удобно, так как программный код, реализующий метод, может иметь немалые размеры. Для сохранения функциональности переопределенного метода в Delphi имеется возможность его вызова из переопределяющего метода с помощью ключевого слова Inherited, используемого следующим образом: Inherited <Название метода>(<список параметров>); Аналогично могут вызываться и переопределенные методы, которые являются функциями: . . . . := Inherited <Название метода>(<Список параметров>); Изменим предыдущий пример таким образом, чтобы сумма свойств в методе GetResult вычислялась с использованием переопределенного варианта этого метода, описанного в классе TwoNums (листинг 5.11). листинг 5.11. Переопределение методов с сохранением функциональности Onit Overriding2 ; Interface Type TwoNums = class public a, b: Integer; function GetResult: Integer; virtual; End; ThreeNums = class (TwoNums) public c: Integer; function GetResult: Integer; override; End; Implementation function TwoNums.GetResult: Integer; Begin Result := a + b; end; • • , .
' 107
Часть II. Объектно-ориентированное программирование в Delphi
function ThreeNums.GetResult: Integer; Begin Result := Inherited GetResult + с ;
( Р е з у л ь т а т функции — сумма значения, выдаваемого переопределенным методом (сумма а и Ь) , и свойства с}
end; End.
5.3. Жизненный цикл экземпляра класса Создание и разрушение экземпляра класса (объекта), а также работа с ним, имеет сложный внутренний механизм. Однако все действия, необходимые для поддержания жизненного цикла объекта, берет на себя компилятор. Рассмотрим последовательно стадии работы с экземпляром класса: 1. Создание экземпляра класса (объекта). 2. Использовани. 3. Разрушени. Каждый из этапов подробно рассмотрен нами далее
5.3.1. Создание экземпляра класса. Конструктор Для создания экземпляра класса необходимо вызвать специализированный статический метод этого /класса, называемый конструктором. Конструктор наследуется автоматически всеми классами от коренного класса TObject и выполняет две важных функции: 1. Выделяет память под структуры данных, необходимые для поддержания жизнедеятельности объекта, то есть инициализирует объект; заполняет порядковые (целочисленные, логические, перечислимые и интервальные) свойства объекта нулевыми значениями; устанавливает нулевые ссылки (значение nil) для свойств-указателей, а также устанавливает нулевые длины динамическим строкам. 2. Возвращает ссылку на объект, которую можно сохранить в переменной для доступа к свойствам и методам объекта, а также для его последующего разрушения. Описание и вызов конструктора Описание конструктора в классе TObject выглядит следующим образом: Constructor Create; Для сохранения ссылки на вновь созданный объект используется переменная соответствующего типа:
108
Глава 5. Основное механизмы и положения ООП Var <Название переменной>:
<Название класса>;
Такая переменная автоматически рассматривается как ссылка на объект, и дополнительное указание компилятору на динамическую природу переменной не требуется. Листинг 5.12. Описание и вызов конструктора unit Geoml; Interface Type TGeomFigure = class ' {Заголовок класса T G e o m F i g u r e , класс автоматически я в л я е т с я наследником класса TObject} End; Implementation Procedure CreateFigure; :
/ Var Figure: TGeomFigure; {Описание переменной — ссылки на экземпляр класса. Несмотря на то, что не указан модификатор л, переменная Figure является переменной-указателем}
Begin Figure := TGeomFigure.Create; {Вызов конструктора класса TGeomFigure, унаследованный от класса TObject, и сохранение ссылки на новый объект в переменной Figure.}
end; End.
Переопределение конструктора Конструктор может быть переопределен в описании класса для выполнения дополнительной инициализации, характерной для экземпляров именно этого класса. Например, в конструкторе можно установить начальные значения свойств. Переопределение конструктора выполняется по обычным правилам переопределения методов, за исключением следующих моментов: 1. Конструктор родительского класса не обязательно должен быть помечен ключевым словом virtual, для того, чтобы его переопределить. Соответственно, если конструктор родительского класса не помечен как виртуальный или динамический, не требуется указание 109
Часть II. Объектно-ориентированное программирование в Delphi
ключевого слова override в описании дочернего класса. Виртуальные конструкторы необходимы для работы с типами данных, являющимися ссылками на классы (а не на объекты). Такая ссылка используется, например, в конструкторе класса TCollection, предназначенного в Delphi для хранения динамических списков. 2. Существенным отличием в переопределении конструкторов и обычных методов, является возможность изменения параметров конструктора дочернего класса относительно конструктора родительского класса. Однако это возможно только в том случае, если конструктор не является виртуальным. з
Заметим, что полное переопределение конструктора обычно недопустимо, так как конструктор, определенный в родительском классе, от которого наследуется дочерний класс, может выполнять некоторые действия, без которых невозможна корректная работа объекта. Для вызова версии конструктора, замененной в классе-наследнике, используется ключевое слово Inherited: Inherited;
Таким образом, любой переопределенный конструктор может и (обычно) должен сначала вызвать ту версию конструктора, которую он переопределяет. Если в классе-наследнике изменен список параметров родительского конструктора (например, список расширен), конструктор родительского класса вызывается с указанием его названия и передачей ему необходимых параметров: Inherited Create(<Список параметров>);
Рассмотрим пример создания объекта с переопределением конструктора (листинг 5.13). Листинг 5.13. Пример создания объекта с переопределением конструктора unit Geom2;
Interface Type TGeomFigure = class
protected Color: Integer; public
110
1
.
{Заголовок класса TgeomFigure, класс автоматически является наследником класса Tobject} {Описание свойства с областью видимости protected)
Глава 5. Основные механизмы и положения ООП Constructor Create(aColor: Integer); {Заголовок конструктора (область видимости public), список параметров изменен по отношению к родительскому классу TObject, конструктор которого не имеет параметров} End;
•
Implementation Constructor TGeomFigure.Create; {Заголовок описательной части конструктора} begin Inherited Create; {Вызов конструктора родительского класса} Color := aColor; {Инициализация свойства, которая не может быть выполнена в родительском классе, так как свойство Color описано в классе TGeomFigure} end; End.
5.3.2. Использование экземпляра класса Использование объекта заключается в вызове его методов и обращении к его свойствам аналогично работе с обычными переменными и подпрограммами, но с указанием переменной-объекта, к которому относятся эти свойства и методы: <Объект>.<Свойство> ИЛИ
<Объект>.<Метод>(<Список параметров>); Пример использования экземпляра класса показан в листинге 5.14. .::.•!:•:••' У.
.
-
-
-
.
. :
'
..
•
•
'
-
.
- •"••
.
'
' . ' . . .
.'":•'•. >'•• • '
' . '
Листинг 5.14. Пример использования экземпляра класса
.
•
•"
-
.
V ' : .•' 3-
unit Geom3; Interface Type TGeomFigure = class protected Color: Integer; public Constructor Create(aColor: Integer); Procedure Draw; {Заголовок метода} End; 111
Часть II. Объектно-ориентированное программирование в Delphi
Implementation
Procedure UsingFigure;
{Описание конструктора} {Описание метода Draw} {Описание процедуры, использующей объект класса TGeomFigure)
Var Figure: TGeomFigure; begin Figure := TGeomFigure.Create(3); (Создание экземпляра класса TGeomFigure} Figure.Draw; {Вызов метода Draw} Figure.Color := 13; {Изменение значения свойства Color} end; End.
5.3.3. Разрушение объекта. Деструктор Объект, естественно, занимает место в памяти, которая необходима для хранения как свойств объекта, так и дополнительной информации, например, таблицы виртуальных методов. Для разрушения объекта и освобождения памяти, которая выделена для него в конструкторе, предназначен деструктор — специализированный метод, изначально определенный в .классе TObject: Destructor D e s t r o y ; virtual; Если в конструкторе объекта производится выделение памяти под какиелибо ресурсы или объект регистрируется в других объектах, передавая им ссылку на себя, то возникает необходимость переопределения деструктора для освобождения памяти и разрушения связей с другими элементами программы. При переопределении деструктора необходимо указывать ключевое слово override, так как деструктор является виртуальным методом (помечен ключевым словом v i r t u a l в описании класса TObject). Последней командой описательной части деструктора должен быть вызов переопределенного деструктора родительского класса с помощью ключевого слова Inherited: I n h e r i t e d Destroy;
Прямой вызов деструктора не применяется для уничтожения экземпляра класса. Для этого предназначен метод Free, также описанный в классе TObject. Данный метод не может быть переопределен, так как не имеет в своем описании ключевого слова virtual, однако метод Free вызы112
Глава 5, Основные механизмы и положения ООП
вает деструктор Destroy, поведение которого может быть специфическим (листинг 5.15). Листинг 5.15. Пример создания и разрушения объекта unit Geom4; Interface Type TGeomFigure = class protected Color: Integer;
{Заголовок класса TGeomFigure, класс автоматически является наследником класса TObject} {Описание свойства с областью видимости protected),
public Constructor Create(aColor: Integer); {Заголовок конструктора (область видимости public) } Destructor Destroy; override; (Заголовок деструктора (область видимости public) } End; Implementation ;
Constructor
\
TGeomFigure.Create; {Заголовок описательной части конструктора}
begin Inherited Create; Color := aColor;
•
{Вызов конструктора родительского класса} {Инициализация свойства, которая не может быть выполнена в родительском классе, так как свойство Color описано в классе TGeomFigure}
end; Destructor TGeomFigure.Destroy; {Заголовок описательной части деструктора} begin Inherited Destroy; {Вызов деструктора родительского класса} end; Procedure UsingFigure;
{Описание процедуры, использующей объект класса TGeomFigure}
113
Часть II. Объектно-ориентированное программирование в Delphi Var Figure: TGeomFigure ; begin Figure := TGeomFigure.Create(3); (Создание экземпляра класса TGeomFigure} Figure.Draw; {Вызов метода Draw} Figure.Color := 13; {Изменение значения свойства Color} Figure.Free; {Разрушение объекта, автоматический вызов деструктора Destroy}
end; End.
5.4. Полиморфизм 5.4.1.
Совместимость
объектов
Очевидно, что при наследовании классов друг от друга количество свойств и методов увеличивается от класса к классу (или, как минимум, не уменьшается). Все объекты некоторого класса могут иметь доступ к методам и свойствам, реализованных в тех классах, от которых он унаследован14. В результате такого свойства объектов появляется теоретическая возможность присвоить переменной-ссылке на экземпляр класса реальную ссылку на экземпляр другого класса. Причем присваиваемый объект должен быть экземпляром класса, находящимся ниже по иерархии, чем тот, который был указан при описании переменной. На рис. 5.1 представлены классы, образующие иерархию. Один из классов — Класс А — является родительским для всех остальных. Соответственно, если некоторая переменная описана как ссылка на экземпляр Класс А, то на самом деле она может указывать еще и на Класс Б, и на Класс в. Класс А
•\ Экземпляр
Класс Б \--\ Экземпляр
Класс В \--\ Экземпляр" Рис. 5.1. Совместимость объектов " В данном случае, речь идет об использовании тех свойств и методов, которые доступны фрагментам программы, использующим объекты, то есть имеющим соответствующие области видимости. 114
Глава 5. Основные механизмы и положения ООП
Как и все объектно-ориентированные языки программирования, Delphi поддерживает свойство полиморфизма объектов при наследовании, которое состоит в правильном выборе виртуального метода, вызываемого из переменной-ссылки на объект. Вне зависимости от того, на экземпляр какого класса указывает переменная-ссылка в соответствии со своим описанием, будет вызван виртуальный метод, описанный в классе, на который реально указывает ссылка, а не его версия, описанная в классе, сопоставленном типу ссылочной переменной. Рассмотрим (листинг 5.16), например, классы Tline и TCircle, являющиеся наследниками класса TGeomFugure и переопределяющие виртуальный метод Draw, чтобы придать ему функциональность, необходимую для вывода каждой конкретной геометрической фигуры. Опишем переменную, которая является ссылкой на экземпляр класса TGeomFigure и присвоим ей ссылку на экземпляр класса TLine. Такая операция возможна, так как класс TLine является наследником класса TGeomFigure. При вызове метода Draw данной переменной этот метод будет вызван в том виде, как он описан в классе TLine, а не в классе TGeomFigure. В этом и выражается полиморфизм (многоформность) объектов, находящихся в одной иерархии. Свойство полиморфизма поддерживается с помощью так называемого позднего связывания (происходящего во время выполнения программы) переменных-экземпляров с методами, в отличие от раннего связывания (на этапе компиляции). Листинг 5.16. Иллюстрация полиморфизма unit PolymorphObjects; Interface Type TGeomFigure = class Procedure Draw; virtual; End; {Описание класса TGeomFigure с виртуальным методом D r a w } TLine = class Procedure Draw; override; End; {Описание класса T L i n e с переопределенным методом Draw} Implementation
.
Procedure TGeomFigure.Draw; begin end; {Описание метода Draw класса TGeomFigure, метод ничего не делает}
Т15
Часть II. Объектно-ориентированное программирование в Delphi Procedure TLine.Draw; begin {Вывод линии} end; (Описание метода Draw класса TLine, метод выводит линию} Procedure DsingPolymorph; {Описание процедуры, использующей . . . полиморфизм объектов} Var GeomFigure: TGeomFigure; {Описание переменной-ссылки на экземпляр класса TGeomFigure} begin GeomFigure := TLine.Create; {Создание экземпляра класса TLine и занесение ссылки на него в переменнуюссылку на экземпляр класса TGeomFigure} GeomFigure.Draw; (Вызов метода Draw из переменной GeomFigure, реально вызываемый метод описан в классе TLine (рисование линии) } GeomFigure.Free;{Разрушение объекта, на который указывает переменная GeomFigure, если бы класс TLine переопределял деструктор, то был бы вызван деструктор, описанный в классе Tline} end; End.
5.4.2. Определение принадлежности к классу и приведение типов объектов Оператор is При использовании полиморфизма нередко требуется определить, ссылка на объект какого именно типа находится в той или иной переменной. Для решения задач такого рода все объекты содержат информацию о классе — так называемую RTTI-информацию (от англ. Run Time Type Information — информация времени выполнения о типе), для доступа к которой во всех классах имеются методы, унаследованные ими от класса TObject. Однако использование этих методов не рекомендуется разработчиками, а вместо них предусмотрен оператор is, проверяющий принадлежность объекта к заданному классу. Оператор is возвращает логическое значение (True или False) и используется в следующем виде:
116
Глава 5. Основные механизмы и положения ООП <Ссылка
на
объект> is <Название
класса>
Проиллюстрируем использование оператора is на примере модуля (листинг 5.17), подключающего к себе модуль PolymorphObjects, разработанный в предыдущем примере. Реализуем процедуру, которая получает в качестве параметра ссылку на экземпляр класса TGeomFigure, и определяет, не принадлежит ли объект, на который реально указывает ссылка, к классу TLine. Если объект является экземпляром класса TLine, то вызовем его метод Draw, в противном случае процедура должна завершиться. Листинг 5.17. Использование оператора is unit: CheckClass; Uses PolymorphObjects; Interface Procedure D r a w F i g u r e ( F i g u r e : TGeomFigure); { З а г о л о в о к процедуры} Implementation Procedure DrawFigure(Figure: TGeomFigure); {Описание процедуры} begin If Figure is TLine {Проверка соответствия объекта, на который указывает переменная Figure, классу T L i n e } Then Figure.Draw {Если тип объекта -- T L i n e , то вызывается метод D r a w } Else Exit; {В противном случае — выход из процедуры} end; End.
Оператор as В рассмотренном примере (листинг 5.17) допускается вызов метода Draw из объекта, на который указывает параметр Figure, переданный в процедуру, так как заявленный тип параметра позволяет это сделать — в классе TGeomFigure описан метод Draw. Однако иногда требуется вызвать метод, который реализован не в заявленном классе, а в его наследнике, реальный экземпляр которого и передается в качестве параметра. Попытка сделать это вызовет ошибку компиляции. Для указания компилятору о том, что объект следует рассматривать как экземпляр какого-либо конкретного класса, предусмотрен оператор as, используемый следующим образом: <Ссылка на объект> as <Название класса>
117
Часть II. Объектно-ориентированное программирование в Delphi В результате такого указания Delphi пытается обращаться к объекту, на который указывает ссылка, как к экземпляру заданного класса. Так, если бы в описании класса TGeomFigure не было метода Draw, а описание его располагалось бы в классе T L i n e , то метод можно было бы вызвать следующим образом: If Figure is
TLine {Проверка соответствия объекта, на который указывает переменная Figure, классу T L i n e } Then (Figure as TLine) .Draw {Если тип объекта — T L i n e , то вызывается метод Draw} Else Exit; {В противном случае — выход из процедуры)
Заметим, что приведение типов может использоваться, в основном, при работе с объектами, изменение иерархии которых невозможно. К таким объектам, например, относятся стандартные объекты Delphi. В остальных случаях наличие подобных конструкций в программе, скорее всего, указывает на неправильно спроектированную иерархию классов.
5.4.3. Абстрактные методы Использование наследования и связанного с ним свойства полиморфизма очень удобно при проектировании сложных систем, состоящих из групп классов, имеющих смысловое единство. Такие классы обычно имеют одного родителя, в котором прописываются заголовки методов, необходимых для поддержки функциональности ветви иерархии. В рассматриваемых нами примерах таким классом был TGeomFigure, в котором метод Draw описывался, но не выполнял каких-либо конкретных действий. Для того, чтобы не описывать «пустых» методов, увеличивая бесполезный размер исходного текста программ, в Delphi предусмотрен специальный модификатор методов abstract (англ. Abstract — абстрактный, фиктивный): Procedure <Класс>.<Метод>(<Список параметров>); virtual; abstract; Или для методов-функций: Function <Класс>.<Метод>(<Список параметров:») : <Тип значения^- virtual; abstract; . Описание метода, помеченного ключевым словом abstract, в классе, где он описан, не требуется, однако и вызвать его из экземпляра данного класса невозможно. При попытке произвести такой вызов возникнет ошибка вре-
118
Глава 5. Основные механизмы и положения ООП
мени выполнения программы "Project Projectl.exe raised exception class EAbstractError with m e s s a g e ; ' A b s t r a c t E r r o r ' . " - "Исключительная ситуация EAbstractError в проекте P r o j e c t l . e x e " . Абстрактный метод всегда является виртуальным (помечен ключевым словом v i r t u a l ) или динамическим (помечен ключевым словом dynamic) и может быть переопределен в одном из классов-потомков. Соответственно, из экземпляров классов, переопределивших абстрактный метод, его вызов возможен. Абстрактные методы называют иногда чисто виртуальными, пример их использования приведен в листинге 5.18.
_
_ . _ .'..
:
. • • •'.._
.
Листинг 5.18. Использование абстрактных методов unit AbstractMethods; Interface Type TGeomFigure = class
{Описание класса TGeomFigure с виртуальным абстрактным методом Draw} Procedure Draw; virtual; abstract; End; {Заголовок абстрактного метода. Описательная часть класса не содержит реализации данного метода} TLine = class ' Procedure Draw; override; End; {Описание класса TLine с переопределенным методом Draw}
Implementation Procedure TLine.Draw; begin end; •
{Вывод линии} {Описание метода Draw класс'а TLine, который в классе-родителе описан как абстрактный}
Procedure UsingPolymorph; „ Var GeomFigure: TGeomFigure; {Переменная — ссылка на экземпляр класса TGeomFigure} begin GeomFigure := TLine.Create; GeomFigure.Draw; {В момент выполнения такого вызова переменная GeomFigure должна 119
Часть II. Объектно-ориентированное программирование в Delphi у к а з ы в а т ь не на экземпляр класса TGeomFigure, а на экземпляр классанаследника, переопределившего абстрактный метод D r a w . В данном случае, это экземпляр класса TLine} GeomFigure.Free; end; End.
5.4.4. Полиморфизм и property-свойства Рассмотрим пример, приведенный в листинге 5.19. Отметим использование свойства полиморфности объектов в рассмотренном примере. При установке значения property-свойству Color автоматически вызывается метод SetColor, которому в качестве параметра передается новое значение цвета. Данное значение заносится в protected-свойство f C o l o r , а затем производится автоматический вызов метода Draw. Метод Draw является виртуальным и может быть переопределен в классах-наследниках. При установке какого-либо значения property-свойству Color класса-наследника будет вызвана та версия метода Draw, которая описана именно в нем, а не в классе TGeomFigure. Таким образом, при отсутствии информации о виде фигур, выводимых классами-наследниками, в классе TGeomFigure полностью реализована логика изменения цвета этой фигуры15. Такой подход позволяет локализовать отдельные фрагменты логики сложных классов уже на стадии проектирования вершины иерархии, существенно упрощая тем самым разработку программного продукта в целом. Листинг 5.19. Полиморфизм и property-свойства unit GeomS; Interface Type TGeomFigure = class Protected fColor: Integer;
{Описание свойства с областью видимости p r o t e c t e d } Procedure Draw; virtual; {Метод прорисовки геометрической фигуры (виртуальный, предназначен для переопределения в классах-наследниках)}
Аналогичные механизмы широко используются в стандартных компонентах Delphi. Например, при изменении свойств, управляющих положением элементов управления на экране, они автоматически перемещаются в окне программы. 120
Глава 5. Основные механизмы и положения ООП Procedure SetColor(aColor: Integer);
(Процедура установки protectedсвойства fColor) Public Property Color:
Integer read fColor write SetColor; {Описание property-свойства Color}
End;
•
Implementation
. Procedure TGeomFigure.SetColor(aColor: Integer); begin fColor := aColor; Draw; end; {Процедура установки protected-свойства fColor заносит новое значение свойства в protected-свойство fColor и вызывает метод Draw для автоматической перерисовки фигуры при смене ее цвета)
1
•**,'!
Procedure TGeomFigure.Draw; begin end; • Procedure UsingFigure; Var Figure: TGeomFigure ; iColor: Integer; begin Figure := TGeomFigure.Create(3); Figure.Color := 13; {Изменение значения property-свойства Color, автоматически вызывается метод SetColor, изменяющий значение свойства fColor, и метод Draw, выводящий изображение фигуры на экран с использованием нового цвета) iColor := Figure.Color; {Обращение к property-свойству Color. В качестве значения, В соответствии с описанием данного propertyсвойства, возвращается значение свойства fColor} Figure.Free; end; End.
121
Часть II. Объектно-ориентированное программирование в Delphi
5.5. Ключевое слово Self Для выполнения некоторых задач, методам, реализованным в классе, иногда требуется сослаться на объект (экземпляр класса), из которого вызван метод во время выполнения программы. Поскольку такая ссылка на экземпляр не всегда известна в процессе разработки программы, в Delphi введено ключевое слово Self, вместо которого на этапе выполнения будет подставлен конкретный адрес объекта. Ссылка Self .может использоваться как обычная переменная, ссылающаяся на класс. В качестве примера можно привести метод некоторого класса, который выдает ссылку на экземпляр, из которого вызван данный метод во время выполнения программы: Туре AnyClass = class Function GetSelf: TObject;
end; Function AnyClass.GetSelf : TObject; Begin Result := Self; {Возвращаем ссылку на экземпляр, из которого вызван метод} end;
end. Использование ссылки Self может быть необходимо при создании компонентов, в конструкторе которых следует передать ссылку на компонентвладелец, не известную на этапе разработки программы.
122
6 Особенности архитектуры программы в ОС Windows
Объектно-ориентированное программирование — это не просто современная технология создания программных продуктов, а новый общепризнанный способ мышления. Объектно-ориентированный подход существенно снижает сложность программных продуктов, что позволяет разработчикам языков программирования изначально заложить в них возможности решения немалого количества широко распространенных задач. Причем это касается практически всех аспектов реализации программ, в том числе и алгоритмических частей, и фрагментов программ, управляющих данными. Задачей настоящей части книги в дальнейшем является описание некоторых возможностей Delphi, отличающих программирование на этом языке программирования от структурных языков. Поскольку создание программных продуктов, предназначенных для работы под управлением операционной системы Windows, также имеет существенные особенности, понимание которых особенно необходимо для более четкой организации работы программы , то в начале рассмотрим именно их.
6.1. Архитектура программы в ОС DOS Программа в операционной системе DOS на уровне подпрограмм представляет собой последовательность взаимных вызовов процедур и функций, обусловленную технически структурными операторами и логически алгоритмом программы. Таким образом, программа сама контролирует процесс своего выполнения от начала до конца и операционная система 123
Часть II. Объектно-ориентированное программирование в Delphi
способна лишь досрочно прекратить выполнение программы, а также приостановить ее выполнение или перенаправить потоки ввода/вывода. Если приложение в DOS интерактивно, то есть ему требуется какая-либо реакция пользователя, то оно может «дожидаться» такой реакции, самостоятельно приостановив свое выполнение. Например, если программе необходимо в некоторый момент времени получить с клавиатуры значение какого-либо параметра, в Turbo Pascal вызывается процедура ReadLn, приостанавливающая программу до ввода значения и нажатия клавиши Enter. Структура программы в операционной системе DOS представлена на рис. 6.1. Начало I Подпрограмма 1 _1_
I Подпрограмма 2 \
~~
~~
\ Подпрограмма 3
~~
~
Конец Рис. 6.1. Структура программы в DOS
В процесс работы программы могут вмешиваться так называемые резидентные программы, «отбирающие» у приложения право монопольного использования ресурсов, но обеспечивающие при этом его корректную работу. Например, программы-русификаторы при вводе пользователем символов с клавиатуры первыми получают доступ к этой информации и могут ее преобразовать. Однако некорректная работа таких программ может привести к краху системы и основного приложения.
6.2. Архитектура программы в ОС Windows . Многозадачность Операционная система Windows является многозадачной, что предполагает довольно жесткие требования к поведению программ. Одно из самых существенных требований — невозможность монопольного использования ресурсов, так как приложение не может самостоятельно определить, кто именно в данный момент может использовать ресурс, и в какую именно программу должна попасть информация, например, вве6 денная пользователем с клавиатуры' . На самом деле существуют ресурсы, которые могут использоваться приложением монопольно. Например, СОМ-порт по своей сути не может быть разделен между приложениями. Однако если одно из приложений получило его в пользование, то другое уже не может этого сделать.
124
Глава 6. Особенности архитектуры программы в ОС Windows
В связи с этим в Windows реализуется схема программ, управляемых событиями. Это значит, что каждая программа представляет собой набор подпрограмм, некоторые из которых вызываются непосредственно ядром операционной системы для реакции на какое-либо событие, произошедшее в системе. При этом ни одно приложение не может ждать совершения события, монополизируя, таким образом, ресурсы и приостанавливая работу других приложений. Анализом изменений в системе и рассылкой сообщений о событиях занимается непосредственно операционная система. Структура программы в Windows представлена на рис. 6.2. Подпрограмма Ядро Windows
—
1
Подпрограмма
i Подпрограмма
\
Рис. 6.2. Общая структура программы в Windows
. Многооконное™ Каждой программе в Windows сопоставляется окно — некоторая область на экране, в которую можно выводить информацию. Окно может занимать весь экран, или только его часть, оно может находиться за пределами экрана, быть свернуто (скрыто) по желанию пользователя или перекрыто окнами других приложений. / Программа может определить расположение своего окна на экране, хотя обычно в этом нет необходимости. Вся информация, выводимая программой, будет автоматически расположена в ее окне. Причем центр координат (точка с нулевыми координатами), используемых при выводе, находится в левом верхнем углу окна, то есть не зависит от расположения окна на экране. Оконное представление программ отвечает условиям, накладываемым на систему многозадачностью, так как несколько приложений одновременно не могут выводить свою информацию в одно и то же место. Помимо удобного представления информации для пользователя, окно имеет вторую важную функцию с точки зрения программы — в любой момент времени одно из окон «находится в фокусе» (является «активным») и потоки вводимой информации направляются операционной системой в программу, которой принадлежит это окно. Механизм такого перенаправления потоков ввода основан на событиях. Рассмотрим подробнее структуру программы в Windows. • 125
Часть II. Объектно-ориентированное программирование в Delphi
Программа, управляемая событиями Windows следит за изменениями в состоянии системы (нажатие клавиш, перемещение мыши и нажатие ее кнопок, и т.д.) и генерирует специальные структуры данных, которые содержат следующую информацию: 1. Ссылка на окно, которое находилось в фокусе, когда произошло событие. 2. Идентификатор события Message (значение типа Word). 3. Дополнительная информация для данного события (два значения — одно типа Word, второе типа Longlnt), например, код нажатой клавиши для сообщения о нажатии клавиши, или координаты мыши для сообщения о перемещении мыши. Далее, эти пакеты сохраняются в специальной очереди сообщений Windows, а ядро опять переходит в режим слежения за системой. С другой стороны, очередь непрерывно разбирается, анализируется ссылка на окно, которому предназначено событие, и вызывается его оконная функция — специальная подпрограмма, описанная по определенным правилам и зарегистрированная в операционной системе как объект назначения для сообщений. Заголовок оконной функции выглядит следующим образом: Function W i n d o w P r o c ( W i n d o w : HWnd; Message, WParam: Word; LParam:' L o n g i n t ) : Longint; export; Жизненный цикл сообщения представлен на рис. 6.3. Вызванная оконная функция должна проанализировать событие по его идентификатору Message, используя дополнительные параметры WParam и LParam, выполнить действия, обусловленные логикой программы, и вернуть в качестве результата функции код завершения обработки. Если Оконная функция приложения 1
Ядро Windows t
Изменения в системе
\ Очередь сообщений \*
Сообщение
^
Оконная функция приложения N
Ядро Windows
А
Сообщение Рис. 6.3. Сообщения в Windows 126
Глава 6. Особенности архитектуры программы в ОС Windows
приложение не может проанализировать код события (обработка такого события не предусмотрена в оконной функции), то должен быть вызван обработчик по умолчанию. Таким образом, большую часть оконной функции обычно занимает оператор множественного выбора Case, который на основании информации о сообщении выполняет необходимые действия. В секции Else такого оператора вызывается обработчик по умолчанию (см. рис. 6.4). Сообщение
Оконная функция приложения
Подпрограмма 1 Подпрограмма
Анализ кода сообщения
2
Подпрограмма N
\
Обработчик сообщений по умолчанию Рис. 6.4. Обработка сообщения в оконной функции
Окно, которое создается системой, должно принадлежать некоторому классу, зарегистрированному в ней 17 . Во время регистрации системе передается информация о многих параметрах окна, например, о наборе его визуальных элементов (наличие меню, цвет и стиль фона, и т.д.), а также ссылка на оконную функцию, экземпляр которой будет создан для каждого окна, принадлежащего данному классу. Рассмотрим основные этапы работы программы в ОС Windows: 1. Создание и регистрация класса окна, передача операционной системе ссылки на оконную функцию. 2. Создание окна на основе класса. 3. Организация предварительного анализа очереди сообщений, работа оконной функции, вызываемой ядром Windows. 4. Уничтожение окна, дерегистрация класса окна. Таким образом, Windows-программа, которая ничего не делает, занимает порядка семидесяти строк и предоставляет множество возможностей для появления в ней ошибок. При этом подавляющее большинство приложений выполняют одни и те же действия при обработке сообщений, да и набор обрабатываемых сообщений примерно одинаков. В такой ситуации логично решение об инкапсуляции характеристик окна и его поведения в некоторый класс, содержащий методы (обычно пустые), вызываемые при обработке событий. " Термин «класс» в данном случае не имеет отношения к объектно-ориентированному программированию.
127
Часть II. Объектно-ориентированное программирование в Delphi
Данные методы доступны для переопределения и предоставляют возможности для удобной работы с окном, основанной на логическом представлении окна в экземпляре соответствующего класса, а не на вызовах системных функций. Сокрытие сложных процессов, происходящих с приложением в Windows и объектно-ориентированный подход к программированию — основное направление развития языков программирования в настоящее время. Исключением не стал и Delphi. Все процессы, связанные с поддержанием работоспособности оконной программы, реализованы разработчиками языка. Это позволяет программисту сосредоточиться на разработке ло18 гики программы, а не той ее части, которая взаимодействует с системой .
Взаимодействие программы с операционной системой Программирование под DOS связано с частым обращением к ресурсам, например, к видеокарте, напрямую без вызова функций операционной системы, что повышает быстродействие программ за счет отказа от универсальности. Однако такой подход к программированию в Windows невозможен, так как может нарушить целостность других приложений. Еще один недостаток прямого подхода состоит в необходимости самостоятельной работы с устройствами, которые иногда существенно отличаются друг от друга, особенно в настоящее время 19 . В Windows эта проблема решена с помощью универсального программного интерфейса операционной системы, обеспечивающей единообразный доступ к устройствам одного типа с помощью набора так называемых API-функций (от англ. API — Application Programming Interface — интерфейс прикладного программирования). Например, вне зависимости от видеокарты, установленной на конкретном компьютере, для вывода изображения используются одни и те же функции. Несмотря на снижение быстродействия, вызываемое более сложным путем выполнения операции «программа — операционная система — драйвер — устройство», программа становится абсолютно переносимой, то есть может быть запущена н-а компьютере с любой конфигурацией. Таким образом, Windows-программы значительно теснее связаны с операционной системой, чем программы, работающие в DOS. Мы не будем рассматривать структуру Windows-программы более глубоко, так как в большинстве случаев это не требуется для написания программ на Delphi. Заметим, что для создания простейшего приложения, достаточно просто зайти в среду разработки Delphi, создать новый проект, и запустить программу. Собственно, разнообразие периферийных устройств и обеспечило, в некоторой степени, быстрое продвижение операционных систем, подобных Windows, так как сняло необходимость самостоятельной их поддержки прикладными программами.
128
V
rf
ft
f
i
Объектноориентированный подход к обработке ошибок
7.1. Понятие исключительной ситуации Исключительная ситуация (исключение) — это некоторая ситуация в программе, которая требует специальной обработки. К таким ситуациям относятся ошибки времени выполнения программы, например, деление на ноль или попытка чтения информации из несуществующего файла. При возникновении исключительной ситуации Delphi создает экземпляр некоторого класса, в зависимости от характера произошедшей ошибки. Все такие классы являются наследниками класса E x c e p t i o n (англ. Exception -- исключение), что позволяет им быть «узнаваемыми» для Delphi. Механизм исключений состоит в досрочном прерывании подпрограммы или метода объекта, в которых произошла ошибка, и поиск обработчика исключения в этой подпрограмме (реализация обработчиков описана ниже). Если обработчик не найден, то управление передается подпрограмме, вызвавшей ту подпрограмму, в которой произошла исключительная ситуация. В вызвавшей подпрограмме также производится поиск обработчика, и в случае его отсутствия управление передается следующей подпрограмме по стеку вызова подпрограмм. Искомый обработчик должен обрабатывать именно те виды исключений, объект которого создан во время возникновения ошибки. Если ни в одной из подпрограмм стека вызова не будет найден обработчик, то исключение передается в стандартный обработчик исключений, автоматически создаваемый в программе. В этом случае на экран будет выдан диалог с сообщением об ошибке (см. рис. 7.1), а программа продолжит свое выполнение в ожидании действий пользователя. 5 Зак. 867
Часть II. Объектно-ориентированное программирование в Delphi
.'Ц
Деление на ноль! Введите другой делитель. ОК
Рис. 7.1. Диалог с сообщением об ошибке
Если же в одной из подпрограмм найден обработчик соответствующих исключений, то управление передается ему, а дальнейший путь работы программы определяется обычным образом.
7.2. Принудительное создание исключительной ситуации «Тихие» исключения В Delphi предусмотрен специальный класс исключений eAbort (от англ. Abort Exception — исключительная ситуация прерванного выполнения), информация о которых не выдается пользователю стандартным обработчиком. Это так называемое «тихое» исключение (англ. Silent exception — тихое исключение). Заметим сходство названия этого исключения с процедурой досрочного выхода из подпрограммы Abort, которая, как раз и выбрасывает исключение eAbort. Для выхода из подпрограммы с передачей управления в вызвавшую подпрограмму без возникновения исключения используется процедура Exit (англ. Exit — Выход). Эмуляция ошибок Любая подпрограмма может также сама сгенерировать исключительную ситуацию («выбросить исключение», «возбудить исключение») с помощью оператора Raise (англ. Raise — будить, провоцировать): Raise Окземпляр -класса исключения>;
Например, можно воспользоваться стандартным классом исключения 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 <0бработчик исключения>; 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. .
-
•
.
•
•
•
,
•
•
•
•
•
.
, ••
• •••••••
•
-;.
•
• •
,•<•
. • • . . . ' • . - . . •:•:
Листинг 7.1. Использование блока обработки исключений Try..Except :
Unit UsingTryExceptl; Interface Function MyDivfA, B: Double): Double; Implementation Uses Math; Function MyDiv(A, B: Double): Double; begin Try {Начало защищенного фрагмента} Result := A / В; Except {Начало секции обработки исключительных ситуаций} Result := NaN; {В случае ошибки функция возвращает результат NaN} end; {Окончание блока обработки} end; End.
132
•
Глава 7. Объектно-ориентированный подход к обработке ошибок
Блок обработки исключений Try..Finally Блок обработки исключений T r y . .Except может быть использован в ситуациях, когда известно, как именно нужно реагировать на произошедшую ошибку, даже если программа и не пытается установить ее тип (не используются конструкции On. .DO. .Else). Например, в предыдущем примере совершенно не важно, почему произошло исключение, а важно только то, что результат функции не вычислен, и функция не может возвратить результат своей работы. Соответственно, вне зависимости от типа ошибки, функция должна вернуть значение NaN. Однако встречаются ситуации, когда реакция программы на ошибку не так важна, как корректное завершение фрагмента программы, вызвавшего эту ошибку. Такие ситуации связаны обычно с необходимостью вернуть операционной системе ресурсы, выделенные ошибочному фрагменту программы. Причем, возвращение ресурсов должно произойти как в случае успешного, так и в случае ошибочного выполнения фрагмента. Для реализации такого поведения программы предусмотрен блок обработки исключений Try. .Finally: Try «Фрагмент программы 1> Finally <Фрагмент программы 2>
End;
При таком способе организации обработки исключений сначала производится попытка выполнить Фрагмент программы 1. В случае успешного его выполнения, управление передается Фрагменту программы 2, и блок обработки заканчивается. Если в Фрагменте программы 1 возникает исключительная ситуация, то его выполнение обрывается, но управление все равно передается Фрагменту программы 2. После выполнения Фрагмента программы 2 исключение выбрасывается заново и происходит его обработка по обычным правилам, например, с помощью блока обработки исключений Try. .Except. Различия в работе блоков обработки исключений На рис.'7.2 представлены три варианта фрагмента программы, в которой происходит ошибка времени выполнения. Каждый из фрагментов состоит из трех процедур, последовательно вызывающих друг друга. Ошибка происходит в третьей. На рисунке показаны три варианта работы в зависимо™ от вида используемого блока обработки или без него. В первом варианте обработка ошибки не производится (чего в принципе не должно быть в корректно созданной программе). Второй вариант позволяет локализовать исключение, то есть не дать ему выйти за преде133
Часть II. Объектно-ориентированное программирование в Delphi
в)
б) Процедура 1
Процедура 3
эпиэы
| Процедура 2
i
I^
Процедура 1
Процедура 1
- Вызов
• Вызов -^.::?::::~ Процедура 2
Процедура 2
— Вызов
• Вызов
Finallv -* --
_^..
Except End
t
Процедура 3 Ошибка
| Процедура 3 Т
[I
II
Рис. 7.2. Механизмы обработки исключительных ситуаций
лы какого-либо фрагмента программы. Благодаря этому остается возможным продолжение нормального выполнения программы. Далее мы остановимся более подробнее на каждом из вариантов. Программа на схеме «а» не обрабатывает исключительную ситуацию и работает следующим образом. Сначала выполняется Процедура I до момента вызова Процедуры 2. Путь корректного (безошибочного) выполнения программы показан сплошной линией. Затем Процедура 2 выполняется до момента вызова Процедуры 3. Процедура 3 выполняется, и в ней происходит ошибка, что влечет автоматическое создание объекта исключения и поиск обработчика, соответствующего данному исключению. Так как Процедура 3 не обрабатывает исключительную ситуацию, то она завершается, управление передается в вызвавшую ее Процедуру 2, и уже там начинается поиск обработчика исключения (направление потока выполнения при возникновении исключительной ситуации показано пунктирной линией). Однако Процедура 2 также не содержит обработчика исключений, следовательно, она будет прервана, а управление перейдет к Процедуре 1. Процедура I не содержит обработчика ошибок, следовательно, также будет прервана. В результате фрагмент программы, вызвавший, но не обработавший ошибку, вообще теряет управление. Теперь рассмотрим схему «б». Программа, представленная на ней работает аналогично схеме «а» до момента возврата управления в Процедуру 2 при возникновении исключения. Вызов Процедуры 3 в Процедуре 2 был сделан из блока обработки T r y . .Except, поэтому управление передается
134
Глава 7. Объектно-ориентированный подход к обработке ошибок
в секцию Except этого блока, после окончания которой фрагмент программы продолжает свое нормальное выполнение. На схеме «в» представлен фрагмент программы, имеющий блок обработки исключений T r y . .Finally. Его работа аналогична предыдущему случаю, за исключением того, что после завершения блока F i n a l l y . .End, Процедура 2 не будет продолжать свою работу, а завершится, и управление перейдет в Процедуру 1. Далее фрагмент будет работать аналогично представленному на рисунке «а».
7.4. Традиционный подход к обработке ошибок Традиционный подход к обработке динамических ошибок унаследован Delphi от Turbo Pascal и заключается в вызове функций, возвращающих коды завершения подпрограмм, например функции lOResuit. Обычно подпрограмма, завершенная с нулевым кодом, считается успешно выполненной, а ненулевые коды говорят об каких-либо ошибках. Однако Windows имеют место и функции, возвращающие нулевое значение в случае ошибки. Возврат кода завершения подпрограммы характерен, например, для системных API-функций Windows. Определение ошибки при работе с такими функциями вызывает существенное затруднение по двум причинам. Вопервых, кодов ошибок для каждой функции множество, а потому немалое время уходит на изучение справочной информации, тем более что каждая функция возвращает свой набор ошибок. Во-вторых, при проверке возвращаемых значений, к каждой строке исходного текста программы добавляется одна или несколько строк с проверками кода завершения, что отрицательно сказывается на читабельности исходных текстов программ. С помощью исключений стандартные подпрограммы и методы классов Delphi сообщают об ошибках, происходящих внутри них. Преимущество такого подхода состоит в том, что программист не должен анализировать значение, возвращаемое подпрограммами, а может сосредоточиться на логике собственной программы, считая, что каждый фрагмент програм.мы работает корректно, а в случае ошибки управление будет передано в соответствующее место автоматически. Управление программой с помощью исключений является наиболее прогрессивным в современном программировании и рекомендуется для использования при написании собственных программ. В качестве примера приведем процедуру, проверяющую корректность двух заданных ей параметров (листинг 7.2). Если параметры имеют положительное значение, то процедура заканчивается, а если хотя бы один параметр отрицателен, то будет выброшено исключение. 135
Часть II. Объектно-ориентированное программирование в Delphi
Листинг 7.2. Обработка ошибок Unit Checking; Interface
eKoordNonCorrect = class(Exception) end; {Описания класса-наследника Exception для использования в процедуре собственного исключения} Procedure CheckKoordinates (X, Y: Integer); {Заголовок процедуры указан в интерфейсной секции, следовательно, данная процедура доступна фрагментам программы, подключающим модуль} Implementation Procedure CheckKoordinates(X, Y: Integer); begin If
(X < 0)
OR
(Y < 0)
{Проверка, не является ли один из параметров отрицательным... }
Then Raise eKoordNonCorrect.Create(хПоложение объекта указано неверно'); { . . . е с л и один из параметров отрицателен, то создается и выбрасывается объект исключения} end; End.
Заметим, что создание и возбуждение исключения произведено в одну строку, при этом в конструктор объекта исключения передается сообщение для пользователя. Использование описанной процедуры может выглядеть следующим образом: Uses Checking;
{Подключение модуля Checking]
Begin CheckKoordinates(180, 0 ) ; {Вызов процедуры с передачей ей неотрицательных параметров. Процедура выполняется без возникновения исключительной ситуации} CheckKoordinates(-30, 10); {Вызов процедуры с передачей ей в качестве одного параметра отрицательного значения. Процедура возбуждает исключение}
136
Глава 7. Объектно-ориентированный подход к обработке ошибок Если фрагмент программы, вызывающий процедуру CheckKoordinates не обрабатывает исключительную ситуацию, то пользователю выдается диалоговое окно, содержащее строку, заданную в качестве параметра конструктору исключительной ситуации в процедуре CheckKoordinates (см. рис. 7.3). Положение объекта указано неверно.
/
Рис. 7.3. Сообщение об ошибке
Заметим, что класс исключения eKoordNonCorrect, описанный в модуле Checking, ничего не добавляет к классу Exception и предусмотрен исключительно для того, чтобы вызывающая подпрограмма могла обработать именно это исключение в случае его возникновения: Uses Checking;
{Подключение модуля Checking}
Begin Try {Начало защищенного блока} CheckKoordinates(180, 0) ; CheckKoordinates(-30, 1 0 ) ; {Вызов подпрограмм, которые могут вызвать исключительную ситуацию} except On eKoordNonCorrect Do begin {Обработка некорректности координат} end;
end;
{Окончание защищенного блока}
При выполнении такой программы пользователю не будет выдано сообщение об ошибке, так как исключительная ситуация локализуется с помощью обработчика T r y . .Except.
137
I7
Объектноориентированный подход к хранению информации
8.1. Общие положения Хранение данных в программе не имеет смысла без доступа к ним. При этом чем сложнее структура информации, тем сложнее и подпрограммы, которые их используют. Использование классов для создания, хранения и получения в каком-либо виде информации, позволяет существенно упростить реализацию программных продуктов, насыщенных сложными данными. Основной мотив в использовании объектно-ориентированных технологий для хранения информации состоит в том, что класс фактически является интерфейсом к данным, содержащимся в его свойствах, и предлагает набор простых методов для доступа к данным сложной структуры. В качестве примера можно рассмотреть создание такой распространенной структуры хранения информации, как список — последовательность записей, каждая из которых содержит в качестве значения одного из своих полей ссылку на следующую запись в этом списке (см. рис. 8.1.а). Хранение информации в списке заключается в заполнении остальных полей какими-либо значениями. Реализация такой структуры не сложна, однако ее использование сопряжено с некоторыми проблемами. Если список реализован в таком виде, как он представлен на рис. 8.1.а, то есть информация хранится прямо в элементах списка (в их «информационных» полях), то для каждой новой структуры данных, на основе которой нужно создать список, придется реализовать свои подпрограммы доступа к списку. Выходом из этой ситуации может быть хранение нетипизированных указателей на информационные структуры (см. рис. S.l.b), но это существен138
Глава 8. Объектно-ориентированный подход к хранению информации
но усложняет разрушение списка, так как он не может автоматически разрушить структуры данных, которыми владеет. Но если заявленной хранимой структурой данных будет являться класс (см. рис. 8.1.с), например TObject, то в таком списке существует и возможность хранения экземпляров наследников данного класса, что существенно облегчает разрушение списка, так как любые объекты могут быть корректно разрушены вызовом метода Free, вне зависимости от их сложности. И, наконец, представление списка в целом в виде экземпляра класса (см. рис. S.l.d) позволяет освободить программу не только от подробностей представления информации, но и от вызовов каких-либо обслуживающих список подпрограмм. В результате разработчик может сконцентрироваться на реализации программы, использующей списки, без подробного изучения модуля, в котором эти списки реализованы.
i
Recordl: TRec Информационные поля Next: ЛТКес; — |
lecordl: TRec
Da Ne xt: *ТКес; — j
1
1
I Iecord2: TRec De, Ne xt: лТКес; — |
Record2: TRec Информационные поля Next: *TRec; — |
1
*
RecordB: TRec Информационные поля Next: *TRec; —1
itecordS: TRec Dt, Nt xt: ATRec; — |
а)
b)
Т" .*
2
ListClass: TList
Recordl: TRec
Procedure AdtlfO: TObject); Function Get: TObject;
Next:ЛГЯ«у — |
1
I
Record2: TRec Next: ЛТКес; — |
1
RecordS: TRec
: _1
Next: *TRec; —\
-^
Jb-mn/r.- ТНкс "•«" n-Wnt. *,,.--r»»..—
Т^ V^~ f
UMf: /«vnt.-«««««J^^1
K.\i>rJ. 1: Title"
.д_..
|
d)
Рис. 8.1. Методы реализации списков 139
Часть II. Объектно-ориентированное программирование в Delphi
В Delphi реализованы классы для представления самых распространенных структур данных — списков, очередей и стеков, а также массивов строк, которые широко применяются в компонентах. Далее рассматриваются некоторые из таких классов.
8.2. СПИСКИ 8.2.1. Основы использования списков Класс TList Для реализации списков указателей на произвольные структуры данных в Delphi предназначен класс TList (англ. List — список), имеющий внутреннее строение, аналогичное представленному на рис. S.l.d, за исключением того, что данные, хранимые в списке, задаются нетипизированными указателями (указателями типа pointer), как на схеме Ь, а не ссылками на экземпляры классов. Удобство использования списков типа TList и аналогичных им классов состоит в том, что к данным, хранимым в списке, использующая его программа обращается через интерфейс в виде методов. Таким образом, реализация класса TList может изменяться, но, если не изменяются его методы, то программа сохраняет работоспособность при переходе на новую версию Delphi. Основные методы, описанные в TList, решают задачи добавления и удаления элементов списка, а также его сортировку. Добавление элементов в список
v
Для добавления элемента в список используется метод Add, получающий в качестве параметра нетипизированный указатель, который помещается в очередной элемент списка. Function
Add(Item:
Pointer):
Integer;
К элементам, которые хранятся в списке, реализован индексный механизм доступа, то есть для указания на элемент используется его порядковый номер, возвращаемый методом Add. Номера элементам выдаются по порядку от 0, а при удалении какого-либо элемента из списка все элементы, следующие за ним, перенумеровываются. Если существует необходимость вставить элемент в середину списка, то в программе может использоваться метод insert: Procedure
Insert(Index:
Integer;
Item:
Pointer);
Параметр index указывает, какой индекс должен быть у нового элемента, при этом он не должен быть больше, чем номер последнего элемента в 140
Глава 8. Объектно-ориентированный подход к хранению информации
списке. Номер последнего элемента на единицу меньше общего количества элементов списка, которое можно получить с помощью свойства Count: property C o u n t :
Integer;
Удаление элементов из списка Для удаления элементов из списка предусмотрены методы Delete и Remove. Параметр index определяет номер удаляемого элемента, а параметр item — ссылку на удаляемый элемент. Procedure D e l e t e ( I n d e x : I n t e g e r ) ; Function R e m o v e ( I t e m : P o i n t e r ) : Integer;
Метод Remove возвращает номер удаленного из списка элемента, который он имел до удаления. Отметим, что при удалении элемента из списка память, в которой хранится информационная структура, не освобождается автоматически, а ссылка на нее теряется. Таким образом, вызов метода Delete является местом потенциальной утечки памяти, если ссылки, хранимые в списке, не дублируются в других фрагментах программы. Аналогичным образом работает метод Clear, удаляющий все элементы списка без разрушения информации, которая в нем хранится: procedure C l e a r ; virtual;
Доступ к элементам списка Для доступа к элементам списка предусмотрено несколько методов и свойств. Для получения указателей на данные, хранимые в первом и последнем элементах списка, используются соответственно методы First и Last: Function F i r s t : P o i n t e r ; Function L a s t : P o i n t e r ;
Получить ссылку на любой элемент, хранимый в списке, можно с помощью property-свойства items, доступ к которому аналогичен доступу к массиву: property I t e m s [ I n d e x :
Integer]:
Pointer;
default;
Свойство items может использоваться для получения данных, хранимых в некотором элементе списка, а также для установки таких данных.
141
Часть II. Объектно-ориентированное программирование в Delphi
8.2.2. Управление размером списка Экземпляры класса TList автоматически изменяют свой размер в момент вызовов методов добавления и удаления элементов, таких как Add и Delete. Однако существует несколько аргументов в пользу более гибкого управления размером. Список имеет две характеристики, которые определяют его размер: 1. Количество элементов, находящихся в данный момент в списке. 2. Количество элементов, которые могут храниться в списке при его настоящем состоянии. Первый параметр определяется свойством Count класса TList и содержит реальное количество элементов списка, существующих на момент обращения к свойству: property Count:
Integer;
При создании экземпляра класса TList данное свойство имеет нулевое значение, которое затем увеличивается при вызовах методов Add и Insert, либо уменьшается при вызовах Delete, Remove и Clear. Если свойству Count присваивается значение, то в список будет добавлено необходимое количество элементов, что аналогично вызову метода Add множество раз с параметром nil: Окземпляр класса T L i s t > . Add (nil) ;
С другой стороны, свойство Capacity управляет потенциально возможным количеством элементов для данного списка: property C a p a c i t y :
Integer;
Значение этого свойства не может быть меньше значения Count, и используется для выделения памяти под список. При добавлении множества элементов в список использование свойства Capacity позволяет заранее выделить списку память под хранение элементов, чтобы не увеличивать текущий размер списка при каждом добавлении элемента, так как эта процедура повышает нагрузку на систему и при частом использовании ведет к фрагментации памяти.
8.2.3. Пример использования списков указателей Последовательность действий, выполняемых над списком, поясним на примере, продемонстрировав использование различных методов добавления и удаления элементов в списке (см. листинг 8.1).
142
,
Глава 8. Объектно-ориентированный подход к хранению информации Листинг 8.1. Работа со списками Туре TInfo = Record A: Integer; S: String; end; {Описание записи — информационной структуры, указатели на экземпляры которой будут храниться в элементах списка} Var List: TList; {Описание ссылки на экземпляр класса TList} Rl, R2, R3: А TInfo; {Описание ссылок на экземпляры записи Tinfo} R: ATInfo; {Описание одной ссылки на экземпляр записи типа TInfo}
New (RibNew (R2) ; New(R3); {Создание информационных структур} List := TList.Create; {Создание экземпляра класса-списка} List.Count := 99; {Установка размера списку через свойство Cou-nt, в результате чего список содержит 99 элементов, в каждом из которых храниться указатель со значением nil} List.Add(Rl); {Создание нового элемента, добавление его в конец списка и занесение в него ссылки , R1. В списке теперь содержится 100 элементов/ а новый элемент получает номер 99, так как нумерация ведется от 0} List.Insert(О, R2) ; {Создание нового.элемента, вставка его в первую позицию списка и занесение в него ссылки R2 . В списке теперь содержится 101 элемент, а новый элемент получает номер 0. Элемент, вставленный предыдущей, командой, сдвигается вперед и теперь имеет номер 100} List.Items[3] := R3; {Занесение в четвертый элемент списка ссылки R3. Количество элементов в списке не изменяется, перенумерация не производится} R := List.Items [100]; {Занесение в переменную R ссылки, хранящейся в элементе списка с номером 100 (ссылка эквивалентна R1) }
143
Часть II. Объектно-ориентированное программирование в Delphi Dispose (R);
{Освобождение памяти, занятой под ссылку R. Соответственно, использование переменной R1 и ссылки, хранящейся в 100-том элементе списка недопустимо} List.Delete(0); {Удаление первого элемента списка без освобождения памяти, на которую указывает ссылка, хранимая в нем (ссылка эквивалентна R2) } Dispose (R2); {Освобождение памяти, занятой под ссылку R2} List.Clear; {Удаление всех элементов списка без освобождения памяти, на которую указывают ссылки, хранимые в них. В данный момент в списке есть одна такая ссылка, эквивалентная R3} Dispose (R3); {Освобождение памяти, занятой под ссылку R3} List.Free; {Разрушение экземпляра класса-списка}
8.2.4. Списки объектов Рассмотренные выше списки нетипизированных указателей имеют некоторые недостатки, связанные с возможными ошибками утечки памяти и нарушения доступа при обращении к разрушенным структурам данных. Более корректную работу приложения обеспечивает наследник класса TList (см. рис. 8.2) — класс TObjectList (от. англ. List of Objects — Список объектов), являющийся списком ссылок на экземпляры классов. Использование списков объектов аналогично работе с экземплярами класса TList, однако в TObjectList методы переопределены таким образом, что вместо нетипизированных указателей в качестве параметров передаются ссылки на экземпляры классов. Например, методы Add и Remove описаны следующим образом: Function A d d ( A O b j e c t : T O b j e c t ) : I n t e g e r ; Function Remove(AObject: TObject): Integer;
Основным достоинством использования объектов в качестве хранимой в элементах списка информации является возможность их разрушения при удалении элементов списка. Соответственно, с вызывающей программы снимается ответственность за разрушение созданных объектов, занесенных в список. Необходимость такого поведения определяется значением свойства O w n s O b j e c t s : property O w n s O b j e c t s :
Boolean;
Если данное свойство имеет значение False, то список объектов ведет себя аналогично списку указателей, то есть при удалении элемента списка, ссылка, хранимая в нем, теряется. При значении True объекты, хранимые в списке, разрушаются при выполнении следующих операций: 144
Глава 8. Объектно-ориентированный подход к хранению информации * » « »
Удаление элементов списка с помощью методов Delete и Remove; Удаление элементов списка с помощью метода Clear; Разрушение объекта-списка; Изменение значения элемента property-свойства items.
Однако автоматическое разрушение хранимых объектов не всегда удобно, и иногда требуется удалить элемент из списка, не прекращая существование хранимого экземпляра класса. Для выполнения такой операции предусмотрен метод E x t r a c t (англ. Extract — изымать), унаследованный от класса TList: Function E x t r a c t ( I t e m :
TObject):
TObject;
Действия, выполняемые данным методом, эквивалентны команде Delete, однако в отличие от Delete, не будет вызван метод Free для хранимого класса.
8.2.5. Списки компонентов Список компонентов TComponentList (от. англ. List of Components — Список компонентов) является наследником класса TObjectList (см. рис. 8.2), имеет такие же свойства и методы, и предназначен для хранения классов, унаследованных от TComponent. Смысл создания именно такого списка заключается в том, что любой класс, имеющий ссьику на экземпляр компонента, имеет возможность получить уведомление о его разрушении20. При разрушении компонента, произведенного за пределами списка, тот элемент списка, в котором хранится этот компонент, автоматически удаляется. В результате такого поведения список содержит только корректные ссылки, то есть указывающие на реально существующие в данный момент времени работы программы экземпляры классов. ' TObject TList TObjectList I
TComponentList Рис. 6.2. Иерархия классов-списков
20
Механизм уведомления рассмотрен в разделе данной книги, посвященном описанию структуры компонентов Delphi.
145
Часть II. Объектно-ориентированное программирование в Delphi
Применение списков компонентов, в отличие от списков объектов, удобно при работе с экземплярами наследников класса TComponent в условиях, когда в списке содержатся объекты, созданные не фрагментом программы, который обращается к списку, а, например, автоматически при инициализации. Использование именно таких списков позволяет не следить за актуальностью ссылок, так как список TComponentList поддерживает ее автоматически.
8.2.6. Списки с заданным порядком элементов. Стеки и очереди у
Понятие стека и очереди Довольно распространенной задачей является организация в программе однотипных данных в некотором порядке и обеспечение последовательного доступа к ним. Используется две разновидности порядкового доступа: 1. Очередь, FIFO (от англ. FIFO — First In First Out — Первым вошел, первым вышел), когда элементы вынимаются из хранящей их структуры в порядке, соответствующем добавлению. Пример очереди изображен на рис. 8.3 (элемент, который не будет затронут ближайшей операцией изъятия, выделен серым фоном). 2. Стек, LIFO (от англ. LIFO — Last In First Out — Последним вошел, первым вышел), когда элементы вынимаются из хранящей их структуры в порядке, обратном добавлению. Пример стека изображен на рис. 8.4 (элементы, которые не будут затронуты ближайшей операцией изъятия, выделены серым фоном). Элемент Добавление (Push) Элемент
Элемент
Элемент
Добавление (Push) 1
' ' Получение (Pop)
Элемент Элемент
Элемент
Элемент
Элемент
• Получение (Pop) Элемент Рис. 8.3. Очередь 146
Ч
Элемент [;..••• Элемент
|
Рис. 8.4. Стек
Глава 8. Объектно-ориентированный подход к хранению информации
Возможности использования класса TOrderedList Для организации стека и очереди в Delphi описан класс TOrderedList, в котором заявлены свойства и методы, необходимые для работы со структурами последовательного доступа. Данный класс предназначен не для непосредственного использования, а для создания других классов на его основе. Основные методы TOrderedList реализуют добавление элементов в список (Push), извлечение их из списка (Pop) и получение хранимого элемента без удаления из списка (Peek): Function P u s h ( A I t e m : P o i n t e r ) : Function Pop: P o i n t e r ; Function Peek: P o i n t e r ;
Pointer;
Вторая группа методов является функциями, возвращающими количество элементов, содержащихся в списке на момент вызова (Count) и логическое значение, определяющее, содержит ли список заданное количество элементов (AtLeast): Function C o u n t : I n t e g e r ; Function A t L e a s t ( A C o u n t : I n t e g e r ) :
Boolean;
Класс TOrderedList не является наследником TList, а использует его в качестве внутреннего свойства для хранения информации. Свойство имеет область видимости protected, поэтому недоступно программе, использующей список. Также в классе TOrderedList не предусмотрено свойство items. Таким образом, фрагменты программы, имеющие ссылку на экземпляр списка с заданным порядком, не могут изменить последовательность элементов в этом списке, что гарантирует целостность и корректность хранимых данных. TObject TOrderedList J_ I TStack I | TQueue
\TObjectStack
\TObjectQueue\
Рис. 8.5. Иерархия списков последовательного доступа
Стек и очередь как наследники класса TOrderedList Стек и очередь реализованы в Delphi как наследники TOrderedList в виде классов TStack (от англ. Stack — Стек) и TQueue (от англ. Queue — Очередь) без добавления каких-либо свойств и методов. Для хранения объектов предусмотрены наследники данных классов TObjectStack (от 147
Часть II. Объектно-ориентированное программирование в Delphi
англ. Stack of Objects — Стек объектов) и TObjectQueue, (от англ. Queue of Objects — Очередь объектов). В них переопределены методы Push, Pop и Peek таким образом, чтобы в качестве параметров и возвращаемых значений указывались не указатели типа Pointer, а ссылки на классы: Function P u s h ( A O b j e c t : T O b j e c t ) : Function Pop: TObject; Function P e e k : T O b j e c t ;
TObject;
Аналогов стека и очереди для хранения компонентов не предусмотрено. Иерархия списков с последовательным доступом показана на рис. 8.5.
8.3. Коллекции 8.3.1. Основы использования коллекций Списки указателей и объектов направлены на временное хранение данных, используемых в программе. Создание хранимых структур данных происходит за пределами списков, а их автоматическое разрушение возможно только для списков объектов и компонентов T O b j e c t L i s t и TComponentList. Эти особенности списков существенно снижают удобство работы со списками во время визуального построения программ, так как визуальный построитель не владеет описанием хранимой в элементах списков информации, и, соответственно, не может создать экземпляр класса даже для списков объектов и компонентов, не говоря уже об указателях, хранимых в т List. Еще одной особенностью рассмотренных нами списков является то, что они не могут быть автоматически сохранены в файле формы, что окончательно делает их непригодными для использования в визуальной разработке. Примечание. О формах мы будем говорить отдельно, в одной из последних глав книги.
Более приспособленным вариантом представления данных в виде списка является класс TCollection (англ. Collection — Коллекция), в котором отсутствуют перечисленные проблемы. Элементы коллекции являются наследниками класса TCollectionltem (англ. Collection Item Элемент коллекции), который, также как и - T C o l l e c t i o n , унаследован от класса т Persistent, приспособленного для сохранения в составе проекта без дополнительных усилий со стороны разработчика2'. 21
Класс Persistent подробно рассмотрен в главе, посвященной описанию структуры компонентов Delphi.
148
Глава 8. Объектно-ориентированный подход к хранению информации
8.3.2. Создание и разрушение коллекций При создании коллекции конструктору в качестве параметра передается название класса-наследника TCollectionltem, экземпляры которого будут храниться в качестве элементов этой коллекции: Constructor
Create(ItemClass:
TCollectionltemClass);
Тип данных T C o l l e c t i o n l t e m C l a s s является так называемым метаклассом — описанием класса, и заявлен следующим образом: T C o l l e c t i o n l t e m C l a s s = class of T C o l l e c t i o n l t e m ;
Использование такого параметра не вызывает затруднений, в качестве его значения передается название класса без использования кавычек. Разрушение экземпляра коллекции производится аналогично разрушению любого объекта в Delphi — вызовом метода Free, после которого автоматически вызывается деструктор Destroy. Приведем пример создания и разрушения коллекции (листинг 8.2), в которой хранятся классы с двумя свойствами: строковым Name и целочисленным Age. Листинг 8.2. Пример создания и разрушения коллекции Туре TCollItem = class(TCollectionltem) Name: String; Age: Integer; end; {Описание класса, экземпляры которого будут храниться в коллекции. Класс является наследником TCollectionltem.} Var Collection: TCollection;
(Описание ссылки на экземпляр класса-коллекции TCollection}
Begin Collection := TCollection.Create(TCollItem); {Создание экземпляра класса-коллекции, в качестве параметра передается название класса-элемента} {Использование коллекции) Collection.Free; {Разрушение экземпляра коллекции}
149
Часть II. Объектно-ориентированное программирование в Delphi
8.3.3. Управление хранимыми данными Для управления данными, хранимыми в коллекциях, реализованы методы добавления и удаления элементов. Методов упорядочивания коллекций, в отличие от списка, не предусмотрено, так как в коллекциях обычно, важен порядок хранимых элементов, зависящий от внутренних целей программы. Например, в коллекциях хранятся описания характеристик столбцов таблиц, или списки полей таблицы базы данных. Упорядочивание данных такого рода, особенно автоматическое, не имеет смысла и может нарушить внутреннюю логику программы. Добавление элементов в коллекцию Добавление элементов коллекции происходит с помощью вызова методов Add или insert, которые, помимо создания элемента списка, одновременно создают и экземпляр класса, хранимый в этом элементе. Ссылка на вновь созданный экземпляр информационной структуры возвращается в качестве значения этих функций: Function Add: T C o l l e c t i o n l t e m ; Function I n s e r t ( I n d e x : I n t e g e r ) : T C o l l e c t i o n l t e m ; Удаление элементов из коллекции Удаление элементов коллекции выполняется методом Delete по заданному индексу элемента. Одновременно с уничтожением элемента удаляются и хранимые данные: Procedure Delete(Index:
Integer);
Для удаления всех элементов коллекции применяется метод Clear, также разрушающий все хранимые данные: Procedure Clear; Отметим, что метод Clear коллекции методом Free.
автоматически вызывается при разрушении г
Доступ к .элементам коллекции Доступ к элементам коллекции, как и к элементам списков, реализуется через свойство items, доступное для чтения и записи: property I t e m s [ I n d e x : I n t e g e r ] : T C o l l e c t i o n l t e m ; Практическое использование коллекций Рассмотрим использование коллекций на примере, показанном в листинге 8.3. 150
Глава 8. Объектно-ориентированный подход к хранению информации
Листинг 8.3. Пример использования коллекций
Туре TCollItem = class(TCollectionltem) Name: String; Age: Integer; end; {Описание класса, экземпляры которого будут храниться в коллекции. Класс является наследником TCollectionltem. }
Var Collection:
TCollection; {Описание ссылки на экземпляр класса-коллекции TCollection} Oneltem: TCollItem;
begin Collection := TCollection.Create(TCollItem) ; {Создание экземпляра класса-коллекции, в качестве параметра передается название класса-элемента} Oneltem := Collection.Add as TCollItem; {Добавление элемента в коллекцию с одновременным созданием экземпляра класса TCollItem, указанного в.конструкторе коллекции. Ссылка на этот экземпляр сохраняется в переменную Oneltem с одновременным приведением типа с помощью оператора as, так как метод Add возвращает значение типа TCollectionltem} Oneltem.Name := 'Michael Suharev'; Oneltem.Age := 26; {Изменение свойств объекта, хранимого в элементе коллекции} Oneltem := Collection.Insert(0) as TCollItem; Oneltem.Name := 'Peter Drach'; Oneltem.Age := 25; {Вставка нового элемента в коллекцию в нулевую позицию и изменение свойств нового хранимого объекта} Oneltem := Collection.Items[0] as TCollItem; {Получение ссылки на объект, хранимый в коллекции под номером 0} {Использование хранимого объекта} Oneltem : = Collection.Items[1] as TCollItem; {Получение ссылки на объект, хранимый в коллекции под номером 1} .: '{Использование хранимого объекта} 151
Часть II. Объектно-ориентированное программирование в Delphi Collection.Delete(0); {Удаление элемента коллекции с номером 0 . Одновременно производится разрушение хранимого объекта и изменение индексов о с т а в ш и х с я элементов коллекции} Collection.Clear; {Удаление всех элементов коллекции с а в т о м а т и ч е с к и м разрушением всех хранимых объектов. Использован для примера и необязателен, так как будет вызван при разрушении коллекции в следующей с т р о к е } Collection.Free; {Разрушение экземпляра коллекции)
Таким образом, коллекции имеют много общего со списками, но у них несколько другой механизм управления элементами, который выражается в праве собственности коллекции на структуры данных, которые в ней хранятся. Все остальные особенности коллекций вытекакУг из возможностей их родительского класса TPersistent, рассмотренного нами ниже. В частности, в коллекциях реализован метод A s s i g n , заявленный в TPersistent и позволяющий легко копировать содержание одной коллекции в другую, причем на основе объектов, хранимых в копируемой коллекции, будут автоматически созданы объекты, предназначенные для хранения в результирующей коллекции, полностью идентичные исходным.
8.4. Массивы текстовой информации 8.4.1. Общие возможности строковых массивов в Delphi Хранение строковых массивов является распространенной задачей при создании программных продуктов, взаимодействующих с пользователем. В визуальных компонентах Delphi также широко используются строковые массивы, например, в списках выбора или компонентах редактирования многострочного текста. Для хранения данных такого рода Delphi предлагает базовый класс TStrings, в котором объявлены абстрактные методы, описывающие основные действия, которые могут производиться с массивами строк. К таким методам относятся: » добавление, вставка и удаление строк из массива с возможностью доступа по индексу, а также просто доступ к строке по индексу; » упорядочивание строк внутри массива; » загрузка массива из файла или потока и сохранение в файл или поток; * сопоставление объектов строкам в массиве для хранения более сложных данных или расширенных параметров строк. 152
Глава 8. Объектно-ориентированный подход к хранению информации
Таким образом, класс TStrings определяет общие возможности строковых массивов в Delphi и обеспечивает универсальность в работе с такими структурами. Компоненты, имеющие свойства такого типа, совместимы между собой, что существенно облегчает работу с ними. Например, строки, сформированные пользователем в окне многострочного редактора, несложно скопировать в список выбора, и наоборот.
8.4.2. Манипуляции со строковыми массивами
j
Добавление элементов Для добавления строк в массивы в классе TStrings предусмотрено несколько методов с широкими возможностями. Для простого добавления строки к массиву предусмотрены методы Add и Append, выполняющие одинаковые действия. Метод Add возвращает номер строки, который она получила при добавлении: Function Add(const S: s t r i n g ) : I n t e g e r ; Procedure Append(const S: s t r i n g ) ;
Если строке должен соответствовать какой-либо объект, то для добавления пары строка-объект следует воспользоваться методом AddObject: Function AddObject(const S: Integer;
string;
AObject:
TObject):
Фактически метод AddObject можно использовать вместо Add, указывая в качестве второго параметра значение nil. Предусмотрена возможность вставки строки или строки в паре с объектом в заданную позицию с помощью методов Insert и insertObject: Procedure I n s e r t ( I n d e x : I n t e g e r ; const S: s t r i n g ) ; Procedure I n s e r t O b j e c t ( I n d e x : I n t e g e r ; const S: s t r i n g ; A O b j e c t : TObject);
Для групповых действий со строками предусмотрен метод Addstrings, добавляющий в массив строки из другого массива, задаваемого параметром strings типа TStrings: Procedure A d d S t r i n g s ( S t r i n g s :
TStrings);
..
И, наконец, для очистки массива и копирования в него строк из другого массива можно использовать метод Assign: Procedure A s s i g n ( S o u r c e :
TPersistent);
override;
153
Часть II. Объектно-ориентированное программирование в Delphi Удаление элементов Для удаления строк из массива TStrings предусмотрены возможности удаления одной строки по заданному индексу с помощью метода Delete и полная очистка списка строк с помощью метода Clear: Procedure Delete(Index: Integer); Procedure Clear; Доступ к элементам Для доступа к строкам в составе массива предназначены свойства класса T S t r i n g s . Наиболее часто используемым из них является свойство Strings, позволяющее получить строку по ее индексу: property Strings [Index: Integer]: string; default.; Аналогичное свойство предусмотрено и для доступа к объектам, привязанным к строкам: property O b j e c t s [ I n d e x : Integer]: TObject; Заметим, однако, что представление текстовой информации в виде множества строк удобно в основном, для пользователя. Тогда как, например, для организации поиска в текстовом документе с точки зрения программирования значительно проще рассматривать текст как одну строку, тем более что в Delphi длина строк практически не ограничена. Для доступа к массивам как к одной строке предусмотрены следующие свойства: property T e x t : string; property CommaText: s t r i n g ; property DelimitedText: s t r i n g ; В свойстве Text находится строка, образованная слиянием всех строк массива, причем между строками вставляются символы перевода строки и возврата каретки. Значение свойства CommaText образуется по более сложным правилам: » каждая строка, в которой присутствуют пробелы, заключается в двойные кавычки; » если в тексте присутствуют двойные кавычки, то они удваиваются; » полученные в соответствии с предыдущими правилами строки разделяются запятыми. Рассмотрим, например, массив строк, состоящий из следующих элементов: Intel Pentium Monitor "LG" 11, 19 Printer 154
Глава 8. Объектно-ориентированный подход к хранению информации
При обращении к свойству Text будет получена следующая строка: Intel Pentium<#13X#10>Monitor "LG" 17, 19<#13X#10>Printer<#13x#10>
где #13 — символ перевода строки, а #10 — символ возврата каретки. При обращении к свойству CommaText будет получено более удобное для восприятия значение: "Intel P e n t i u m " , " M o n i t o r " " L G " " 17,
19",Printer
Свойство DelimitedText аналогично свойству CommaText, однако более гибко, так как не использует для представления массива в виде одной строки жестко заданные разделители, а учитывает значения свойств QuoteChar (в какие символы заключается строка, имеющая пробелы) и Delimiter (какой символ следует использовать для разделения строк): property QuoteChar: C h a r ; property D e l i m i t e r : Char;
Например, если установить этим свойствам значения, соответственно, «#» и «=», то для нашего примера, свойство DelimitedText будет иметь следующее значение: #Intel Pentium#=#Monitor "LG" 17,
19#=Printer
Сохраняемость строковых массивов Для чтения элементов массива TStrings из файла или потока предусмотрены соответственно методы LoadFromFile и LoadFromStream: Procedure LoadFromFile(const F i l e N a m e : s t r i n g ) ; Procedure LoadFromStream(Stream: T S t r e a m ) ;
Для сохранения строк в файл или поток используются методы SaveToFile И SaveToStream: Procedure SaveToFile(const F i l e N a m e : s t r i n g ) ; Procedure SaveToStream(Stream: T S t r e a m ) ;
Сохранение и чтение текстовой информации происходит в соответствии с правилами представления свойства Text, то есть строки записываются подряд с использованием символов перевода строки и возврата каретки.
.
155
Часть II. Объектно-ориентированное программирование в Delphi
8.4.3. Практическое использование строковых массивов Все описанные выше методы либо являются абстрактными, либо в своей реализации используют абстрактные методы. Таким образом, создание экземпляров класса TStrings в программе не имеет смысла, а вместо этого следует использовать классы, унаследованные от него. Наследником TStrings, используемым во многих стандартных компонентах Delphi, является класс TStringList (см. рис. 8.6), поддерживающий все свойства, объявленные в TStrings, реализующий все методы, которые в родительском классе были абстрактными, и добавляющий некоторые дополнительные возможности. К последним относятся: » автоматическое упорядочивание строк; * автоматический контроль повторяемости строк. Упорядочивание строк производится по алфавиту, если свойство Sorted имеет значение True: property Sorted:
Boolean;
С помощью свойства CaseSensitive имеется возможность указать, следует ли учитывать регистр символов при сортировке: property C a s e S e n s i t i v e : B o o l e a n ;
Если свойство Sorted имеет значение False, то список не упорядочивается автоматически,, но может быть отсортирован вызовом метода Sort: Procedure Sort;
Список строк TStringList предлагает возможность автоматического управления уникальностью строк путем различной реакции на добавлении строк, аналогичных существующим. Для включения этой возможности и выбора типа реакции на добавление строки, дублирующей уже существующую, предназначено перечислимое свойство Duplicates: property D u p l i c a t e s : T D u p l i c a t e s ; TObject TPersistent TStrings TStringList Рис. 8.6. Иерархия строковых массивов 156
Глава 8. Объектно-ориентированный подход к хранению информации
Данное свойство может принимать следующие значения: » duplgnore - при добавлении строки, аналогичной существующей, вызов отклоняется без предупреждения, а строка не добавляется в массив; • dupError — добавление строки не производится, выбрасывается исключение eStringListError; » dupAccept - добавление дублирующей строки разрешено.
8.5. Хранение сложных двоичных данных Программы могут использовать данные, которые находятся в двоичном формате, например, графические изображения. Delphi предлагает объектно-ориентрованный подход к хранению и представлению такой информации. Так, например, реализована в виде классов поддержка графических форматов BMP, JPEG и EMF, имеющих сложную внутреннюю структуру. Также, с помощью Delphi, несложно использовать специальные структуры, получаемые из баз данных — BLOB-поля, доступ к которым осуществляется с помощью объектов и освобождает разработчика прикладного программного продукта от подробностей внутренней реализации сложных данных.
157
Объектноориентированный подход к вводу/выводу информации
9.1. Общие сведения о потоках С усложнением задач, решаемых программными продуктами, увеличиваются объемы используемых в них данных. Причем сами информационные структуры также усложняются. В качестве примера можно привести базу данных среднего предприятия, в которой может находиться несколько десятков таблиц, состоящих из множества полей и содержащих тысячи строк. При выборке информации из такой структуры могут использоваться различные ее части, связанные между собой, что ведет не только к существенной сложности алгоритма, но и к проблеме хранения большого объема данных как в памяти во время работы программы, так и в долгосрочной перспективе на каких-либо носителях. Объектно-ориентированный подход к хранению информации, рассмотренный выше, позволяет существенно упростить хранение данных во время работы программы, но не снимает вопросов о вводе информации в программу и ее последующем сохранении. Разнообразие физических устройств, предназначенных для хранения информации, еще более усложняет реализацию программных продуктов, заставляя разрабатывать уникальные для каждого устройства программные модули. Часть подобных задач берет на себя операционная система, представляя одинаковый программный интерфейс к похожим устройствам, например, Flash-памяти и жестким дискам. Однако этого не достаточно. С другой стороны, если рассматривать загрузку и сохранение информации с точки зрения логики процесса, без учета особенностей конкретного устройства, будь то оперативная память, магнитный носитель, или 158
Глава 9. Объектно-ориентированный подход к вводу/выводу информации
сетевое соединение, то можно легко описать абстрактное устройство для хранения информации. Вне зависимости от типа устройства ввода/вывода оно должно поддерживать довольно узкий набор возможностей, таких, как: » запись информации из программы на устройство; » чтение информации из устройства в программу; » определение параметров устройства, например, его емкости.
ч
Систематизация устройств ввода/вывода с такой позиции обеспечивает возможности объектно-ориентированного подхода к процессам ввода/ вывода с помощью создания некоторого базового класса, описывающего абстрактное устройство, и классов-наследников, реализующих заявленные методы чтения и записи информации в зависимости от особенностей конкретного устройства. Такой подход не только снижает сложность программы за счет использования объектов, но и придает ей значительную гибкость, так как переход с одного устройства хранения информации на другое не затрагивает фрагментов программы, непосредственно записывающих и считывающих информацию. Абстрактное устройство ввода/вывода называется потоком. Базовым классом, описывающем потоки (англ. Stream — поток), является TStream, в котором заявлены методы чтения и записи информации с устройства и на устройство. Кроме того, в нем содержатся свойства, с помощью которых можно получить емкость устройства и некоторые n iраметры процесса чтения или записи. Имеется также целый набор классов-наследников TStream, каждый из которых предназначен для работы с конкретным устройством, или ориентирован на работу с информацией определенного типа. Список таких классов приведен в табл. 9.1. На рис. 9.1 представлена схема работы программы, использующей потоки для ввода и вывода информации. Фрагменты программы (подпрограммы) обращаются к ссылке на экземпляр конкретного потока как к абстрактному устройству TStream. Далее, в зависимости от реального класса, на экземпляр которого указывает ссылка, информация перенаправляется на соответствующее физическое устройство, например, в файл. Стандартные классы-потоки
Таблица 9.1 Назначение
Класс TFileStream
Работа с файлами
TStringStream
Работа со строками, хранимыми в оперативной памяти
TMemoryStream
Работа с динамической памятью
TBIobStream
Работа с двоичными BLOB-полями баз данных
TWinSocketStream
Работа с сетевыми соединениями
TOIeStream
Ввод/вывод информации в OLE-объектах
159
Часть II. Объектно-ориентированное программирование в Delphi
i
'•
Запись
Чтение
TStream
Ч
Ч
Подпрограммы Работа с информацией
Работа с физическими устройствами
Рис. 9.1. Схема работы программы с потоками ввода/вывода
Возвращаясь к вопросу о гибкости программы по отношению к устройствам хранения информации, заметим, что при использовании потоков логика чтения и записи информации локализуется в двух разных частях программы. Указание на информацию, с которой следует произвести какие-либо действия, происходит в подпрограммах, а непосредственная работа с физическими устройствами обеспечивается экземпляром класса-потока. Предположим, что в программе используется файловый ввод и вывод информации, для чего в переменную Stream типа TStream заносится ссылка на экземпляр класса TFileStream: Var
Stream: TStream;
{Описание ссылки на абстрактное устройство}
Stream := TFileStream.Create(...); {Создание экземпляра класса для работы с файлами} Stream.Read(...); Stream.Write(...);
{Чтение данных из потока} {Запись данных в поток}
S tream.Free;
{Разрушение экземпляра класса}
При подобной организации программы для перехода на другое устройство ввода/вывода достаточно изменить тип создаваемого объекта на соответствующий новому устройству. Например, для использования динамической памяти вместо файла на диске, следует создать экземпляр класса TMemoryStream, а не класса TFileStream, остальная же часть программы остается неизменной:
160
Глава 9. Объектно-ориентированный подход к вводу/выводу информации
Var Stream: TStream;
{Описание ссылки на абстрактное устройство}
Stream := TMemoryStream.Create (...)/' (Создание экземпляра класса для работы с памятью} Stream.Read(...); Stream.Write(...);
{Чтение данных из потока} {Запись данных в поток}
Stream.Free;
(Разрушение экземпляра класса}
9.2. Базовый класс для работы с потоками Информация о состоянии устройства ввода/вывода В базовом потоковом классе TStream описано свойство Size, определяющее объем информации, который может храниться в потоке: property S i z e :
Int64;
Данное свойство обычно используется для определения текущего размера потока, однако некоторые наследники класса TStream реализуют изменение размера потока при изменении свойства size. Потоки построены на основе последовательного доступа к информации. Это значит, что после открытия потока информация считывается из его начала (записывается в начало). Позицию, откуда будет считана (куда будет записана) очередная порция информации, определяет свойство Position: property P o s i t i o n :
Int64;
Свойство P o s i t i o n доступно для изменения. Например, для указания потоку, что запись следует производить в его начало, можно воспользоваться следующей конструкцией: • <Поток>.Position := 0 ; Создание и разрушение потока Создание экземпляров потоковых классов существенно зависит от типов устройств, с которыми они связаны, так как в конструкторе любого потокового класса передается вся информация, необходимая для установ6 Зак. 867
161
Часть II. Объектно-ориентированное программирование в Delphi
ки связи с устройством. Формат конструкторов для разных видов потоков рассматривается ниже. Разрушение потока производится в обычном порядке с помощью вызова метода Free: Окземпляр потокового класса>. 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 <Имя класса>.<Имя 1Йетода>(<Список параметров>) ; <Список модификаторов;»; Или для методов-функций: Function <Имя класса>.<Имя метода>(<Список параметров>): <Тип значения>; <Список модификаторов>; Отличием методов от обычных процедур и функций является то, что они могут обращаться к свойствам классов по имени без указания объектов. На этапе выполнения программы такие обращения будут перенаправлены к свойствам объектов, из которых вызываются соответствующие методы. Вызов метода из экземпляра класса предваряется названием переменной, указывающей на необходимый экземпляр: <Имя объекта>.<Имя метода>(<Список параметров>); Объясните понятия статических, перегружаемых и абстрактных методов Ответ: В Object Pascal реализованы методы класса (статические методы), особенность которых состоит в том, что для их вызова нет необходимости создавать объект (экземпляр класса). Статические методы можно использовать как обычные подпрограммы. Однако такие методы доступны и из экземпляров класса. Для описания статических методов используется ключевое слово class перед заголовком метода. Для вызова метода класса необходимо указать название класса и название метода со списком необходимых параметров. 200
Вопросы с ответами для повторения по части II Допускается использование перегружаемых методов, то есть методов с идентичными именами, но разной сигнатурой. Их описание и использование аналогично работе с перегружаемыми подпрограммами. Для того чтобы не описывать «пустые» методы, в Delphi предусмотрен специальный модификатор методов abstract. Описание метода, помеченного ключевым словом abstract, в классе, где он заявлен, не требуется, однако и вызвать его из экземпляра данного класса невозможно. Абстрактный метод всегда является виртуальным (помечен ключевым словом virtual) или динамическим (помечен ключевым словом dynamic) и может быть переопределен в одном из классов-потомков. Соответственно, из экземпляров классов, переопределивших абстрактный метод, его вызов возможен. Опишите правила наследования классов Ответ: Наследование — это построение нового класса на основе ранее описанного. Полученные в результате наследования классы называются классами-наследниками (или дочерними классами), а классы, на основе которых они построены — классами-родителями (или родительскими классами). При наследовании дочерний класс приобретает все свойства и методы родительского класса и имеет доступ к любому члену родительского класса, за исключением описанных с областью видимости private. При описании дочернего класса с использованием наследования имя родительского класса указывается в скобках после ключевого слова class в заголовке интерфейсной части описываемого класса. Класс-наследник может быть описан на основе любого другого класса, вне зависимости от того, является ли родительский класс в свою очередь дочерним. Все классы в Delphi являются наследниками класса TObject, даже если это явно не указывается. Поясните сущность полиморфизма Ответ: Переменной-ссылке на экземпляр класса можно присвоить реальную ссылку на экземпляр другого класса, если они состоят в отношении родитель-наследник. Так как при наследовании классов друг от друга количество свойств и методов не уменьшается, то присваиваемый объект может быть экземпляром класса, находящимся ниже по иерархии, чем тот, который был указан при описании переменной. Как и все объектно-ориентированные языки программирования, Delphi поддерживает свойство полиморфизма объектов при наследовании, которое состоит в правильном выборе виртуального метода, вызываемого из переменной-ссылки на объект. Вне зависимости от того, на экземпляр какого класса указывает переменная-ссылка в соответствии со своим описанием, будет вызван тот виртуальный метод, который описан в классе, на который указывает реальная ссылка. -.
Приведите правила переопределения методов Ответ: Для переопределения метода, реализованного в объекте-родителе, следует:
201
Часть II. Объектно-ориентированное программирование в Delphi » указать его заголовок в описании дочернего объекта без изменения имени, списка параметров и возвращаемого значения (если метод является функцией); » указать после заголовка метода ключевое слово override; « реализовать метод в описательной части объекта по обычным правилам. Если ключевое слово override не указано, то метод не переопределяется. Чтобы метод мог быть переопределен в дочерних классах, он должен быть помечен ключевыми словами virtual или dynamic в интерфейсной части класса-родителя. •' ' Поясните разницу между использованием ключевых слов virtual и dynamic Ответ: Ключевое слово virtual указывает на то, что метод должен быть занесен в так называемую таблицу виртуальных методов (ТВМ), а ключевое слово dynamic — на то, что метод должен быть найден по имени в дереве родительских объектов. Разница между использованием virtual или dynamic заключается в направлении оптимизации компилятором вызовов переопределяемых методов. Методы, помеченные virtual, оптимизируются по скорости, а dynamic-методы по размеру программного кода. В большинстве случаев рекомендуется использование виртуальных методов, а использование динамических методов целесообразно при высоких степенях вложенности связей родитель-наследник. Расскажите о возможностях определения типа объекта во время выполнения программы и приведение типов объектов Ответ: Для определения, к какому именно классу принадлежит объект, на который указывает некоторая ссылочная переменная, в Delphi предусмотрен оператор is, возвращающий значение true, если объект является экземпляром заданного класса, или класса-наследника, или false, если объект не принадлежит заданному классу и его наследникам. После определения факта принадлежности к классу объект можно привести к заданному типу для получения доступа к свойствам и методам, заявленным в этом классе, а не в классе, тип которого указан в ссылочной переменной. Для приведения к заданному классу используется оператор аз. Опишите жизненный цикл экземпляра класса, расскажите о конструкторе экземпляров класса Ответ: Жизненный цикл экземпляра класса представляет собой его создание с помощью вызова конструктора, использование свойств и методов экземпляра класса и последующее разрушение структур объекта с помощью вызова деструктора. Конструктор наследуется автоматически всеми классами от коренного класса TObject и выполняет две основных функции: 1. Выделяет память под структуры данных, необходимые для поддержания жизнедеятельности объекта, то есть инициализирует объект; заполняет
202
Вопросы с ответами для повторения по части II порядковые (целочисленные, логические, перечислимые и интервальные) свойства объекта нулевыми значениями; устанавливает нулевые ссылки (значение nil) для свойств-указателей; а также устанавливает нулевые длины строкам. 2. Возвращает ссылку на объект, которую можно сохранить в переменной для доступа к свойствам и методам объекта, а также для его последующего разрушения. Конструктор может быть переопределен в описании класса для выполнения дополнительной инициализации, характерной экземплярам именно этого класса. Например, в конструкторе можно установить начальные значения свойств. Переопределение конструктора выполняется по обычным правилам переопределения методов, за исключением следующих моментов: 1. Конструктор родительского класса не должен быть обязательно помечен ключевым словом virtual, для того, чтобы его переопределить. Соответственно, если конструктор родительского класса не помечен как виртуальный или динамический, не требуется указание ключевого слова override в описании дочернего класса. 2. Существенным отличием в переопределении конструкторов и обычных методов является возможность изменения параметров конструктора дочернего класса, относительно конструктора родительского класса. Однако это возможно, только если конструктор не является виртуальным. Для вызова версии конструктора, замененной в классе-наследнике, используется ключевое слово Inherited. Расскажите о деструкторе класса экземпляров класса Ответ: Объект естественным образом занимает место в памяти, которая необходима для хранения как свойств объекта, так и дополнительной информации, например, таблицы виртуальных методов. Для разрушения объекта и освобождения памяти, которая выделена для него в конструкторе, предназначен деструктор — специализированный метод, изначально определенный в классе TObject. Если в конструкторе объекта производится выделение памяти под какие-либо ресурсы или объект регистрируется в других объектах, передавая им ссылку на себя, то возникает необходимость переопределения деструктора для освобождения памяти и разрушения связей с другими элементами программы. При переопределении деструктора необходимо указывать ключевое слово override, так как деструктор является виртуальным методом (помечен ключевым словом virtual в описании класса TObject). Последней командой описательной части деструктора должен быть вызов переопределенного деструктора родительского класса. Прямой вызов деструктора не применяется для уничтожения экземпляра класса, так как для этого предназначен метод Free, также описанный в классе TObject. Данный метод не может быть переопределен, так как не имеет в своем описании ключевого слова virtual, однако метод Free вызывает конструктор Destroy, поведение которого может быть специфическим.
203
Часть II. Объектно-ориентированное программирование в Delphi
| Расскажите об описании и использовании специальных свойств | объекта property Ответ: Свойства property аналогичны свойствам объекта в смысле их использования. Однако такие свойства не являются простым отображением памяти, доступным для чтения и изменения, а подразумевают вызов методов объекта. Описание свойств property в простейшем виде выглядит следующим образом:
t
property <Имя свойства>: <Тип> read <Функция чтения значения> write <Процедура установки значения>; Когда вызывающая подпрограмма обращается к свойству property для получения его значения, вместо конкретного значения возвращается результат функции, указанной по имени после модификатора read. Аналогично производится установка значения свойства — вместо прямой записи значения вызывается процедура, указанная после модификатора write. Соответственно, данная процедура должна принимать один параметр, причем его тип должен быть таким же, как тип самого свойства. Расскажите о ссылке Self Ответ: Ссылка Self может использоваться при разработке методов классов для указания на конкретный экземпляр, из которого вызван метод. Расскажите об особенностях программирования в ОС Windows Ответ: В Windows реализуется схема программ, управляемых событиями. Это значит, что каждая программа представляет собой набор подпрограмм, некоторые из которых вызываются непосредственно ядром операционной системы для реакции на какое-либо событие, произошедшее в системе. При этом ни одно приложение не может ждать совершения события, монополизируя, таким образом, ресурсы и приостанавливая работу других приложений. Анализом изменений в системе и рассылкой сообщений занимается непосредственно операционная система. Каждой программе в Windows сопоставляется некоторая область на экране, в которую можно выводить информацию — окно. Оконное представление программ отвечает условиям, накладываемым на систему многозадачностью, так как несколько приложений одновременно не могут выводить свою информацию в одно и то же место. В любой момент времени одно «активным») и потоки вводимой системой в программу, которой перенаправления потоков ввода
из окон «находится в фокусе» (является информации направляются операционной принадлежит это окно. Механизм такого основан на событиях.
Опишите механизм сообщений в ОС Windows Ответ: Windows следит за изменениями в состоянии системы (нажатие клавиш, перемещение мыши и нажатие ее кнопок, и т.д.) и генерирует специальные структуры данных, которые содержат следующую информацию:
204
Вопросы с ответами для повторения по части II 1. Ссылка на окно, которое находилось в фокусе, когда произошло событие. 2. Идентификатор события. 3. Дополнительная информация для данного события. Далее эти пакеты сохраняются в очереди сообщений Windows, а ядро опять переходит в режим слежения за системой. С другой стороны, очередь непрерывно разбирается, анализируется ссылка на окно, которому предназначено событие, и вызывается его оконная функция — специальная подпрограмма, описанная по определенным правилам и зарегистрированная в операционной системе как объект назначения для сообщений. Вызванная оконная функция должна проанализировать событие по его идентификатору, используя дополнительные параметры, выполнить действия, обусловленные логикой программы, и вернуть в качестве результата функции код завершения обработки. Дайте определение исключительной ситуации Ответ: Исключительная ситуация (исключение) — это некоторая ситуация времени выполнения программы, которая требует специальной обработки. К таким событиям относятся ошибки времени выполнения программы, например, деление на ноль или попытка чтения информации из несуществующего файла. Расскажите об объектах-исключениях в Delphi Ответ: При возникновении исключительной ситуации Delphi создает экземпляр некоторого класса, в зависимости от характера произошедшей ошибки и вызывает команду Raise, передавая ей ссылку на этот объект. Все такие классы являются наследниками класса Exception, что позволяет им быть «узнаваемыми» для Delphi. После вызова команды Raise по очереди прерываются подпрограммы, вызвавшие ту подпрограмму, в которой произошла исключительная ситуация. Данный процесс останавливается, когда в одной из подпрограмм стека обнаруживается обработчик соответствующего класса исключительных ситуаций. Любая подпрограмма может сама сгенерировать исключительную ситуацию с помощью оператора Raise. Расскажите о методах обработки исключительных ситуаций Ответ: Когда некоторый фрагмент программного кода необходимо защитить от досрочного завершения по ошибке, его следует заключить в блок обработки исключения. Таких блоков в Delphi предусмотрено два: « Блок обработки исключений T r y . . Except пытается выполнить заданный фрагмент программы, а, в случае появления исключения, передает управление специальному обработчику, расположенному в секции Except. Блок обработки исключений Try. .Except может быть использован в ситуациях, когда известно, как именно нужно реагировать на произошедшую ошибку, даже если программа и не пытается установить ее тип.
205
Часть II. Объектно-ориентированное программирование в Delphi » Когда реакция программы на ошибку не так важна, как корректное завершение фрагмента программы, вызвавшего эту ошибку, может быть использован блок обработки исключений Try.. Finally. При его использовании вне зависимости от корректности работы основного фрагмента программы, управление всегда передается в секцию Finally, где обычно производятся действия по возврату системе ресурсов, полученных перед входом в блок обработки исключения. Расскажите о классах, предназначенных для хранения информации в виде списков Ответ: Для хранения информации в виде списков в Delphi предусмотрено множество классов: » Класс TList — предназначен для хранения информации, представленной нетипизированными указателями. » Класс TObjectList — предназначен для хранения информации, представленной экземплярами классов. » Класс TComponentList — предназначен для хранения информации, представленной экземплярами классов-наследников класса TComponent. • Класс TStringList — используется для хранения списка строк и связанных с ними объектов. * Класс TCollection — предназначен для хранения экземпляров классов-наследников класса TCollectionltem. » Классы TStack и TObjectstack — предназначены для хранения нетипизированных указателей и экземпляров классов в порядке, определяемом принципом LIFO. » Классы TQueue и TObjectQueue — предназначены для хранения нетипизированных указателей и экземпляров классов в порядке, определяемом принципом FIFO. Классы TObjectList (TComponentList), TStack (TObjectstack), и TQueue (TObjectQueue) наследуются от класса TList, тогда как классы TStringList и TCollection наследуются от класса TPersistent, что делает их удобными для использования в компонентах Delphi. Расскажите об основных свойствах и методах классов-списков Ответ: Для всех классов-списков, кроме TStack и TQueue, определены методы добавления, удаления и вставки элементов Add, Delete и insert. Всеми классами поддерживается метод Clear, удаляющий из списка все элементы. Количество элементов, которое находится в данный момент в списке, и потенциально возможное количество хранимых элементов без дополнительных операций выделения памяти, можно получить с помощью свойств Count и Capacity. Для доступа к элементам списков, которые унаследованы от TList, используется индексное свойство items. К массивам строк, хранимых в экземплярах классов TStringList, и связанным с ними объектам можно обращаться через свойства strings и Objects, а стеки TStack и очереди TQueue
206
Вопросы с ответами для повторения по части
выдают свои элементы при их удалении из списка, либо с помощью команды Peek. Классы TStack и TQueue имеют особенности в системе организации свойств и методов в связи с тем, что организуют структуры для последовательного доступа. Добавление элементов в них осуществляется методом Push, а удаление методом Pop. Опишите особенности классов-списков, предназначенных для хранения объектов Ответ: Для хранения объектов с произвольным доступом к ним, то есть экземпляров классов, предназначены классы TObjectList, TComponentList и TCollection. Особенностью этих классов является возможность владения хранимыми объектами в смысле их разрушения при разрушении самих списков, или при удалении элементов. Разрушение производится методом Free и снимает с программы, использующей списки, ответственность за корректное освобождение памяти, выделенной под эти объекты, что существенно снижает риск появления динамических ошибок и повышает надежность программы. Первые два класса производят разрушение объектов опционально, при истинном значении свойства OwnsObjects, а класс TCollection владеет хранимыми в нем объектами полностью. Классы-списки последовательного доступа TObjectstack и TObjectQueue не имеют средств для разрушения хранимых объектов, так как имеют .другую логическую направленность — они предназначены для временного хранения экземпляров классов, и объекты, которые хранятся в таких списках, должны быть изъяты оттуда какими-либо фрагментами программы. Опишите возможности и особенности хранения строковых массивов в классах-наследниках TStrings Ответ: Для хранения массивов строк в Delphi описан базовый класс TStrings, в котором объявлены абстрактные методы, описывающие основные действия, такие как: » добавление, вставка и удаление строк из массива с возможностью доступа по индексу, а также просто доступ к строке по индексу; * упорядочивание строк внутри массива; » загрузка массива из файла или потока и сохранение в файл или поток; » сопоставление объектов строкам в массиве для хранения более сложных данных или расширенных параметров строк. Помимо обычных для представления информации в виде списков методов Add, Delete и Insert, в классе TStringList предусмотрены методы для добавления строк со связанными объектами AddObject и insertobject. Получение информации также ориентировано на текстовый характер хранимых данных, например, поддерживается возможность получения всего массива строк в виде одной строки, причем в различных форматах, в зависимости ОТ используемого свойства — Text, CommaText или DelimitedString.
207
Часть II. Объектно-ориентированное программирование в Delphi Строковые массивы поддерживают возможность сохранения информации в файл или поток и чтения ее оттуда. Поддерживается автоматическое упорядочивание строк по алфавиту и контроль уникальности. X
<Ь
Поясните понятие потока и объясните преимущества использования потоков по отношению к традиционным методам чтения/записи данных Ответ: Потоком называют программный интерфейс к некоторому устройству ввода/вывода. Абстрактное устройство представлено в Delphi классом TStream, а наследники этого базового потокового класса реализуют интерфейс, заявленный в нем, и поддерживают методы чтения и записи информации для конкретного устройства. Можно назвать следующие преимущества использования потоков по отношению к традиционным методам ввода/вывода: » существенное снижение сложности программного продукта за счет объектно-ориентированного подхода в результате использования единого интерфейса к разным устройствам ввода/вывода; • повышение гибкости программного продукта в смысле изменений, так как логика процессов ввода/вывода разбивается на две части — манипуляции с самим потоком (его создание и разрушение) и непосредственные операции ввода/вывода, то есть операции с информацией; » дополнительные возможности временного хранения информации в памяти с возможностью ее быстрого сохранения на любой носитель за счет совместимости разных потоков между собой. • Назовите потоковые классы, предназначенные для работы с реальными устройствами Ответ: В стандартной поставке Delphi, кроме базового класса TStream, реализованы следующие потоковые классы: » TFileStream, предназначенный для работы с файлами; » TStringStream, предназначенный для работы со строками, хранимыми в оперативной памяти; » TMemoryStream, предназначенный для работы с динамической памятью; » TBlobStream, предназначенный для работы с двоичными BLOB-полями баз данных; » TWinSocketstream, предназначенный для работы с сетевыми соединениями; * TOleStream, предназначенный для ввода/вывода информации в OLEобъектах. Расскажите про файловые потоки Ответ: Файловые потоки — это потоки, связанные с файлами. Файловые потоки реализованы с помощью класса TFileStream. При создании экземпляров данного класса в конструктор передаются параметры, определяющие название файла, с которым связан поток, режим, в котором файл
208
Вопросы с ответами для повторения по части II должен быть открыт (для чтения, записи, или в смешанном режиме), а также способ разделения файла с другими приложениями (другим приложениям запрещено считывать информацию из данного файла, записывать информацию в файл, считывать и записывать, либо разрешены все действия). Расскажите о потоках, построенныхе на основе оперативной памяти Ответ: Класс TMemoryStream представляет собой поток для временного хранения информации в оперативной памяти. Такие потоки могут использоваться для временного хранения информации и передачи ее в другой поток для записи на какой-либо носитель. Удобство такого метода хранения информации заключается в совместимости потоков TMemoryStream с другими потоками, в результате чего информация, хранимая в этом потоке, может быть легко скопирована в другой поток, например с помощью метода CopyFrom. Интересной особенностью потоков TMemoryStream является возможность сохранения информации в файл и чтения ее из файла с помощью методов SaveToFile и LoadFromFile. Расскажите о строчных потоках, построенных на основе оперативной памяти Ответ: Строчные потоки используются для доступа к строкам, хранимым в памяти, и реализованы в виде класса TStringStream. В каждом экземпляре такого класса хранится одна строка, доступ к которой возможен с помощью обычных методов, характерных для потоков, то есть Read, Write, и другими. Если программе, использующей такой поток, необходим доступ к хранимой информации, как к строке в целом, то ссылка на строку может быть получена с помощью свойства DataString. Особенностью строчных потоков является возможность записи и чтения строк с помощью методов WriteString и ReadString. Поясните понятие графического контекста Windows Ответ: Операционная система Windows является многозадачной, поэтому в ней запрещен непосредственный доступ к устройствам, в том числе и к видеокарте, так как это может вызвать конфликт между приложениями. Вместо непосредственного взаимодействия с аппаратной частью предназначен программный интерфейс, представляемый операционной системой в виде набора системных API-функций. Для того, чтобы идентифицировать какое-либо устройство, Windows использует понятие его контекста (DC), с которым и производятся все необходимые операции. Для вывода графической информации на экран, принтер и другие аналогичные им устройства используется специальная разновидность контекста устройства — графический контекст, представляющий собой описание параметров графического устройства и используемый по ссылке (НОС).
209
Часть II. Объектно-ориентированное программирование в Delphi Ссылка на графический контекст передается API-функциям в качестве одного из параметров, в результате чего выводимая информация попадает на необходимое устройство. Контекст графического устройства имеет следующий набор характеристик: » стиль и цвет обводки; » стиль и цвет заливки; » шрифт для вывода текстовой информации. Расскажите о представлении графического контекста Windows в Delphi В Delphi графический контекст Windows инкапсулирован в класс TCanvas, представляющий собой область отображения, и имеющий множество методов для вывода графической информации, например: » » » » *
отрезков и ломаных линий; прямоугольников, в том числе со скругленными краями; окружностей, эллипсов и дуг; кривых Безье; текстовой информации.
Также поддерживается заливка любой замкнутой области. Характеристики контекста также представляются экземплярами классов. Для хранения параметров обводки используется класс ТРеп, для хранения характеристик заливки — класс TBrush, а для хранения информации о шрифте — класс TFont. Расскажите о форматах графических файлов, поддерживаемых Delphi Ответ: В Delphi реализована поддержка следующих графических форматов: 1. Формат BMP представляет собой растровое изображение, хранимое в виде карты точек без сжатия. Изображения могут иметь различную цветность — от монохромной до TrueColor. Файлы с такими изображениями имеют размеры, пропорциональные размерам самого изображения и существенно зависят от количества используемых цветов. 2. Формат JPEG представляет собой растровые изображения, использующие сжатие с потерей качества. Изображения в данном формате являются либо полноцветными (TrueColor), либо черно-белыми (256 градаций серого цвета). Файлы с такими изображениями занимают немного места на диске, и их размер зависит не только от размеров и цветности самого изображения, но и от степени его сжатия. 3. В формате ICO представляется изображение, используемое для вывода Windows-иконок, и существенно отличается от других растровых изображений наличием прозрачной фоновой области, не затирающей нижележащую информацию при выводе на экран. Работа с такими изображениями более сложна, чем с изображениями, хранимыми в других форматах.
210
Вопросы с ответами для повторения по части
4. В формате EMF представляется последовательность команд вывода графической информации на экран. Данный формат поддерживает весь спектр API-функций Windows, ориентированных на вывод графической информации, и фактически является векторным. Существенным его отличием от растровых форматов является возможность качественного масштабирования изображения при выводе, пропорциональность размеров файлов хранимой информации, но, с другой стороны, он отличается более долгим выводу на экран с вероятностью неприятных для пользователя эффектов. Другие форматы Delphi не поддерживаются и требуют модулей стронних разработчиков. Однако приведенный набор форматов перекрывает весь спектр возможных применений графических изображенией с учетм различных особенностей. Расскажите о средствах хранения графических изображений в Delphi Ответ: Для хранения графических изображений в Delphi предусмотрен класс TGraphic и унаследованные от него TBitmap, TGPEGlmage, Tlcon и TMetafile, предназначенные для хранения, соответственно растровых изображений, JPEG-изображений, иконок и метафайлов. Все перечисленные классы, так или иначе, поддерживают загрузку информации из файлов или потоков, поэтому могут быть легко использованы в программных продуктах. Для хранения изображения в одном из форматов, в Delphi имеется класс TPicture, являющийся оболочкой для всех специализированных классов. Экземпляры класса TPicture имеют возможность считывать графические файлы по заданному имени, анализируя расширение файла для использования соответствующего метода чтения.
211
ЧАСТЬ III
г /7
МЕТОДИКА СОЗДАНИЯ ПРОГРАММ В DELPHI
ИСПОЛЬЗОВАНИЕ ИСР DELPHI В РАЗРАБОТКЕ ОБЩИЕ ПРИНЦИПЫ ПРОГРАММИРОВАНИЯ В DELPHI
ОСНОВЫ DELPHI. Профессиональный
Использование ИСР Delphi в разработке
Как было отмечено ранее, существенная часть разработки приложения в Delphi не требует написания какого-либо программного кода, а происходит с помощью интерактивного визуального построителя, интегрированного в среду разработчика. На этом этапе указывается количество и характеристики окон, используемых программой, а также набор и характеристики интерфейсных элементов для взаимодействия с пользователем. Таким образом, визуальная разработка программы в основном касается пользовательского интерфейса, однако иногда этого вполне достаточно для создания полноценного Windows-приложения. Дело в том, что визуальное построение программы основано на использовании так называемых компонентов (англ. Component) — классов-наследников TComponent. Соответственно, чем больше возможностей предоставляет конкретный компонент, тем меньше в его поведении должна изменить использующая программа. Например, для создания системы управления базой данных, которая обращается к удаленному серверу баз данных, например, Microsoft SQL Server или Oracle, получает от него информацию, изменяет ее и отсылает обратно, вообще достаточно только визуального построителя. Причем все эти процессы будут происходить в удобной для пользователя форме, свойственной Windows-приложениям.
11.1. Интегрированная среда разработчика Delphi Интегрированная среда разработчика Delphi (от англ. Integrated Developer's Environment — IDE — интегрированная среда разработчика) представляет собой современный инструмент разработки Windows-приложений любой сложности26 и состоит из трех основных частей: Несмотря на то, что Delphi выпускается производителем в нескольких вариантах (Editions), они отличаются друг от друга только набором компонентов и инструментов для работы с некоторыми технологиями. Однако среда разработчика Delphi одинакова для всех модификаций одной и той же версии. 213
Часть III. Методика создания программ в Delphi
1. Визуального построителя на основе формы — заготовки окна программы. 2. Текстового редактора для подготовки исходных текстов программы с подсветкой синтаксиса и встроенной системой контекстной помощи. 3. Встроенного отладчика. IDE состоит из нескольких окон, одно из которых является главным (в нем находится главное меню и палитра компонентов, доступных для размещения на форме). Окна могут перемещаться друг относительно друга, а также вставляться одно в другое на усмотрение разработчика. Один из вариантов внешнего вида IDE представлен на рис. 11.1. Заметим, что такая организация программы называется SDI — Single Document Application — приложение для редактирования одного документа, причем «документом» в данном случае является проект или группа проектов Delphi. На рисунке представлены только некоторые части среды разработчика, на самом же деле их может быть больше. Удобство работы в Delphi во многом обеспечивается интегрированной средой разработчика и неразрывно связно именно с ней. Поэтому мы будем рассматривать составляющие IDE Delphi одновременно с методами программирования и сопутствующими понятиями.
Ffe
Edit
Search
View
Project
Run
component
Database
Toots
window
Help I ||31Лег Desktop 1152 J^j < &* S(, \
' " . : ..'.'.Г;''
' Standard ]Addfondr ICE ttnt«cOr«T««iication Engra I i W«n32J System! Data Access! Data Controls i dbExoress
Actor
• л.
255 S Anchor.
^TOR^-;
BordeiSlyle
biSizeabie
..;.•'; . ; interface :| '. - : - \nmet
Э Memo!
'• •
;
* Dialogs.;:.
;1
:
! type j TForml Иеюо
I
:
i
I
But
'
priva
442 GcffltnFace (TSizeCwwtreni True ciDelault " dmAcfceForrn
t|
dmWanua) fiiie ' ' '" ' " :
297
jj
1 •!
I
\
1
\ Инспектор объектов
;
1
|
:
:
:••
<
: '.л;
: . |. :
:
Butoni
,
Button
,
•
Buttons
'
*'**41 i v^x> т^ , : ]:;::' : "': : : ; : : : : : ; .
Fa»!: Worrnl;
[tnvlementation
i
,.
IS 33
\ Дерево объектов
'ModfaJ
\
\
{Iken
\CodeXDiag™/
V
\ Текстовый редактор
Рис. 11.1. Интегрированная среда разработчика Delphi
214
;
" J
end;
i я
:
{ P
\ :|b; 1
\
Aj'sho&i
I
.
publi
;| :: :-;iwjii,
\
IsNoirnal "
But
Memol
{ Pub'
| J|| DtagMode Enabled ШРиУ / "' FonnSMe Heighi '
,inj.xi
|
^
L .-ч'!
I
ый редактор 270
"| ..;•!
3 ВШОПЭ
[akLeltakTopj ' True
BCModa
\\ Captain CfentHeigN . ClieritWidih •".Cokx EContffainU Ct!30 Cursor
l^>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иЬ, принадлежность
•<£:
TCommon Dialog
| Невизуальные компоненты
Диалоги
Рис. 11.3. Иерархия компонентов Delphi
11.4. Свойства и события компонентов. Инспектор объектов Основы использования компонентов При переносе компонента из Палитры компонентов на форму, неважно, визуальный это компонент, или невизуальный, создается экземпляр соответствующего класса. Published-свойства (например, размеры визуальных компонентов), описанные в компоненте, заполняются некоторыми значениями по умолчанию, и компонент отображается на форме. При размещении визуального компонента он выглядит так же, как и во время выполнения программы, а невизуальные компоненты и диалоги представлены иконками, соответствующими им в Палитре компонентов (см. рис. И.4). 223
Часть 111. Методика создания программ в Delphi
Вип'альный компонент StrineGrid
^Вичуеиьный компонент Kitlitn Рис. 11.4. Визуальные и невизуальные компоненты на форме
Понятие активного компонента Один из компонентов можно выбрать, нажав над ним левую кнопку мыши, после чего он станет «активным», о чем свидетельствует габаритная рамка вокруг него. Рамка предназначена для изменения размеров визуальных компонентов, а для невизуальных просто указывает на активность компонента. На рис. 11.4 активным является невизуальный компонент. Однако изменение размеров — не единственный повод сделать компонент активным, а наиболее существенные возможности реализованы в Инспекторе объектов (экземпляров компонентов), который по умолчанию находится в левой части окна. Если Инспектор объектов отсутствует на экране или скрыт другими окнами, его можно вызвать с помощью пункта главного меню View->Object inspector, или нажав клавишу F11. Предусмотрена возможность выбора нескольких компонентов одновременно, выбирая их при нажатой клавише Shift. •
Инспектор объектов
Инспектор объектов предназначен для изменения всех published-свойств, описанных в компоненте, и состоит из двух частей — заголовка, в котором указано название активного объекта и его тип (название класса), и блокнота с двумя закладками: Properties (англ. Properties — свойства), на которой в табличной форме представлены свойства, «е являющиеся ссылками на подпрограммы-обработчики событий, и Events (англ. Events — события), на которой в аналогичной форме представлены свойства, являющиеся ссылками на подпрограммы-обработчики событий. Варианты Инспектора объектов с выбранными закладками Properties и Events представлены на рис. 11.5 и 11.6. Как видно из заголовка Инспектора, активным объектом является Buttonl — экземпляр компонента TButton. 224
Глава 11. Использование ИСР Delphi в разработке • object Inspector Buttonl
.'f|lS
Т Button
j Buttonl
Properties j Events)
Properties
Action •*• El Anchors j [akLeft,akf op] ; BiDiMode 1 bdLeftToRight Cancel j False Caption [Buttonl 1 В Constraints I S izeConstraints) .......r.,.r..,,|(T ..,„.,.,,. . , ., .. [ MaxHeignt f 0 . MaxWidth 10 l^nH eight JO MinWidth ]6 Cursor icrDefault : : Default j False _rJ DragCursor р^шИНННН Mzl I 1 ГЫ6
• object Inspector
s&
Рис. 77.5. Закладка Properties Инспектора объектов
Т Г), .Up,-
jj
Events
"Action OnClick Buttonl Click OnContextPopi OnDragDrop 1 OnDragOver jj OnEndDock OnEndDrag »: OnEnter BitBtn2Click pnExit OnKeyDown Buttonl Click , OnKeyPress OnKeyUp OnMouseDowr
A.
d
IAII shown Рис. 11.6. Закладка Events Инспектора объектов
Published-свойства компонента могут иметь различные типы данных, как простые, например, Integer или Double, или более сложные, например string, так и сложные, например, записи и массивы. Инспектор объектов имеет возможность отображать и изменять значения свойств различных типов данных на основе зарегистрированных в IDE редакторов свойств. Каждый компонент, содержащий свойства нестандартных (не входящих в состав инсталляционного пакета Delphi) типов данных, должен при добавлении его в Палитру компонентов регистрировать собственные редакторы свойств. Таким образом, разработчик программы не должен заботиться о корректности отображения значений в Инспекторе объектов — ее поддерживает разработчик компонентов. Если свойство компонента подразумевает наличие подсвойств (полей), то слева от него в таблице на закладке Properties отображается символ «+». При нажатии на этот символ мышью свойство разворачивается и появляется возможность редактирования его полей (см. свойства Anchors и Constraints на рис. П.5). Правая часть таблицы свойств содержит поля ввода значений, причем для удобства разработчика значения можно не только вводить, но и выбирать из списков допустимых значений. Это касается, прежде всего, свойств типа Boolean (в списке выбора представлены значения True и False, см. свойство Default на рис. П.5), а также свойств перечислимых ти8 Зак. 867
225
Часть III. Методика создания программ в Delphi
пов (в списках выбора находятся константы, указанные при описании соответствующих типов данных). При создании экземпляра компонента все его свойства, представленные на закладке Properties заполняются начальными значениями, так как подразумевают под собой наличие некоторых структур данных внутри объекта, причем нередко зависимых друг от друга. Со свойствами, расположенными на закладке Events, ситуация противоположна: при создании компонента они не заполнены и имеют пустые значения. Данные свойства являются ссылками на подпрограммы-обработчики событий, вызываемые при появлении какого-либо события в системе, связанного с данным компонентом. Изначально значение таких свойств — nil — пустая ссылка, то есть при возникновении события не будет вызван никакой метод. Значение nil не отображается в Инспекторе объектов, вместо него там находится пустая строка. Если свойству сопоставлен какой-либо метод, то вместо ссылки на него отображается его название. Для того, чтобы сопоставить какой-либо метод формы свойству-событию, можно воспользоваться выпадающим списком, в который Delphi автоматически подберет все свойства, подходящие для соответствующего обработчика по сигнатуре (набору и последовательности параметров). Такой список представлен на рис. 11.6 напротив свойства OnEnter. Заметим, что в этом списке присутствует метод Buttoniciick, уже сопоставленный свойству Onclick. Если выбрать его как обработчик события OnEnter, то данный метод будет вызываться при появлении обоих событий. Для создания нового метода формы, являющегося обработчиком события компонента, принадлежащего этой форме, следует дважды нажать левую кнопку мыши над редактором соответствующего свойства. Таким образом, в Delphi реализован следующий принцип обработки событий: каждому событию может быть сопоставлен один обработчик, однако один и тот же обработчик может быть сопоставлен нескольким событиям, если он подходит им по сигнатуре 31 . Еще раз напомним, что значения свойств, установленные в Инспекторе объектов и ссылки на подпрограммы-обработчики событий автоматически сохраняются в файле формы, и будут восстановлены при следующем сеансе работы с проектом в среде Delphi, а также при запуске программы. Такая возможность реализована средой Delphi и методами класса TPersistent, наследниками которого являются все компоненты. 31
Такой принцип обработки событий соответствует концепции событий в Windows и концепции визуальной разработки Delphi, однако он не единственно возможный, и, вероятно, не самый удачный. Например, в Java реализован принцип «подписки» на события. Один компонент подписывается на события другого, а компонент, в котором произошло событие, вызывает по очереди соответствующие методы всех подписавшихся на его события компонентов. Такой подход позволяет уведомить о событии большее количество объектов.
226
Глава 11. Использование ИСР Delphi в разработке
11.5. Окно исходного текста программы Окно исходного текста программы (см. рис. 11.7) позволяет редактировать любые текстовые файлы, например, файл проекта или модули форм, и является инструментом, соответствующим современным требованиям. Редактор поддерживает встроенную систему контекстной помощи (вызывается нажатием клавиши F1 и учитывает идентификатор, на котором расположен курсор), систему синтаксической подсветки текста (выделение ключевых слов, комментариев, и т.д.), автодополнение программного кода по используемым объектам (подбор возможных методов и полей, которые могут быть использованы в данном фрагменте программы) и подпрограммам (списки параметров подпрограмм с учетом перегружаемое™ подпрограмм), а также систему навигации по тексту с помощью дерева объектов текущего модуля. JsLxJ Unit»
^ TForml ED Published !- Ф Bullonl ; 4u Buttonl Click : tj> Button2 : ф Buttons I- !J> Imagel = 0 Panell : О Panel2 $ fi Variables/Constants
procedure TForml.ButtonlClicktSender: TObject); begin
Imagel. | end; *
destructor Destroy; Canvas: Т Canvas; ; property Align: TAIign; : property Anchors: TAhchors; prciper^ AutoSize : Boolean; j pfcjpesty Center: Boolean; : pfopet^y Constraints : TSizeConstiaints; '-. property DragCursor: TCursor; DragKind : TDragKind; =: property propeity DragMode : TDiagHode; property Enabled : Boolean; : property IncrementalDisplay : Boolean; Parents howHint: Boolean; : ; property : ; pfoperty Picture ; Т Picture; . ('.li'CiJ.ji^fy PopupMenu: TPopupMenu;
S'J Fnm1 S-ffljUses Classes Controls Dialogs ExtCtrls Forms Graphics
[peg Messages SldDrls SysUtils Variants Windows
,
Рис. 11.7. Редактор исходного текста программы
Окно навигации по коду Простейшая навигация по исходному тексту программы может происходить с помощью пунктов меню Search, описанных выше, однако это не исчерпывает возможности Delphi в данной области. В момент отображения модуля формы в окне редактирования текста программы, автоматически устанавливается структура открытого модуля и вложенных в него структур, например, классов. Собранная информация представляется в виде дерева (см. рис. 11.7) и может быть просмотрена в окне навигации по коду (от англ. Code Explorer — Навигатор по коду).
227
Часть III. Методика создания программ в Delphi
По умолчанию данное окно встроено в редактор текста, однако оно может быть откреплено, перемещено в другое место экрана, скрыто или перекрыто другими окнами среды разработчика. Если окна навигации по коду нет на экране, оно может быть выведено туда с помощью пункта главного меню view->Code Explorer. На рис. 11.7 показано окно навигации по тексту, встроенное в редактор текста (в левой части рисунка). В окне навигации отображаются все типы данных, классы (с методами и свойствами), глобальные переменные и глобальные подпрограммы, описанные в текущем модуле, а также подключаемые модули. Для перехода к некоторому идентификатору в тексте следует выбрать его в 32 дереве объектов и дважды нажать над ним левую кнопку мыши . Автодополнение кода Автодополнение кода основано на анализе средой вводимого программистом текста в редакторе исходного текста. В Delphi реализовано автодополнение двух видов — завершение идентификатора по нескольким набранным символам и формирование списка выбора допустимых свойств и методов объекта, используемого в программе. Для автоматического завершения идентификатора по нескольким символам (начальным) следует, нажать комбинацию клавиш Ctrl-Пробел, после чего будет сформирован список идентификаторов, которые могут быть использованы в данном месте и начинаются с данных символов. При дальнейшем вводе символов выведенный список не исчезает с экрана, а уменьшается таким образом, чтобы в нем оставались только те идентификаторы, которые соответствуют текущему сочетанию введенных символов. Например, если в редакторе исходного текста набрать символы be и нажать Ctrl-Пробел, то средой будет сформирован и выведен на экран список свойств и методов текущего класса, а также глобальных переменных и процедур, доступных в данном фрагменте модуля. Все эти идентификаторы будут начинаться с символов be (см. рис. 11.8). При дальнейшем вводе, например, символа д, в списке останутся только те идентификаторы, которые начинаются с сочетания beg, и так далее.
32
Аналогичные возможности, но для выбора объектов (компонентов) на форме, а не в исходном тексте, Delphi предоставляет с помощью окна дерева объектов, в котором представлены объекты активной формы в виде дерева по принципу Владелец -» Объект. Для выбора компонента на форме следует один раз нажать левую кнопку мыши над названием компонента. Это окно является крайне полезным, так как некоторые компоненты на форме могут быть перекрыты другими компонентами, например, сама форма может быть полностью заполнена визуальными компонентами и нет возможности сделать ее активной для изменения ее свойств в Инспекторе объектов.
228
Глава 11. Использование ИСР Delphi в разработке procedure TForral.BitBtnlCliclc(Senaer: TObject) , begin
н
uiTr BeginAutoUFag
: procedure BeforeDextraction; en': рюрег'у BevelEdges : TBevelEdges; Bevellnner: TBevelCut; BevelQuter: TBevelCut; -; property sjioced'jfs ркчч^игб •pfocedure function function
BevelKind : TBevelKind; BevelWidlh : TBevefWidlh; В eginD rag (Immediate: Boolean; Threshold: Integer = -1 );jj. BeginGlobalLoading; Beep; Beep(dwFreq: Cardinal; dwDuration: Cardinal): LongBool; BeginUpdateResource(pFileName: PAnsiChar; bDeleK ^
Рис. 11.8. Автодополнение кода
- .
I
i
Заметим, что автозавершение идентификатора по малому количеству символов не имеет смысла, так как список будет формироваться значительное время, и будет иметь немалый размер, так что из него будет трудно выбрать нужный идентификатор. Второй механизм автодополнения кода ориентирован на работу с классами и их экземплярами, по смыслу похож на автозавершение идентификаторов, и заключается в формировании списка свойств и методов объекта, допустимых в текущем фрагменте модуля. Список формируется и выводится на экран, когда после названия объекта или класса ставится точка, свидетельствующая о необходимости вызова метода этого класса или объекта, или обращения к свойству объекта. Пример такого списка представлен на рис. 11.7, где в процессе редактирования модуля было набрано название идентификатора Image 1 и поставлена точка. Так же, как и при автозавершении идентификаторов, при дальнейшем вводе в окно редактирования текста отображенный список будет фильтроваться для соответствия вновь введенным символам. Описанные механизмы позволяют быстрее набирать программный код за счет автоматического ввода длинных идентификаторов и отсутствия необходимости точно помнить их названия.
229
IS
Общие принципы программирования в Delphi
12.1. Визуальное построение приложения Общие принципы программирования в Delphi мы рассмотрим на простейшем примере В качестве примера создадим приложение — калькулятор, состоящий из двух строк ввода для двух чисел, четырех кнопок для указания действия, которое следует выполнить с этими числами, а также еще одной строки ввода для вывода результата (см. рис. 12.1). Для построения такого приложения запустим среду разработчика Delphi33. В зависимости от ранее установленных настроек среда может выглядеть по-разному, но, если настройки по умолчанию не изменены, то будет автоматически открыт пустой проект, не сохраненный на диске и состоящий из одной формы — главного окна приложения — на которой нет интерфейсных компонентов. Для начала работы необходимо вызвать форму на экран, если она еще не отображена. Для этого следует воспользоваться пунктом главного меню view->Forms или нажать комбинацию клавиш Shift-F12. На экране появится диалог выбора активной формы, состоящий из трех частей. В верхней части диалога находится строка ввода для ускоренного поиска формы по имени, а в нижней — список всех доступных в проекте форм. Пустой проект содержит всего одну форму с названием Forml. Чтобы сделать форму активной, необходимо выбрать ее в списке и нажать на кнопку Ok, находящуюся в правой части диалога. Интерфейс IDE Delphi немного изменяется от версии к версии, однако такие изменения не являются существенными. Мы используем в качестве основы для изложения Delphi 7, однако все описываемые возможности существовали уже в шестой версии Delphi. Более того, для начинающих рекомендуется использование Delphi 5, как наименее перегруженной компонентами версии.
230
Глава 12. Общие принципы программирования в Delphi
Пересе число: |10
Второе число: ш
Результат: О Рис. 12.1. Калькулятор на Delphi
Форма, отображенная на экране, представляет собой окно с заголовком, на котором находятся кнопки минимизации, максимизации и закрытия окна, а также иконка оконного меню. Простейшие действия, которые можно выполнить с формой — это изменить ее размеры и положение на экране. По умолчанию главное окно программы будет иметь после запуска именно такие размеры и положение. После этого можно выбрать пункт главного меню Run->Run или нажать клавишу F9. Результатом будет выполнение программы — вывод главного окна на экран, которое будет находиться там до нажатия пользователем кнопки его закрытия. Теперь можно перейти к формированию пользовательского интерфейса программы. Для этого обратим внимание на ту часть среды разработчика, которая находится под ее главным меню и представляет собой набор закладок. На закладках расположены компоненты, которые могут располагаться на форме. По умолчанию выбрана закладка с названием standard, на которой находятся стандартные элементы управления Windows. Для начала добавим простую подпись для строки ввода первого числа с подписью «Первое число:». Для этого выберем компонент Label (англ. Label — метка) обозначенный иконкой А , и разместим его на форме, нажав левую кнопку мыши на форме в том месте, где должен располагаться левый верхний угол подписи. На форме появится новый элемент управления (см. рис. 12.2). Элемент управления можно перемещать по форме с помощью мыши и изменять его размеры, ^сли компонент не обведен рамкой, то его нужно выделить, нажав над ним левую кнопку мыши.
Рис. 12.2. Компонент Label 231
Часть III. Методика создания программ в Delphi Object Inspector Labell Properties
Events
Align Alignment ElAnchors AutoSize BiDiMode Caption Color S Constraints Cursor DragCusor DragKind DragMode Enabled FocusContiql [B Font
jalNone ! taLefUustify j[akLeft,akTop] (True IbdLeftfoRight Первое число:| incffiKnFace itTSizeConstraints) UrDefault i crDrag jdkDragI dmManual
Теперь можно изменить надпись на метке. Для этого следует воспользоваться Инспектором объектов (от англ. Object Inspector — Инспектор объектов), в котором представлены все свойства активного компонента (см. рис. 12.3). Инспектор объектов находится в левой части экрана. Если данное окно не отображено на экране, то его можно вызвать с помощью пункта главного меню view->Object Inspector или нажав клавишу F11.
Инспектор объектов представляет собой таблицу из двух столбцов — в левом находятся названия свойств текущего компонента, а в правом — значения этих свойств. Все изменения, внесенные в программу с помощью Инспектора объектов, j (f Font) будут сохранены средой. Подпись, вывоiAII shown димая на метке, содержится в свойстве Рис. 12.3. Инспектор объектов Caption (англ. Caption — заголовок). При изменении данного свойства соответствующим образом изменится подпись на метке. Итак, изменим значение свойства Caption на «Первое число:». Установив положение метки, можно переходить к созданию первой строки ввода. Для этого необходимо расположить на форме компонент Edit (англ. Edit — редактировать), помеченный иконкой РЕП на странице standard Палитры компонентов. Размещение компонента Edit производится аналогично компоненту Label. Текст, вводимый пользователем с помощью этого компонента во время выполнения программы, доступен через свойство Text. Данное свойстэо доступно и на этапе проектирования, то есть его значение можно изменить с помощью Инспектора объекта. Изначально значением свойства Text является название компонента, в нашем случае, это «Editl». Это значение не подходит по смыслу задачи, так как в текстовые поля калькулятора должны вводиться числовые значения, а не текстовые. Поэтому значение свойства Text следует изменить, например, на «О». Аналогичным образом добавляются текстовые метки Label с подписями «Второе число:» и «Результат:», а также строки ввода для второго операнда и результата. Теперь добавим на форму кнопки выбора действия. Компонент, соответствующий данному элементу управления, также расположен на странице Standard Палитры компонентов и помечен иконкой шэ|. Надписью, отображаемой на кнопке, управляет свойство Caption компонента. 232
Глава 12. Общна принципы программирования в Delphi
Обратим внимание на выпадающий список в верхней части Инспектора объектов, с помощью которого можно сделать некоторый компонент активным по его имени, даже если его полностью перекрывают другие компоненты. Имя компонента формируется автоматически при добавлении компонента на форму, заносится в свойство Name компонентов и может быть изменено в обычном порядке через Инспектор объектов. Автоматическое формирование имени компонента строится на основе названия компонента и его порядкового номера на форме. Например, при добавлении компонента Label, ему будет присвоено имя Label 1. Если компоненты добавлялись к форме в описанной последовательности, то они имеют следующие имена: Компонент
Название
Подпись «Первое число:»
Label 1
Подпись «Второе число:»
Label2
Подпись «Результат:»
Labels
Строка ввода первого операнда
Editl
Строка ввода второго операнда
Edit2
Строка вывода результата
Edits
Кнопка «+»
Buttonl
Кнопка «-»
Button2
Кнопка «*»
Buttons
Кнопка «/«
Button4
Таким образом, интерфейс программы сформирован. Если запустить программу на выполнение, то в строки ввода можно будет вводить значения, на кнопки можно будет нажимать 34 , но программа не будет иметь необходимой функциональности. Соответственно, на этом и заканчивается визуальное проектирование приложения и начинается собственно программирование.
12.2. Автоматическое создание программного кода Во время визуального построения интерфейса приложения среда Delphi автоматически формирует два файла — файл описания формы (имеет расширение dfm), в котором содержится описание интер" Заметим, что элементы управления, созданные с помощью Delphi, являются на самом деле элементами управления Windows, то есть полностью поддерживают поведение, свойственное им в конкретной версии Windows. Например, строки ввода, созданные в нашей программе, поддерживают работу с буфером обмена.
233
Часть III. Методика создания программ в Delphi
фейсных элементов, расположенных на форме, и файл модуля формы (имеет расширение pas), в котором располагается описание класса формы — наследника класса TForm. Файл описания формы используется системой для внутренних целей, а именно, для хранения значений свойств компонентов, которые представлены в Инспекторе объектов во время визуального построения формы. Изменение данного файла практически никогда не требуется, а основная часть работы происходит с модулем формы. Класс формы является логическим отображением формы и содержит, например, ссылки на все интерфейсные компоненты, добавленные на этапе визуального проектирования. Это крайне важно, так как позволяет обратиться к элементам интерфейса как к свойствам класса через соответствующие классы Delphi, являющиеся оболочками интерфейсных элементов Windows. Чтобы изучить описание этого класса, следует выбрать пункт главного меню view->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(<Родительский класс>, <Интерфейс 1>, . . . , <Интерфейс N>); Оаголовки методов, описанные в интерфейсах> 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>; <3аголовок метода N>;
end;
.
Механизм интерфейсов позволяет частично осуществлять множественное наследование, то есть создание класса на основе одного родительского класса и нескольких интерфейс'ов. Для этого стандартное описание класса расширено, и в его заголовке после родительского класса указывается список реализуемых интерфейсов: Туре <Название класса> = class (<Родительский класО, <Интерфейс 1>, ..., <Интерфейс <Свойства> _ <3аголовки методов, описанные в интерфейсах> 273
Часть IV. Компоненты и их использование
Оаголовки собственных методов> Оаголовки переопределенных методов родительского класса> 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
Красный ^Оранжевый) Желтый] Зеленый | Голубой ] Синий) Ч <•* Светлый
Г" Темный
С Нейтральный
Красный | Оранжевый) Желтый) Зеленый] Голубой] Синий) «И I >l <~ Светлый
(* Темный
С Нейтральный
Рис. 14.10. Компоненты постраничного представления элементов управления
Расположение закладок можно изменить с помощью перечислимого свойства TabPosition: property TabPosition: TTabPosition; Type TTabPosition = (tpTop, tpBottom, tpLeft, tpRight);
Это свойство может принимать значения: tpTop
закладки расположены вдоль верхней стороны контейнера, как показано на рисунке; tpBottom..закладки расположены вдоль нижней стороны контейнера; tpLeft закладки расположены вдоль левой стороны контейнера; tpRight . закладки расположены вдоль правой стороны контейнера. Также возможно расположение закладок в несколько рядов, если свойство MultiLine имеет значение True. Более того, если закладки расположены вдоль одной из боковых сторон компонента, то это свойство всегда должно иметь значение True. . . property M u l t i L i n e :
Boolean;
Размер закладок выбирается автоматически, однако может быть установлен разработчиком с помощью целочисленных свойств TabWidth (ширина закладки) и TabHeight (высота закладки): property TabWidth: Smallint; property T a b H e i g h t : S m a l l i n t ;
Визуальное представление закладок можно изменить, используя перечислимое свойство style: property S t y l e : TTabStyle; Type T T a b S t y l e = ( t s T a b s , t s B u t t o n s , t s F l a t B u t t o n s ) ;
Это свойство может принимать следующие значения: tsTabs обычные закладки; tsButtons закладки в виде трехмерных кнопок; tsFlatButtons . закладки в виде плоских кнопок. 293
Часть IV. Компоненты и их использование
Во время выполнения программы или визуальной разработки номер активной закладки можно определить или изменить с помощью целочисленного свойства Tablndex (закладки нумеруются от нуля): property T a b l n d e x : Integer;
При смене активной закладки вызывается обработчик сообщения O n C h a n g e , если он определен для данного компонента. Обработчик OnChange представляет собой процедуру с одним параметром — ссылкой на компонент-источник, в котором произошло событие: property OnChange: TNotifyEvent; Type TNotifyEvent = procedure (Sender: TObject) Of Object;
Теперь рассмотрим отличия компонентов TabControl и PageControl друг от друга. Более простым в использовании является компонент PageControl, который уже во время разработки программы предоставляет набор страниц, на которых располагаются группы элементов управления. При добавлении PageControl на форму в нем не содержится страниц для размещения элементов управления и, соответственно, нет закладок. Для добавления очередной страницы в процессе визуального, построения программы следует воспользоваться контекстным меню компонента, вызываемым при нажатии правой кнопки мыши, и выбрать пункт New Page. Для удаления страницы предназначен пункт меню Delete Page. Каждая страница, добавляемая в компонент PageControl, является экземпляром класса TTabSheet, который в свою очередь унаследован от TWinControl (см. рис. 14.11). Наиболее важным свойством компонента TTabSheet является свойство Caption, определяющее заголовок страницы, который выводится на закладке. Элементы управления, добавляемые на страницы, принадлежат этим страницам, в том числе и визуально, поэтому компонент PageControl имеет возможность управлять видимостью этих элементов управления через видимость страниц. PageControt
TabSheetl
TabSheet2
TabSheel Рис. 14.11. Компонент PageControl 294
Глава 14. Визуальные компоненты
Компонент TabControl построен по другому принципу и представляет собой только набор закладок и место для расположения элементов управления. Определение видимости элемента управления в каждый момент времени выполнения является функцией программы. Анализ может быть основан на свойстве Tablndex, рассмотренном выше, и производиться в обработчике события O n C h a n g e . На первый взгляд, такая конструкция существенно проигрывает в сравнении с компонентом PageControl, однако имеются и некоторые положительные стороны такого подхода. Во-первых, компонент TabControl может быть использован не только для группировки компонентов, а еще и для выбора одного значения из списка, представленного закладками. Во-вторых, появляется интересная возможность активности нескольких страниц одновременно (см. рис. 14.12). Для того, чтобы пользователь имел возможность выбора нескольких страниц, свойство MultiSelect должно иметь значение True. property M u l t i S e l e c t :
Boolean;
-Inixj:
Рис. 14.12. Выбор нескольких закладок компонента TabControl
Использование множественного выбора возможно только при использовании стилей закладок tsButtons или tsFlatButtons. Так как компонент TabControl не содержит каких-либо реальных страниц, подписи, выводимые на закладках, задаются с помощью свойства Tabs: property Tabs:
TStrings;
14.2.5. Пример использования контейнеров На рис. 14.13 изображено окно Project Options Интегрированной среды разработчика Delphi, вызываемое с помощью пункта главного меню Project-X)ptions. Реализуем программу, главная форма которой аналогична данному диалогу. Для простоты добавим в окно только те элементы управления, которые являются контейнерами, причем только для закладки Compiler. \ 295
Часть IV. Компоненты и их использование Pro ject Options (oi Project 1 л>» :• DJectories/Concftionals / > : : Forms | Appfcatkm ••'. ' г Code generation :: 1 P Optimization
"™ *
:•:
Compiler •
Version info Packages j Compfer Messages . | Linker
} ;•" Runtime etfots i !•? Range checking i p' 1/0 checking I p Overflow checking
! p" Stacjs frames .! |7 PentRtfrvsafeFDlV | fg"'"T"*Vj Record field afignment : ."• ! г" Debugging"1""- " — ^J i js? Debug information "y ! .-_^4^^4^„^1. !. г" Syntax options ™ — " I 1 P" Local symbols 1 fP" Refeteoce »ifo If? Strict i/ar-stitngs f? Definitions onlii i Г* Complete bodean eval i i P Assertions I P " Extended syntax | | Г UseDebugOCUs i f~ B$>ed @ opeia(ot .1 P" Opencatamrtws ! fl? Huge jtrings 117 Assignable typed constants
;гшз; л:;-
:
OK
Cancel
i ... . I 1' •
ЫФ
)
Рис. 14.13. Диалог Project Options ИСР Delphi
Как видно, большую часть диалога занимает компонент-контейнер постраничного расположения элементов управления. Для добавления такого контейнера можно воспользоваться компонентами PageControl или TabControl. Для начала попробуем использовать PageControl. Запустим среду разработчика Delphi и откроем новый проект. Установим в Инспекторе объектов размеры главной формы с помощью свойств W i d t h (значение 424) и Height (значение 399). Установим значение свойства Caption (заголовок формы): «Project Options for Projectl.exe». Разместим на форме компонент PageControl с закладки W i n 3 2 Палитры компонентов и установим его положение и размеры с помощью свойств L e f t (значение 6), Тор (значение 8), width (значение 408), Height (значение 327). Визуальный построитель автоматически присвоит этому компоненту название PageControll. С помощью пункта Add Раде контекстного меню, вызываемого при нажатии на правую кнопку мыши, добавим в компонент PageControll восемь страниц. Переключаясь между страницами с помощью закладок, присвоим следующие подписи закладкам с помощью свойств Caption каждой страницы: «Directories/Conditionals», «Version Info», «Packages», «Forms», «Application», «Compiler», «Compiler'Messages», «Linker». Промежуточный результат разработки показан на рис. 14.14. Как видно, закладки на добавленном нами компоненте PageControll расположены в один ряд, а при наведении мыши на закладку она не подсвечивается, как это происходит у настоящего диалога Project Options. Для исправления такой ситуации сделаем активным компонент PageControll, нажав кнопку мыши над его верхней частью, где распо296
Глава 14. Визуальные компоненты ддядтаи^р.^ляда.. Packages 1 Fcum* j Application j Сотрйи j Compfct M«se0es t-hk» j < j » j
Рис. 74.14. Промежуточный результат разработки
ложены закладки (иначе, будет выбрана текущая страница компонента). Изменим в Инспекторе объектов значения свойств MultiLine (на True) и HotTrack (также на True). Сделаем активной ту страницу компонента PageControll, которая соответствует закладке Compiler, выбрав эту закладку мышью. Расположим на данной странице четыре компонента GroupBox с закладки standard — два в левой части страницы и два в правой. Им автоматически присваиваются названия GroupBoxl, GroupBox2, GroupBoxS и GroupBox4. Для соответствующего расположения контейнеров внутри компонента PageControll настроим их свойства Left, Top, W i d t h и Height, а для изменения заголовков изменим свойства Caption следующим образом: Компонент
Left
GroupBoxl GroupBox2 GroupBoxS GroupBox4
Top
Width
Height
Caption
5
4
187
107
Code Generation
5
120
187
129
System options
202
4
185
85
Runtime errors
185
117
Debugging
202
100
Запустим программу на выполнение с помощью клавиши F9 и попробуем сделать активной другую страницу диалога. При смене активной страницы ее закладка выделяется, а элементы управления, расположенные на ней (в данном случае, это компоненты GroupBoxl, GroupBox2, СгоирВохЗ и GroupBox4), становятся невидимыми. Аналогичным образом можно построить такой диалог и на основе компонента T a b C o n t r o l , однако автоматического переключения между группами компонентов при использовании TabControl происходить не 297
Часть IV. Компоненты и их использование
будет, поэтому в программе должен быть предусмотрен обработчик события OnChange этого компонента. Откроем новый проект, настроим размеры формы и ее заголовок, а затем добавим на форму компонент TabControl. Название нового компонента формируется автоматически: «TabControll». Вместо добавления страниц в компонент откроем редактор свойства Tabs в Инспекторе объектов и введем в нем следующие строки: Directories/Conditionals Version Info Packages Forms Application
Compiler Compiler Messages Linker Настроим свойства MultiLine и HotTrack компонента TabControll, и добавим на его поверхность четыре компонента GroupBox. Установим свойству Tablndex значение 5, что соответствует номеру закладки Compiler, в результате чего данная закладка станет активной. Запустив программу на выполнение, убедимся, что при смене активной страницы элементы управления, расположенные на ней, не теряют видимость, хотя в остальном компонент TabControl ведет себя аналогично PageControl. Сделаем активным компонент TabControll и переключимся в Инспекторе объектов на страницу Events. Двойным щелчком мыши напротив события OnChange создадим обработчик этого события и реализуем его следующим образом: Procedure T F o r m l . T a b C o n t r o l l C h a n g e ( S e n d e r : T O b j e c t ) ; begin G r o u p B o x l . V i s i b l e := ( T a b C o n t r o l l . T a b l n d e x = 5 ) ; G r o u p B o x 2 . V i s i b l e := ( T a b C o n t r o l l . T a b l n d e x = 5 ) ; G r o u p B o x S . V i s i b l e := ( T a b C o n t r o l l . T a b l n d e x = 5 ) ; G r o u p B o x 4 . V i s i b l e := ( T a b C o n t r o l l . T a b l n d e x = 5 ) ; end;
Поясним действия, выполняемые обработчиком события. Свойство T a b l n d e x компонента TabControll содержит номер активной страницы (точнее, номер закладки, так как в компонентах TabControl нет физических страниц). В соответствии со списком подписей к закладкам страница Compiler имеет номер 5 (напомним, что закладки нумеруются от нуля). 298
Глава 14. Визуальные компоненты
. Difectoriet/Corxftionak Appfcatigrh
[
; Code Geneietion
1
^"["P!1*1
Vetston Info j!
j
\
Packages
Compiler Mewages
j
For. Link
RLtitime euois
Рис. 74.15. Результат разработки Соответственно логическое выражение (TabControll.Tablndex = 5) принимает значение True, если активной является закладка Compiler, и False во всех остальных случаях. Значение данного выражения присваивается свойству видимости Visible каждого компонента, «расположенного» на странице Compiler. Таким образом, при каждом изменении активной закладки вызывается обработчик OnChange, в котором определяется видимость компонентов GroupBoxl, GroupBox2, GroupBoxS И GroupBox4. Если бы в нашей программе была предусмотрена поддержка других страниц, то в обработчике события OnChange присутствовала бы логика управления видимостью компонентов, расположенных на этих страницах. Допустим, на странице Linker (закладка номер 7) расположен компонент GroupBoxS. Тогда обработчик OnChange будет выглядеть следующим образом: Procedure T F o r m l . T a b C o n t r o l l C h a n g e ( S e n d e r : T O b j e c t ) ; begin GroupBoxl.Visible (TabControll.Tablndex = 5 ) ; GroupBox2.Visible (TabControll.Tablndex = 5); (TabControll.Tablndex = 5); GroupBoxS.Visible GroupBox4.Visible (TabControll.Tablndex = 5); GroupBoxS.Visible (TabControll.Tablndex = 1) ; end;
Заметим, что при создании компонента TabControl событие OnChange не возникает, поэтому видимость компонентов, расположенных внутри TabControl, должна соответствовать текущей странице, то есть странице, номер которой находится в свойстве Tablndex. Иначе корректное представление компонентов будет обеспечено только после первой смены активной страницы, так как будет вызван обработчик OnChange, а такая ситуация недопустима.
299
Часть IV. Компоненты и их использование
В этом случае обработчик события можно вызвать в момент инициализации формы, то есть в обработчике события формы OnActivate. Так как свойство OnChange компонента TabControl является процедурным типом, то вызов этого обработчика может выглядеть так: Procedure TForml.FormActivate(Sender : TObject); begin TabControll.OnChange(self) ; end; В качестве параметра события передается ссылка Self, которая в данном случае определяет форму, на которой и находится компонент TabControll.
14.3. Компоненты для отображения информации 14.3.1. Виды компонентов для отображения информации Данная группа визуальных компонентов предназначена для отображения следующей текстовой и графической информации в окнах программы: » » * » »
текстовые строки; геометрические фигуры; диаграммы и графики; изображения, считанные из графических файлов; видеоклипы.
В настоящем разделе рассмотрены стандартные компоненты, перечисленные в табл. 14.6.
14.3.2. Текстовые метки Текстовые метки используются для подписи других элементов управления, расположенных на форме и представлены в Delphi компонентами Label (закладка standard) и StaticText (закладка Additional). Как визуальные компоненты, Label и StaticText поддерживают свойства расположения, выравнивания и видимости Left, Top, Многострочны|)компонент Label: Labell |statieText с W i d t h , Height, A l i g n , A n c h o r s И i рам кой Visible, а также добавляют множество ОДНОСТРОЧНЫЙ' собственных, приведенных в таблицах Рис. 14.16. Текстовые метки 300
14.7, 14.8 и 14.9.
Глава 14. Визуальные компоненты Компоненты для отображения информации Компонент ;
Aj
Название
Таблица 14.6 Описание
Label
Текстовая метка
StaticText
Текстовая метка
ProgressBar
Индикатор процесса
Animate
Проигрыватель видеофайлов
Bevel
Рамка или отдельные линии рамки
;^°j
Shape
Геометрическая фигура
QI
Image
Изображение из графического файла
Chart
Диаграмма
PaintBox
Область отображения для программного вывода графической информации
И ГййГ -{
ш Ш Ш
Общие свойства компонентов Label и StaticText Свойство
Тип
Таблица 14.7 Описание
Caption
String
Color
TColor
Цвет фоновой области, на которой отображается строка
TFont
Шрифт, которым выводится строка
Font
Строка, выводимая на экран
Указывает, следует ли (значение False) компоненту автоматически изменять свой размер при изменении выводимой строки или характеристик вывода (например, размера шрифта). AutoSize
Boolean
Alignment
TAIignment
При использовании значения True в свойстве Wordwrap компонента Label (разбивать строку по словам) более приоритетно изменение высоты. Увеличение ширины Label происходитТголько если очередное слово имеет ширину, большую ширины метки Label. Иначе при вводе очередного слова оно переносится на новую строку Указывает способ выравнивания текста внутри компонента. Может иметь следующие значения: taLeftJustify (выравнивать строку по левому краю — установлено по умолчанию), taCenter (выравнивание по центру) и taRightJustify (выравнивание по правому краю)
Специфические свойства компонента Label
Таблица 14.8
Тип
Описание
Layout
TTextLayout
Указывает выравнивание текста, выводимого меткой, по высоте и может принимать три значения: tITop (текст выровнен по верхнему краю), tICenter (текст выровнен по центру), tIBottom (выравнивание по нижнему краю). Использование свойства имеет смысл при отключении функции автоматического подбора размеров (установка свойству AutoSize значения False)
Transparent
Boolean
Отключает прорисовку фона метки вне зависимости от значения свойства Color
Свойство
301
Часть IV. Компоненты и их использование Специфические свойства компонента StaticText
Таблица 14.9
Свойство
Тип
BorderStyle
TstaticBorderStyle
Определяет наличие и вид рамки, выводимой вокруг метки. Может принимать следующие значения: sbsNone (рамка отсутствует), sbsSingle (простая рамка из тонкой линии) и sbsSunken (трехмерная рамка)
TBevelCut
Определяют соответственно наличие и вид внешней и внутренней рамки. Могут принимать следующие значения: bvNone (рамка отсутствует), bvLowered (вдавленная рамка), bvRaised (рамка выпуклая), bvSpace (рамка не выводится, но место под нее резервируется, если свойство BevelKind = bkTile, иначе рамка выпуклая)
TBevelKind
Определяет стиль внешней и внутренней рамок и может принимать следующие значения: bkNone (рамки не выводятся), bkTile (четкое выделение рамки), bkSoft (менее контрастная по сравнению с bkTile рамка), bkFlat (рамки выводятся без эффекта трехмерности, вне зависимости от значений свойств BevelOuter и Bevellnner)
BevelOuter Bevellnner
BevelKind
Описание
Существенное отличие текстовых меток как классов Delphi заключается в том, что метка Label является наследником класса TGraphicControl, а метка StaticText — наследником компонента-оболочки TWinControl. Соответственно, при использовании большого количества текстовых меток, одновременно расположенных на экране, следует учитывать, что компонент StaticText является более ресурсоемким, и, по возможности, выбирать метки Label для разработки интерфейса. Однако это не является общим правилом, так как компонент StaticText в некоторых обстоятельствах имеет преимущества перед Label. Одно из таких преимуществ — наличие рамки, выводимой вокруг текста, что позволяет отделить метку от других элементов интерфейса. Второе вытекает из неоконности компонента Label. Напомним, что наследники класса TGraphicControl выводят свое изображение на область отображения компонента-контейнера, которому они визуально принадлежат (см. рис. 14.17), тогда как оконные компоненты находятся визуально выше своего контейнера. Таким образом, любой оконный компонент будет перекрывать изображение метки Label, если они находятся в одном и том же контейнере, и их видимые области пересекаются. На рис. 14.16 показана такая ситуация: оконный компонент StaticTextl перекрывает неоконные метки Labell и Label2. Обе текстовые метки имеют возможность вывода многострочного текста, однако настройка разбиения на строки производится разными методами. Компонент Label управляет возможностью разбиения длин51 ной строки на несколько с помощью логического свойства Wordwrap: property W o r d w r a p : sl
Boolean;
Строки в текстовых метках разбиваются по словам, причем разделителем слов считается только пробел.
302
Глава 14. Визуальные компоненты
Рис. 14.17. Правило перекрытия областей видимости оконных компонентов и компонентов-оболочек
Если свойство Wordwrap имеет значение False, то строка, выводимая на экран, не разбивается. Если при этом свойство A u t o S i z e имеет значение True, то есть метка должна автоматически изменять свой размер, то увеличивается ее ширина. В противном случае строка урезается по размеру метки, и на экране видна только часть очередного слова (см. рис. 14.18). AutoSize - Fatse
Длинная строк^ '{Длинная строк^ AutoSize = True Рис. 14.18. Использование свойства AutoSize
Разбиение строки в компоненте staticText производится всегда, причем вне зависимости от размеров метки и значения свойства A u t o S i z e на экран выводятся только целые слова, а те, которые не подходят по ширине для текущей строки, переносятся на следующую.
14.3.3.
Компоненты-индикаторы
Для представления одного или нескольких числовых параметров в графическом виде в Delphi реализованы компонент-индикатор ProgressBar, расположенный на странице Win32 палитры компонентов, и компонентдиаграмма Chart (страница Additional). 303
Часть IV. Компоненты и их использование
Компонент ProgressBar Компонент ProgressBar предназначен для отображения значения какого-либо одного целочисленного параметра как части некоторого интервала. Визуально данный компонент представляет из себя (см. рис. 14.19) прямоугольник, символизирующий интервал, причем часть его заполнена синим цветом, что символизирует значение параметра. • • • • • » I
I Рис. 14.19. Компонент ProgressBar
Программно интервал задается целочисленными свойствами Min и Мах компонента ProgressBar, а значение параметра — целочисленным свойством Position: property M i n : I n t e g e r ; property M a x : I n t e g e r ; property P o s i t i o n : I n t e g e r ;
Заливка области компонента может отображаться в горизонтальном направлении слева направо или и вертикальном — сверху вниз. Метод вывода задается свойством Orientation, которое может принимать два значения: pbHorizontal (горизонтальное расположение) и pbvertical (вертикальное расположение). property O r i e n t a t i o n :
TProgressBarOrientation;
Также с помощью логического свойства Smooth поддерживается возможность вывода непрерывной заливки (значение True), либо состоящей из прямоугольников (значение False). ,. property S m o o t h : B o o l e a n ;
Для изменения положения заливки во время работы программы можно использовать свойство Position, при изменении значения которого компонент будет автоматически перерисован, например: ProgressBarl.Position := ProgressBarl.Position + 10; Однако увеличение значения свойства Position является самой частой операцией над индикатором. Поэтому в компоненте предусмотрен 304
Глава 14. Визуальные компоненты
метод Steplt, изменяющий значение свойства P o s i t i o n на значение, заданное целочисленным свойством Step: property Step: I n t e g e r ; procedure S t e p l t ;
Если свойство step имеет отрицательное значение, то значение свойства Position будет уменьшено. Для смещения индикатора на произвольное значение можно использовать метод StepBy: procedure
StepBy(Delta:
Integer);
Компонент Chart — построение диаграмм и графиков Компонент Chart представляет собой область для вывода диаграмм и графиков множества видов одновременно (см. рис. 14.20). Этот компонент содержит следующие визуальные составляющие: * фон компонента с возможностью заполнения градиентной заливкой; » заголовок компонента; * оси аргументов и значений с заголовками и подписями; » легенда. Все характеристики перечисленных составляющих, например, стили линий, шрифты и заливки, могут быть настроены как в процессе визуального построения приложения, так и во время его работы. Компонент Chart имеет множество свойств и методов, в результате чего является очень гибким, и реализует практически все необходимые для вывода диаграмм и графиков функции.
Рис. 14.20. Компонент Chart 305
Часть IV. Компоненты и их использование
14.3.4. Изображения геометрических фигур Для изображения геометрических фигур без их специальной программной прорисовки52 предусмотрены компоненты Bevel и Shape, расположенные на странице A d d i t i o n a l . Эти два компонента, особенно Shape, являются, видимо, самым ярким примером предназначения класса T G r a p h i c C o n t r o l . Они оба выполняют несложные действия, выводя свое изображение на область отображения контейнера, в котором находятся, используя свое свойство Canvas и вызывая методы этого объекта. Отображение разделительных рамок. Компонент Bevel Компонент Bevel предназначен для визуального разделения элементов управления на экране (см. рис. 14.21) и представляет собой трехмерную рамку — вдавленную или выпуклую в зависимости от перечислимого свойства Style, которое может принимать два значения: * bsLowered — линии компонента вдавлены, как у горизонтальной линии на рис. 14.21; » bsRaised — линии компонента выпуклые, как у вертикальной рамки на рис. 14.21. Несмотря на то, что компонент Bevel занимает место на экране в соответствии со свойствами width и Height, как и остальные визуальные компоненты, его внутренняя область прозрачна, а вместо рамки может выводиться только одна линия. При этом, если с помощью свойства Cursor компоненту задан какой-либо нестандартный курсор, то он будет отображаться во всей внутренней области (см. рис. 14.21).
Компонент Bevel является
Г Вопрос!
И
г вопроса
П наследником класса:
?* Вопрос 3
I
П7 TObject
Г' Вопрос 4
\!
ТУ TComponent
Г BonpocS
M Г
Вопросб
TVmControl TGts?)hicCoritrol
Курсор мыши
Рис. 14.21. Компонент Bevel
Имеется в виду отсутствие необходимости реализации такой "прорисовки разработчиком прикладного программного продукта. Сами компоненты естественным образом выводят свое изображение.
306
Глава 14. Визуальные компоненты
Внешний вид компонента Bevel определяется перечислимым свойством Shape (англ. Shape -фигура): property Shape: TBevelShape; Type TBevelShape = (bsBox, bsFrame, bsTopLine, bsBottomLine, bsLeftLine, bsRightLine, bsSpacer);
Возможные значения свойства Shape описаны в табл. 14.10. Возможные значения свойства Shape компонента Bevel Значение
Таблица 14.10
Выводимая геометрическая фигура
bsBox
Компонент обводится трехмерной рамкой со всех сторон так, что сам выглядит трехмерным
bsFrame
Компонент обводится трехмерной рамкой со всех сторон
bsTopLine
Выводится только верхняя линия компонента
bsBottomLine
Выводится только нижняя линия компонента
bsLeftLine
Выводится только левая линия компонента
bsRightLine
Выводится только правая линия компонента
bsSpacer
Компонент обводится пунктирной линией
Изображение графических примитивов. Компонент Shape Для изображения графических примитивов предназначен компонент Shape, выводящий на экран различные геометрические фигуры в зависимости от перечислимого свойства Shape: • property Shape: TShapeType; Type TShapeType = ( s t R e c t a n g l e , stSquare, stRoundRect, stRoundSquare, stEllipse, stCircle);
Возможные значения свойства Shape описаны в табл. 14.11. Возможные значения свойства Shape компонента Shape Значение
Таблица 14.11
Вид компонента
stCircle
Круг
stEllipse
Эллипс
stRectangle
Прямоугольник (значение устанавливается по умолчанию)
stSquare
Квадрат
stRoundRect
Прямоугольник со скругленными краями
StRoundSquare
Квадрат со скругленными краями
Все фигуры, выводимые компонентом Shape, имеют стиль линии и заливку, устанавливаемые, соответственно, свойствами Реп и Brush: property P e n : TPen; property B r u s h : TBrush; 307
Часть IV. Компоненты и их использование
Если с помощью свойства B r u s h установлена заливка, отличная от bsSolid (сплошная заливка), то компонент становится частично или полностью (если стиль заливки bsClear) прозрачным. Например, на рис. 14.22 текстовая метка Label видна через внутренние области обоих компонентов Shape. ютка Label является легковеснтцм компоне /наследована от кл TGraphicContool и выво£ ажениедмобласть 01 родительского конте
Рис. 14.22. Компонент Shape
Заметим, что если вместо метки L a b e l использовать компонент StaticText, то строка, выводимая им, будет видна вне зависимости от характеристик заливки геометрических фигур. Это связано с тем, что компоненты Shape и Label выводятся в данном случае на область отображения формы, то есть находятся «в одном слое», и их взаимное перекрытие определяется порядком их добавления в контейнер (чем позже добавлен — тем выше находится). Компонент StaticText имеет собственное окно, находящееся над контейнером, а, следовательно, перекрывает все, что находится на области отображения контейнера.
14.3.5. Вывод сложной графической информации Delphi поддерживает: » вывод на экран изображений, считываемых из графических файлов, с помощью компонента Image (страница Additional Палитры компонентов); • вывод видеоклипов, считываемых из avi-файлов, не использующих сжатие изображения, с помощью компонента Animate (страница Win32); » вывод любого изображения, формируемого программой с помощью компонента PaintBox (страница System). Вывод изображений из графических файлов Компонент Image основан на использовании класса TPicture, рассмотренного нами при обсуждении методов и представления хранения графической информации. Компонент image и имеет удобный для разработчика механизм настройки и использует механизм вывода графической информации, рассмотренный нами в гл. 10). После расположения компонента Image на форме изображение, которое следует вывести на 308
Глава 14. Визуальные компоненты
Рис. 14.23. Компонент Image
экран загружается с помощью соответствующего диалога в Инспекторе объектов (см. рис. 14.23). После этого изображение сохраняется в файл формы и доступно при последующей работе с проектом без исходного графического файла. Диалог вызывается при попытке редактирования свойства Picture: property P i c t u r e : T P i c t u r e ;
Во время работы программы изображение может быть загружено из файла вызовом метода LoadFromFile свойства Picture: ImageI.Picture.LoadFromFile(Nfantasy.jpg'); Свойства компонента image направлены на управление изображением в пределах охватывающего прямоугольника компонента и предоставляют возможность центрирования изображения (свойство Center), a также его растяжения (свойство Stretch): property Center: Boolean; property s t r e t c h : Boolean;
Если размер изображения превышает размеры охватывающего прямоугольника компонента, а свойство Stretch имеет значение False, то изображение будет урезано. Для контроля пропорционального изменения ширины и высоты изображения при растяжении предусмотрено свойство Proportional, ука-
309
Часть IV. Компоненты и их использование
зывающее, следует ли (значение True) сохранять изначальное соотношение ширины и высоты: Property Proportional:
Boolean;
Если данная возможность используется, то изображение увеличивается до того момента, когда его ширина либо высота достигнут, соответственно, ширины или высоты охватывающего прямоугольника компонента. На рис. 14.24 представлена форма с двумя компонентами image, в которые загружено одно и то же изображение, причем его размеры существенно превышают размеры компонентов, в результате чего изображение автоматически масштабируется (уменьшается). Свойству Proportional обоих компонентов установлено значение True, поэтому изображение занимает не всю их область, а только максимально возможную при условии сохранения пропорциональности ширины и высоты.
Рис. 14.24. Автоматическое масштабирование изображение
Отметим, что свойству Center обоих компонентов также присвоено значение True, поэтому изображение центрируется внутри компонентов по ширине (в левом компоненте) и высоте (в правом). Если бы' данное свойство имело значение False, то изображение в левом компоненте находилось бы у его левой границы, а в правом — у его верхней границы. Вывод видеоклипов Компонент Animate аналогичен компоненту Image, но на экран выводится не просто изображение из графического файла, а видеоклип, название файла с которым хранится в свойстве FileName: property FileName:
310
String;
Глава 14. Визуальные компоненты
Рис. 14.25. Компонент Animate
Клип, в отличие от изображения компонента image, не сохраняется вместе с формой, так как может иметь значительный размер, поэтому во время выполнения программы файл, на который указывает свойство FileName, должен присутствовать на диске. Свойство FileName также может быть изменено во время выполнения программы, что вызовет загрузку файла с клипом, либо исключительную ситуацию, если такого файла не существует, или его формат не соответствует поддерживаемому компонентом Animate: Animatel.FileName
Л
Е:\WINNT\clock.avi';
Для автоматического запуска клипа после старта программы используется свойство Active: property A c t i v e : Boolean; Клип будет проигран, если данное свойство имеет значение True, иначе для запуска клипа следует установить такое значение программно или воспользоваться методами компонента Animate, описанными ниже. Воспроизведение начинается с кадра, номер которого находится в свойстве StartFrame, и продолжается до кадра, номер которого находится в свойстве StopFrame. При загрузке файла значения этих свойств автоматически устанавливаются в минимально и максимально возможные значения, то есть будут воспроизведены все кадры ролика. Далее, клип может быть проигран еще несколько раз, в зависимости от значения целочисленного свойства Repetitions. Если данное свойство имеет нулевое значение, то количество повторений будет бесконечным.
311
Часть IV. Компоненты и их использование
Для программного управления воспроизведением клипа используются методы Play (проиграть клип), Reset (вернуться к началу клипа), Seek (переместиться на заданный кадр) и Stop (остановить воспроизведение). Метод Play проигрывает клип в интервале кадров, заданном параметрами FromFrame и ToFrame, несколько раз в соответствии с параметром Count: Procedure P l a y { F r o m F r a m e ,
ToFrame:
Word;
Count:
Integer);
Метод stop останавливает воспроизведение и не имеет параметров: Procedure Stop; Применение методов Reset и Seek имеет смысл при использовании свойства Active вместо методов Play и Stop. Метод Seek делает текущим кадр, заданный параметром Frame, а метод Reset делает текущим самый первый кадр анимации: Procedure S e e k ( F r a m e : Procedure R e s e t ;
Smalllnt);
Отображение созданных программно изображений И, наконец, компонент PaintBox представляет собой наследника класса TGraphicControl с одним событием OnPaint. В обработчике этого события должен быть размещен программный код, выводящий на область отображения, определяемое свойством Canvas данного класса, некоторое изображение. Соответственно такой компонент предназначен для вывода изображений, которые не могут быть построены стандартными средствами. Для примера реализуем обработчик события On P a i n t компонента PaintBox, выводящий на экран несложное изображение — прямоугольник с градиентной заливкой (см. рис. 14.26). Точка в левом верхнем углу прямоугольника будет иметь черный цвет, а в правом нижнем — красный. Точки, находящиеся на одинаковом удалении от левого верхнего угла компонента, будут иметь одинаковые цвета. Для построения такого изображения добавим на форму компонент PaintBox и настроим его свойство A n c h o r s таким образом, чтобы правый нижний угол компонента перемещался при изменении размеров формы. Для этого следует включить в множество A n c h o r s элементы akRight и akBottom. Выберем страницу Events в Инспекторе объектов, найдем в списке событие OnPaint и создадим его обработчик двойным нажатием мыши. Реализуем обработчик следующим образом53 (см. листинг 14.1) 53
Заметим, что это далеко не лучший алгоритм вывода подобных изображений, зато очень простой.
312
Глава 14. Визуальные компоненты
Рис. 14.26. Компонент PaintBox Листинг 14.1. Обработчик события OnPaint, рисущий прямоугольник с градиентной красной заливкой procedure TForml.PaintBoxlPaint(Sender: TObject); Заголовок обработчика формируется автоматически Var
х, у: Integer; Length2, MaxLength2: Integer; begin MaxLength2 := Sqr(PaintBoxl.Width) + Sqr(PaintBoxl.Height); {Расчет максимально возможного расстояния точки заливки от левого верхнего угла компонента} For х := 0 То PaintBoxl.Width — 1 Do For у : = 0 То PaintBoxl. Height — 1 Do begin (Перебор всех точек области отображения слева направо и сверху вниз} Length2 := Sqr(х) + Sqr(у); {Расчет расстояния текущей точки от левого верхнего угла компонента} PaintBoxl.Canvas.Pixels [х, у] := RGB( Round(Length2 * 255 / MaxLength2), 0, 0); {Установка цвета точке (х, у). Красная составляющая определяется соотношением квадрата расстояния данной точки от левого верхнего угла компонента и квадратом максимального расстояния. Остальные составляющие цвета заданы нулевыми} end; end;
313
Часть IV. Компоненты и их использование
При изменении размеров формы автоматически изменяются и размеры компонента PaintBox в соответствии со свойством Anchors. В этот момент заносятся новые значения в свойства width и Height данного компонента, и вызывается обработчик события On Paint, который на основе новых значений свойств Width и Height выводит на область отображения компонента PaintBox новое изображение. \ Использование виртуального экрана Алгоритм, использованный в обработчике (листинг 14.1), выводит изображение медленно. Причем процесс прорисовки виден не только при изменении размеров компонента, но и при затирании его каким-либо окном, например окном другого приложения. Что касается медленного вывода изображения при изменении размеров компонента, то с этим можно бороться только с помощью улучшения алгоритма, так как цвета всех точек должны быть рассчитаны заново. Быстрый ввод при затирании сторонним окном обеспечить несложно, так как цветовая информация уже была получена при предыдущем выводе. Для сохранения такой информации можно использовать так называемый виртуальный экран — растровое изображение TBitmap, на которое на самом деле будет выводиться градиентный прямоугольник, а вывод изображения в окно будет происходить уже с виртуального экрана. Создание виртуального экрана (экземпляра класса TBitmap) должно происходить при создании формы, а его разрушение при ее закрытии. Для этого следует реализовать соответственно обработчики событий формы OnCreate и OnClose. Ссылку на виртуальный экран можно расположить в секциях private, protected или public описательной части класса формы. В обработчике OnPaint компонента PaintBox поместим проверку на соответствие размеров компонента и виртуального экрана. Если размеры совпадают, то изображение виртуального экрана следует вывести на область отображения PaintBox, если нет, то нужно выполнить следующие действия: 1
» Изменение размеров виртуального экрана; * Формирование изображения на виртуальном экране; * Вывод изображения в компонент PaintBox. Модуль формы будет выглядеть, как показано на рис. 14.2.
314
Глава 14. Визуальные компоненты
Листинг 14.2. Модуль формы unit Unitl;
•
interface uses Windows, Messages, SysOtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls; i
type TForml = class(TForm) PaintBoxl: TPaintBox; procedure PaintBoxlPaint(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); private VirtualScreen: TBitmap; {Описание ссылки на виртуальный экран} public { Public declarations } end;
var Forml: TForml; implementation <$R *.dfm> procedure TForml.PaintBoxlPaint(Sender: TObject); Var x, y: Integer; Length2, MaxLength2: Integer; begin if (VirtualScreen.Width <> PaintBoxl.Width) OR (VirtualScreen.Height <> PaintBoxl.Height) then {Проверка несоответствия размеров виртуального экрана и компонента PaintBoxl} begin {Если размеры не одинаковы} VirtualScreen.Width := PaintBoxl.Width;
315
Часть IV. Компоненты и их использование VirtualScreen.Height := PaintBoxl.Height;
{Установка размеров виртуального экрана, соответствующих компоненту PaintBoxl} MaxLength2 := Sqr(VirtualScreen.Width) + Sqr(VirtualScreen.Height); For x := 0 To VirtualScreen.Width — 1 Do For у := 0 To VirtualScreen.Height — 1 Do begin Length2 := Sqr(x) + Sqr(y); VirtualScreen.Canvas.Pixels[x, y] := RGB ( Round(Length2 * 255 / MaxLength2), 0, 0); end; {Вывод изображения на виртуальный экран} end; Canvas.Draw(0, 0, VirtualScreen); {Копирование изображения с виртуального экрана на область отображени-я компонента PaintBoxl} end; procedure TForml.FormCreate(Sender: TObject); begin VirtualScreen := TBitmap.Create; end; {Обработчик события создания формы — создание виртуального экрана (экземпляра класса TBitmap)} procedure TForml.FormClose(Sender: TObject; var Action: TCloseAction); begin VirtualScreen.Free; end; {Обработчик события закрытия формы — разрушение вритуального экрана (экземпляра класса TBitmap)} end.
316
Глава 14. Визуальные компоненты
14.4. Компоненты-кнопки 14.4.1. ВИДЫ КНОПОК
Кнопки используются в подавляющем большинстве программных продуктов и предназначены для однократного выполнения какого-либо действия. Кнопки представляют собой поверхность с подписью, обычно обведенную трехмерной рамкой, которая может появляться при наведении на нее мыши (так. называемый flat-стиль). Также имеется возможность вывода на поверхность кнопки графического изображения. В Delphi данные элементы управления реализованы в трех вариантах: аи] — компонент Button на странице standard Палитры компонентов; SsDJ — компонент BitBtn на странице Additional; i£j| — компонент SpeedButton на странице Additional. Компоненты Button и BitBtn являются оболочками вокруг стандартного элемента управления Windows, причем класс TBitBtn унаследован от TButton (см. рис. 14.27). Компонент SpeedButton принадлежит к группе легковесных компонентов и имеет некоторые особенности в использовании относительно Button и BitBtn. TComponent TControl TWinConlrol
TGraphicControl
TButton
TSpeedButton
TBitBtn Рис. 14.27. Иерархия компонентов-кнопок
14.4.2. Кнопки Button и BitBtn Простые кнопки Button Самый простой компонент Button помимо свойств, унаследованных от базового класса элементов управления TControl, поддерживает свойство Caption, в котором хранится строка, выводимая в качестве подписи на кнопке, и Wordwrap, аналогичное одноименному свойству 317
Часть IV. Компоненты и их использование
Открыть Закрыть
Выход из I программы
Рис. 14.28. Компонент Button ' компонента Label, и указывающее на возможность разбиения подписи на несколько строк (см. рис. 14.28):
property Caption: String; property W o r d w r a p : Boolean; Шрифт, которым подпись выводится на поверхность кнопки, задается свойством Font соответствующего типа: property Font: T F o n t ; Несмотря на то, что в классе TFont описано свойство Color, теоретически определяющее цвет надписи, подпись на кнопке всегда выводится черным цветом, то есть цвет шрифта игнорируется. Использование компонентов-кнопок в программе основано на обработчике события Onclick, которое возникает при нажатии пользователем кнопки. Нажатие может быть произведено с помощью мыши или, для компонентов Button и BitBtn, с помощью клавиатуры (клавишами пробел или Enter), когда кнопка находится в фокусе. Обработчик данного события имеет тип TNotifyEvent, то есть в него не передается каких-либо параметров, кроме ссылки на компонент, который вызвал это событие: property Onclick:
TNotifyEvent;
Например, обработчик события нажатия на кнопку, который сдвигает кнопку, вызвавшую это событие, влево на десять точек, может выглядеть следующим образом: Procedure TForml.ButtonlClick(Sender: TObject); begin B u t t o n l . L e f t := B u t t o n l . L e f t -• 10; end; Кнопки BitBtn — кнопки с изображениями Компонент BitBtn наследует все описанные возможности Button и добавляет возможность вывода на поверхность кнопки растрового изображения, задаваемого свойством Glyph (см. рис. 14.29): property Glyph: TBitmap; 318
Глава 14. Визуальные компоненты
Файл с заданным именем существует. Заменить?
Рис. 14.29. Кнопки с изображением
Любая кнопка типа BitBtn может находиться в трех состояниях, в зависимости от действий, которые выполняются пользователем в данный момент: 1. Пользователь не воздействует на кнопку, но компонент допускает взаимодействие, то есть свойство Enabled имеет значение True (см. левую часть рис. 14.30). В таком состоянии на поверхность кнопки выводится изображение, идентичное заданному свойством Glyph и подпись имеет черный цвет. 2. Пользователь не воздействует на кнопку, и компонент не допускает взаимодействие, то есть свойство Enabled имеет значение False. Изображение, заданное свойством Glyph переводится в оттенки серого цвета, подпись также изменяет свой цвет (см. центральную часть рис. 14.30). 3. Пользователь нажал на кнопку и еще не отпустил кнопку мыши. Изображение и подпись соответствуют нормальным, но сдвигаются вправо вниз, чтобы обеспечить эффект вдавленности кнопки (см. правую часть рис. 14.30).
\J
Обновить
0
Обновить
t
Об$к»вить
Рис. 14.30. Различные состояния компонента BitBtn
Такое представление компонента BitBtn задано по умолчанию, но в некоторых случаях автоматического преобразования изображения при отмене доступа пользователя и нажатиях на кнопку может быть недостаточно, либо само преобразование цветов может быть некорректным. В таких случаях для каждого из состояний может использоваться собственное изображение, причем все три изображения должны храниться в свойстве Glyph. Изображения должны иметь одинаковые размеры и располагаться в один ряд. В рассмотренном нами примере в качестве свойства Glyph использовано изображение, представленное на рис. 14.31.
319
Часть IV. Компоненты и их использование
Рис. 14.31. Пример изображения, заносимого в свойство Glyph компонента BitBtn
Для указания количества элементов в рисунке используется целочисленное свойство NumGlyphs: property NumGlyphs: TNumGlyphs; В рассматриваемом примере данное свойство имеет значение 3, что указывает на необходимость разбиения изображения, заданного свойством Glyph, на три равных части и вывода каждой из них в качестве изображения кнопки, в зависимости от ее состояния. Так, в обычном состоянии будет выведена левая треть изображения (зеленые стрелки), в запрещенном состоянии на поверхности кнопки будет отображена центральная треть рисунка (серые стрелки), а при нажатии кнопки — правая треть (уменьшенные зеленые стрелки). Для указания относительного расположения подписи и изображения на поверхности кнопки используется перечислимое свойство Layout, возможные значения которого перечислены в табл. 14.12: property Layout: TButtonLayout; Возможные значения свойства Layout компонента BitBtn Значение
Таблица 14.12
Вид компонента
bIGIyphLeft
Изображение располагается слева от подписи (устанавливается по умолчанию)
blGiyphRight
Изображение располагается справа от подписи
bIGIyphTop
Изображение располагается сверху от подписи (см. рис. )
bIGIyphBottom
Изображение располагается снизу от подписи
Также следует отметить целочисленное свойство Spacing, управляющее расстоянием от изображения до подписи на поверхности кнопки: property Spacing: Integer; Свойство Spacing может принимать как положительные, так и отрицательные значения для приближения используемых кнопок к общей концепции интерфейса программы. 320
Глава 14. Визуальные компоненты
Использование стилей для кнопок BitBtn Интересной возможностью компонента BitBtn является набор стилей, для каждого из которых заданы подпись и изображение, что может существенно ускорить построение пользовательского интерфейса. Стиль, который необходимо назначить, находится в перечислимом свойстве Kind: property Kind: TBitBtnKind; Список поддерживаемых стилей приведен в табл. 14.13. Таблица 14.13 Возможные значения свойства Kind компонента BitBtn Значение
Вид компонента
bkCustom
Вид компонента определяется свойствами Caption и Glyph
bkOK
<*& OK
bkYes bkAII
: tdr All ^
bkNo
®No
bkCancel
X Cancel
bkAbort
X Abort
bkRetry
^Z***d
bklgnore
•^ Ignore
bkHelp
? Help
bkClose
1 I
j
;|
JQ, Close |
14.4.3. Кнопки SpeedButton Компоненты SpeedButton близки по внешнему виду к кнопкам BitBtn и поддерживают вышеописанные свойства Caption, Glyph, NumGlyphs, Layout и Spacing, однако не реализуют свойства Kind и Wordwrap. Кнопки SpeedButton могут отображаться в плоском виде и принимать трехмерный вид при наведении на них мыши (см. рис. 14.32), если свойство Flat имеет значение True: property F l a t :
11
Зак. 867
Boolean;
321
Часть IV. Компоненты и их использование
Получить данные
П олучигь
Рис. 14.32. Плоский стиль элементов управления
Особенностью кнопок SpeedButton является их возможность фиксации в нажатом положении. Соответственно, при использовании специальных изображений, выводимых на поверхность кнопки, свойство Glyph может состоять не из трех частей, как у компонентов BitBtn, а из четырех (см. рис. 14.33). Если четвертое изображение не задано, и свойство NumGlyphs имеет значение 3, то в зафиксированном положении будет выведено первое изображение, соответствующее ненажатой кнопке.
©
П олучиь данные
Рис. 14.33. Кнопка SpeedButton в зафиксированном состоянии и изображение, хранимое в свойстве Glyph
Для определения, в каком состоянии находится кнопка, предусмотрено логическое свойство Down, доступное в Инспекторе объектов и во время работы программы, как для чтения, так и для изменения с немедленным отображением соответствующего состояния кнопки на экране: property Down: Boolean; После создания компоненты SpeedButton не поддерживают фиксацию в нажатом положении. Для включения этой возможности используется целочисленное свойство Grouplndex: property Grouplndex:
Integer;
Если свойство Grouplndex имеет значение 0, то компонент SpeedButton ведет себя как обычная кнопка BitBtn. При ненулевом значении у кнопки появляется возможность фиксации в нажатом положении, причем кнопки, имеющие одинаковое значение этого свойства, образуют группу, в которой может быть нажата только одна из кнопок. При нажатии другой кнопки в группе все остальные переходят в нормальное положение. При этом событие Onclick для каждой кнопки возникает только 322
Глава 14. Визуальные компоненты
при непосредственном нажатии на нее пользователем, поэтому для работы с группами компонентов SpeedButton обычно используется общий обработчик события Onclick для каждой группы. Представим себе панель управления лифтом восьмиэтажного дома, имеющую, соответственно, восемь кнопок с номерами этажей, а также кнопки «Стоп» и «Вызов» (см. рис. 14.34). Для разработки программы, на главной форме которой находится такая панель, запустим среду разработчика Delphi, откроем новый проект, и добавим на главную форму десять компонентов SpeedButton, расположив их в два столбца. Установим необходимые значения свойствам Caption каждого из десяти компонентов.
Рис. 14.34. Свойства компонентов
Кнопки «Стоп» и «Вызов» предназначены для выполнения однократного действия — остановки лифта и включения переговорного устройства для связи с диспетчером, поэтому они не должны поддерживать возможность фиксации в нажатом положении. Так и происходит, так как свойству Grouplndex по умолчанию установлено нулевое значение. Обратная ситуация с кнопками выбора этажа. После нажатия пользователем на кнопку лифт начинает двигаться, и в течение всего этого времени кнопка остается нажатой. Если во время движения будет нажата другая кнопка, то предыдущей выбор должен быть отменен. Таким образом, кнопки выбора этажа образуют друг с другом группу зависимых элементов управления, поэтому им следует установить одинаковое значение свойства Grouplndex, причем оно должно быть отлично от нуля. Например, установим значение 1. Теперь представим, как должна реагировать программа на нажатие какой-либо кнопки. Очевидно, что обработчики событий нажатий на кнопки выбора этажа, выполняют одни и те же действия, а именно: 1. Направляют лифт на заданный этаж, если лифт еще не движется. 2. Останавливают лифт, если он уже движется, и перенаправляют его на вновь заданный этаж. 323
Часть IV. Компоненты и их использование
Необходимость выполнения одинаковых действий при нажатии на разные кнопки позволяет назначить им единый обработчик события Onclick, который будет вызываться при нажатии любой из восьми кнопок выбора этажа. Для начала создадим обработчик для первой кнопки, которой при добавлении на форму п р и с в о е н о н а з в а н и е SpeedButtonl. Для этого сделаем компонент SpeedButtonl активным, выберем закладку Events Инспектора объектов и дважды щелкнем мышью напротив события Onclick. Среда разработки автоматически _ создает обработчик события: procedure TForml.SpeedButtonlClick(Sender: TObject); begin end; Реализация обработчика события может строиться на основе сравнения значения параметра Sender, в котором содержится ссылка на объект-источник сообщения, со ссылками на кнопки выбора этажа: procedure TForml.SpeedButtonlClick(Sender :x TObject); begin If <Лифт движется> Then <0станавливаем лифт>; If Sender = SpeedButtonl Then begin <Направляем лифт на 1-ый этаж>; Exit; {Выходим из обработчика события} end; If Sender = SpeedButton2 Then begin <Направляем лифт на 2-ой этаж>; Exit; {Выходим из обработчика события} end; If Sender = SpeedButton3 Then begin <Направляем лифт на 3-ий этаж>; Exit; {Выходим из обработчика события} end; {И так далее, для всех восьми кнопок} end; Можно построить обработчик события на основе анализа свойств Down кнопок выбора этажа: procedure T F o r m l . S p e e d B u t t o n l C l i c k ( S e n d e r : T O b j e c t ) ; begin If <Лифт движется> Then <0станавливаем лифт>;
324
Глава 14. Визуальные компоненты If SpeedButtonl.Down Then begin <Направляем лифт на 1-ый этаж>; Exit; {Выходим из обработчика события} end; If SpeedButton2.Down Then begin <Направляем лифт на 2-ой этаж>; Exit; {Выходим из обработчика события} end; If SpeedButtonS.Down Then begin <Направляем лифт на 3-ии этаж>; Exit; {Выходим из обработчика события} end;
{И так далее, для всех восьми кнопок} end;
Теперь следует назначить обработчик события O n c l i c k компонента SpeedButtonl всем остальным компонентам, кроме кнопок «Стоп» и «Вызов». Для этого будем последовательно делать активными все компоненты по очереди, и на странице Events Инспектора объектов не создавать новый обработчик события Onclick двойным щелчком мыши, а выбирать из списка уже реализованный обработчик SpeedButtonlClick (см. рис. 14.35). И, наконец, обратим внимание на свойство AllowAllUp компонента SpeedButton, определяющее, могут ли все кнопки, входящие в группу, одновременно находиться в ненажатом состоянии: property A l l o w A l l U p : Boolean; Object Inspector ]SpeedButton2
T;
Properties Events I
Action OnClick OnDbOck ISpeedButtonl Click pnMouseppwrl I OnMouseMov^ OnMouseUp j PopupMenu i 1АИ shown
Рис. 14.35. Назначение событию ранее реализованного обработчика 325
Часть IV. Компоненты и их использование
У компонентов, используемых в примере, его значение (False) не изменялось относительно установленного по умолчанию. В соответствии с этим значением нажатие на кнопку, уже находящуюся в нажатом состоянии игнорируется и не приводит к ее переводу,в нормальное состояние что вполне удовлетворяет заданию, так'как кнопки в лифте не могут быть отжаты пользователем. Для кнопок «Стоп» и «Вызов» установка любого значения этого свойства не приводит к какому-либо результату, так как для них не определено нажатое состояние.
14.5. Компоненты для редактирования простых данных Под простыми данными мы подразумеваем все параметры, которые в каждый момент' времени работы программы могут иметь не более одного значения какого-либо заранее заданного типа. Это могут быть строки, вещественные и целые числа, даты и логические значения. В настоящем разделе рассматриваются стандартные компоненты, указанные в табл. 14.14. Таблица 14.14
Компоненты для редактирования простых данных Компонент
5Й
Edit
Компонент для ввода пользователем текстовой строки
И
LabeledEdit
Ввод строки с текстовой меткой
|йГ|
MaskEdit
Ввод строки по маске
Up Down
Изменение числового значения, находящегося в компонентах Edit, LabeledEdit и MaskEdit
!х
CheckBox
Независимый переключатель
<*
RadioButton
Зависимый переключатель
Scrollbar
Полоса скроллинга
TrackBar
Компонент для выбора числового значения из заданного диапазона
MonthCalendar
Выбор даты
DateTimePicker
Выбор даты или времени
ш '.irrva'-
щ
я 326
Описание
Название
Глава 14. Визуальные компоненты
14.5.1. Ввод строк и чисел Для ввода строк предусмотрены стандартные компоненты Edit (страница S t a n d a r d Палитры компонентов), M a s k E d i t (страница A d d i t i o n a l ) и LabeledEdit (страница Additional). В качестве строк могут вводиться и числовые значения, причем некоторые из приведенных компонентов могут автоматически контролировать корректность вводимой информации. Все перечисленные компоненты являются наследниками класса TCustomEdit (см. рис. 14.36), поэтому имеют множество одинаковых свойств, добавляя при этом некоторую собственную функциональность. TComponent TControl TWinControl
I TCustomEdit
TLabeledEdit
TEdit
TMaskEdit Рис. 14.36. Иерархия строк ввода
Строки ввода поддерживают возможность прокрутки текста, если его размеры превышают ширину компонента, выделение подстроки с помощью мыши или клавиатуры, копирование выделенной подстроки в буфер обмена Windows и вставку подстроки из буфера обмена. Работа с буфером осуществляется либо с помощью нажатия комбинаций клавиш Ctrl-Insert (копирование подстроки в буфер) и S h i f t Insert (вставка информации из буфера), либо с помощью контекстного меню, вызываемого при нажатии правой кнопки мыши над компонентом (см. рис. 14.37). Для упрощения редактирования целочисленных параметров может использоваться компонент UpDown, расположенный на странице W i n 3 2 Палитры компонентов. Рассмотрение компонентов, предназначенных для редактирования текстовой информации, мы начнем с описания класса TEdit, так как его методы, свойства и события являются общими для всех трех компонентов. 327
Часть IV. Компоненты и их использование
работу с буфером обме Отменить Вырезать Копировать Вставить Удалить : Выделить все :
Рис. 14.37. Строки ввода поддерживают прокрутку вводимой информации и работу с буфером обмена
Итак, компонент Edit предназначен для ввода пользователем одной строки, которая хранится в свойстве Text и доступна для анализа и изменения как во время визуального построения программы, так и во время ее выполнения: property
Text:
AnsiString;
Поскольку данный элемент управления может использоваться для решения различных задач, например, ввода паролей, в компоненте Edit предусмотрены некоторые свойства для управления представлением вводимого текста. Например, свойство PasswordChar задает символ, который выводится вместо любого символа строки. При этом значение свойства Text не меняется и содержит строку с реально вводимой информацией, без учета этого символа. property P a s s w o r d C h a r :
Char;
Если свойство PasswordChar имеет значение #0, устанавливаемое по умолчанию, то символы в выводимой строке выводятся без замены, то есть свойство PasswordChar игнорируется. Второе свойство, управляющее отображением — CharCase (от англ. Char Case — Регистр символов) — включает автоматическое преобразование вводимого текста в верхний или нижний регистр. При изменении значения данного свойства сразу же преобразуются все символы в строке Text. property CharCase: TEditCharCase; Данное свойство может иметь следующие значения: ecLowerCase
ecUpperCase
328
в вводимой строке используются строчные символы. Если пользователем вводится заглавный символ, он автоматически преобразуется в строчный. .». в вводимой строке используются заглавные символы. Если пользователем вводится строчный символ, он автоматически преобразуется в заглавный.
Глава 14. Визуальные компоненты
ecNormal
символы, вводимые пользователем, не проверяются на соответствие какому-либо регистру.
Другие свойства, общие для компонентов E d i t , M a s k E d i t и LabeledEdit, приведены в табл. 14.15. Таблица 14,15
Некоторые свойства компонента Edit Свойство
Тип
Описание
AutoSelect
Boolean
Определяет, следует ли выделять вводимую строку при переходе фокуса на данный компонент
AutoSize
Boolean
Определяет, следует ли автоматически подбирать высоту элемента управления для соответствия ее размеру используемого шрифта. Эту возможность можно отключать в случаях, когда высота символов шрифта некорректно определяется компонентом
Color
TColor
Определяет цвет фона компонента
Boolean
Определяет, следует ли скрывать выделение текста при потере фокуса данным компонентом
Integer
Задает максимально возможную длину вводимой строки. Используется только для контроля ввода от пользователя и не накладывает ограничений на программное изменение значения свойства Text. Если свойство MaxLength имеет нулевое значение, то количество символов в вводимой строке не ограничивается
Boolean
Запрещает (значение True) изменение информации пользователем. В отличие от свойства Enabled, полностью запрещающего доступ пользователя к компоненту, данное свойство отключает только возможность исправления строки. Прокрутка содержимого, выделение подстроки и копирование выделенной подстроки в буфер обмена не запрещаются
HideSelection
MaxLength
Readonly
Совместно со строками ввода часто используются текстовые метки Label или StaticText для указания пользователю на характер информации, которую следует ввести (см. рис. 14.38). Для упрощения разработки подобных интерфейсных связок предназначен компонент LabeledEdit, имеющий в своем составе метку Label и предоставляющий свойства для настройки ее характеристик.
Имя пользователя: Mouse Пароль
•
******
Текстовые метки
Строки ввода
Рис. 14.38. Совместное использование компонентов Label и Edit
329
Часть IV. Компоненты и их использование
Для доступа к текстовой метке, находящейся внутри компонента LabeledEdit, используется свойство EditLabel, разворачиваемое в Инспекторе объектов. Единственное отличие этой метки от обычной метки Label состоит в отсутствии свойств автоматического выравнивания A l i g n и Anchors, так как положением внутренней метки управляет непосредственно компонент LabeledEdit. Чтобы задать относительное расположение текстовой метки и поля ввода, предусмотрено перечислимое свойство LabelPosition, которое может иметь значения ipLeft (метка слева от строки ввода), IpRight (метка справа), IpAbove (метка сверху) и ipBelow (метка снизу), а также целочисленное свойство LabelSpacing, определяющее расстояние от текстовой метки до строки ввода. Строки ввода с различными положениями текстовых меток представлены на рис. 14.39.
' |Метка снизу
Е1иЩ
LabeledEditl
LabeledEditZ LabeledEdit3 [Метка слева [метка справа
LabeledEdiM
Рис. 14.39. Компонент LabeledEdit с различным расположением текстовых меток
Компонент UpDown используется для организации ввода целочисленных данных в компоненты Edit и LabeledEdit, и выглядит как две стрелки, направленные в разные стороны (см. рис. 14.40). Направление стрелок задается перечислимым свойством Orientation, которое может иметь два значения: * udHorizontal — стрелки направлены влево и вправо; » udVertical — стрелки направлены вверх и вниз, property.Orientation: TUDOrientation; Компонент UpDown не предназначен для самостоятельной работы и должен использоваться только совместно с компонентами E d i t , MaskEdit или LabeledEdit. Для указания, к какому именно компоненту формы привязан UpDown, используется свойство Associate: property A s s o c i a t e :
TWinControl;
Заметим, что данное свойство имеет тип TWinControl, то есть компонент UpDown можно привязать к любому оконному компоненту, например, к компоненту StaticText 54. При использовании компонента StaticText числовое значение выводится в его свойство Caption. 330
Глава 14. Визуальные компоненты
Рис. 14.40. Совместная работа компонентов Edit и UpDown Для настройки пределов изменения редактируемого значения и шага изменения при однократном нажатии какой-либо кнопки компонента UpDown, используются, соответственно, целочисленные свойства Min,
Мах и Increment:
.
property Min: Smalllnt; property Max: Smalllnt; property Increment: Smalllnt; Компонент MaskEdit (см. рис. 14.41) предназначен для ввода информации по заданному шаблону (маске) и имеет всего одно дополнительное свойство EditMask, в котором содержится описание этого шаблона: property EditMask: AnsiString;
l *c «,« ftЮГП11
'
••••••.'. '
Введите Ваш номер телефона: f(*7812)998-Z| Рис. 14.41. Ввод строки по шаблону
Шаблон представляет собой строку символов, часть из которых рассматривается как управляющие (см. табл. 14.16). Управляющие символы включают и отключают какой-либо тип фильтрации. Те символы, которые не распознаются как управляющие, выводятся без изменения как часть вводимой информации и не подлежат редактированию пользователем. Маска состоит из трех частей, разделенных точкой с запятой: 1. Первая указывает собственно маску как последовательность символов, в том числе и управляющих; 2. Во второй части находится символ, указывающий, следует ли (символ 1) переносить в свойство Text не управляющие символы, которые.используются в маске, или нет (символ 0); З.В третьей части задается символ, который выводится для подсказки пользователю о позициях строки, в которые следует ввести символы.
331
Часть IV. Компоненты и их использование Управляющие символы, используемые в шаблонах компонента MaskEdit
Таблица 14.16
Символ
Значение в маске
>
Все символы, вводимые пользователем после текущей позиции, переводятся в верхний регистр. Действие прекращается после указания символа < или символов о
<
Все символы, вводимые пользователем после текущей позиции, переводятся в нижний регистр. Действие прекращается после указания символа > или символов о
о
Отключение автоматического изменения регистра вводимых символов
\
Необходим для использования в маске символов, которые считаются управляющими. Если перед управляющим символом используется \, то сам символ рассматривается как обычный
L
В текущей позиции может присутствовать только буква
I
В текущей позиции может присутствовать только буква, либо не присутствовать ничего
0
В текущей позиции может присутствовать только цифра
9
В текущей позиции может присутствовать только цифра, либо не присутствовать ничего
А
В текущей позиции может присутствовать только буква или цифра
а
В текущей позиции может присутствовать только буква или цифра, либо не присутствовать ничего
С
В текущей позиции может присутствовать любой символ
с
В текущей позиции может присутствовать любой символ, либо не присутствовать ничего
#
В текущей позиции может присутствовать цифра, знак (плюс или минус), либо не присутствовать ничего В текущую позицию будет вставлен символ, разделяющий часы, минуты и секунды в значениях времени. Символ автоматически считывается из настроек Windows
/
В текущую позицию будет вставлен символ, разделяющий месяца, дни и года в датах. Символ автоматически считывается из настроек Windows
;
Используется для отделения частей маски 'друг от друга
„
В текущую позицию автоматически вставляется пробел
Если пользователем не введена какая-либо позиция, заявленная в шаблоне, то при попытке закончить редактирование (окончанием редактирования считается потеря компонентом фокуса), возникает исключительная ситуация. Возможны ситуации, когда даже самая сложная маска не может обеспечить необходимую фильтрацию вводимой информации. Например, маска не поддерживает ввод неопределенного количества однотипных символов. В таких случаях следует использовать обработчик события OnKeyPress, возникающего после нажатия пользователем клавиши, но перед изменением информации в свойстве Text. В обработчик этого события в качестве параметра Key передается символ, соответствующий нажатой клавише: property OnKeyPress: TKeyPressEvent; Type TKeyPressEvent = procedure (Sender:- TObject; var Key: Char) of object; 332
Глава 14. Визуальные компоненты
Если ввод символа в данный момент недопустим, то параметру Key следует присвоить значение #0, в результате чего нажатие клавиши будет проигнорировано. Например, чтобы запретить нажатие на клавишу «1» для какого-либо компонента, следует реализовать обработчик OnKeyPress данного компонента следующим образом: procedure TForml.MaskEditlKeyPress(Sender: TObject; var Key: Char); begin If Key = 4' Then Key := #0; end; Заметим, что запрет ввода в обработчиках такого рода распространяется только на символы, вводимые пользователем непосредственно. Операции вставки строк из буфера обмена и программная установка свойства Text не вызывают события OnKeyPress. Компоненты Edit, LabeledEdit и MaskEdit поддерживают еще два события, аналогичных OnKeyPress. События возникают соответственно при нажатии и отпускании клавиш, и описаны следующим образом: property OnKeyDown: TKeyEvent; p r o p e r t y OnKeyUp: TKeyEvent; Type TKeyEvent = procedure (Sender: TObject; var Key: W o r d ; Shift: TShiftState) of object;
Дополнительный
параметр
S h i f t S t a t e , передаваемый в обработчики ЭТИХ СОбыТИЙ ЯВЛЯеТСЯ MHOТаблица 14.17 ЖССТВОМ, ОПредеЛЯЮЩИМ СОСТОЯНИе Элементы множества TShiftState
дополнительных клавиш Shift, Alt и Control в момент нажатия клавиши, КОД КОТОРОЙ НаХОДИТСЯ В Параметре K e y . Возможные элементы множества типа TShiftState представлены в табл. 14.17.
Элемент
Значение
ssShift
Нажата клавиша Shift
ssAlt
Нажата клавиша Alt
ssCtrl
Нажата клавиша Control
В качестве примера использования фильтрации вводимой информации напишем программу, предлагающую пользователю ввести номер телефона в формате «Код страны — Код города — Номер из семи цифр», причем код страны и код города являются необязательными для ввода, первая цифра номера телефона должна находиться в интервале от 1 до 5, а все символы, используемые для удобства пользователя, не будут сохраняться в свойстве Text. Для просмотра свойства Text предусмотрим текстовую метку, в которую при нажатии специальной кнопки будет переноситься строка, введенная пользователем.
333
Часть IV. Компоненты и их использование
Добавим на форму компонент M a s k E d i t и настроим его свойство EditMask. Итак, первая часть строки, содержащая код страны, представляет собой символ + и необязательную для ввода цифру. Соответственно, эта часть маски будет иметь следующий вид: «+9», что подразумевает возможность ввода цифры во вторую позицию строки. Вторая часть строки содержит код города из трех цифр, также необязательных для ввода. Соответственно следующая часть маски будет выглядеть так: «999» . И, наконец, сам номер телефона представляет собой семь цифр, обязательных для ввода: «0000000». Таким образом, маска, состоящая из всех этих частей, представляет собой строку «+99990000000». Для удобства пользователя добавим в нее несколько символов, отделяющих различные части номера друг от друга: +9 999 0 0 0 - 0 0 - 0 0
Чтобы обеспечить очистку введенного значения от пробелов, при переносе его в свойство Text, следует добавить в маску второй раздел, в котором указываем символ 0: +9 999 0 0 0 - 0 0 - 0 0 ; 0
Для указания позиции ввода будем использовать символ подчеркивания: +9 999 000-00-00;0;_
Таким образом, маска сформирована, и можно переходить к реализации анализа корректности семизначного номера. Наличие всех семи цифр контролировать не требуется, так как это будет происходить автоматически, в соответствии с маской. Нам следует обеспечить только проверку восьмого символа вводимой строки, который должен находиться в интервале от «1» до «5». Создадим обработчик события OnKeyPress компонента MaskEdit 1 следующего вида: procedure
TForml.MaskEditlKeyPress(Sender: TObject; var K e y : C h a r ) ; begin , if ( M a s k E d i t l . S e l S t a r t = 7) and not ( ( K e y > X 0 ' ) and (Key < ' 6 ' ) ) Then Key := #0; end;
Проверка корректности ввода основана на анализе значения свойства SelStart компонента MaskEdit 1, в котором находится номер позиции (отсчитывается от нуля) ввода текущего символа. Позиция определяется непосредственно в компоненте MaskEdit и не имеет никакого отношения к маске. Поэтому первая цифра номера телефона находится
334
Глава 14. Визуальные компоненты
в позиции 7. Если пользователь вводит символ в седьмую позицию, то выполняется проверка на корректность вводимой цифры. Если условие выполнено полностью, ввод символа запрещается. Далее расположим на форме кнопку Button и текстовую метку Label. Создадим обработчик Onclick кнопки следующего вида для переноса введенного значения из строки ввода в текстовую метку: 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 : begin Labell.Caption := M a s k E d i t l . T e x t ; end;
TObject);
Результат работы программы представлен на рис. 14.42. При попытке некорректного ввода первой цифры номера, нажатие на клавишу игнорируется, а при попытке нажать на кнопку «Показать» при незаполненных позициях номера, на экран выводится предупреждение об ошибке ввода (см. рис. 14.42). JnjJ
78122470818 Kj§:
Invalid input value. Use escape key to abandon changes.
OK
Рис. 14.42. Ввод по маске с фильтрацией. Ошибка ввода
14.5.2. Редактирование логических значений. Переключатели Для редактирования логических значений предусмотрены компоненты CheckBox (страница Standard Палитры компонентов) и RadioButton (также страница standard). Оба компонента являются наследниками класса TButtonControl, который, в свою очередь унаследован от TWinControl (см. рис. 14.43) и описывает свойства, характерные для переключателей. Компоненты-переключатели могут находиться в двух состояниях — «включено» и «выключено» — определяемых логическим свойством Checked: property Checked:
Boolean;
-
335
Часть IV. Компоненты и их использование TComponent TControl TW'mControl TBnttonControl
TCheckBox
TRadtoButton
Рис. 14.43. Иерархия компонентов-переключателей
Значение True данного свойства указывает на положение переключателя «включено», а значение False — на положение «выключено». Свойство доступно для анализа и изменения, как во время визуального построения программы, что позволяет установить некоторые начальные значения, так и во время ее работы. Компонент CheckBox выглядит как прямоугольник, в котором может находиться отметка о включении (см. рис. 14.44), а компонент RadioButton имеет вид круга. Оба компонента имеют подпись, указывающую пользователю на параметр, включением которого управляет данный переключатель. Строка, выводимая в качестве подписи, содержится в свойстве Caption и может быть разбита на несколько строк в соответствии со свойством Wordwrap: property C a p t i o n : S t r i n g ; property Wordwrap: Boolean;
f/ Автоматически определять формат файлов Р Использовать мнргпцот ний режим! :
Рис. 14.44. Независимые переключатели
Подпись может выводиться слева или справа от компонента, в зависимости от значения перечислимого свойства Alignment: property A l i g n m e n t :
TLeftRight;
При значении данного свойства taLeftJustify, подпись выводится слева от компонента, а при значении taRightJustify — справа (см. рис. 14.45). 336
Глава 14. Визуальные компоненты
Рис.
!? taRighUustify
taLeftJustify Г
Г taRightJustify
taLeftJustify f
14.45. Выравнивание подписи относительно компонентов
Существенное отличие между двумя рассматриваемыми компонентами состоит в том, что компонент checkBox предназначен для самостоятельной работы и называется соответственно независимым переключателем, тогда как RadioButton представляет собой компонент для работы в группах, аналогичных группам компонентов SpeedButton, рассмотренным выше. Поскольку часто последствия включения или выключения какой-либо возможности в программе могут быть не ясны для пользователя (или в зависимости от ситуации, пользователь не готов использовать значение, предлагаемое программой) в компоненте CheckBox, кроме двух вариантов логического значения «да» и «нет», реализован третий — «не знаю». Внешне, независимый переключатель, имеющий такое значение, выглядит как включенный, но символ активности выводится серым цветом. Возможностью такого состояния независимого переключателя управляет логическое свойство A l l o w G r a y e d (от англ. Allow Grayed — разрешить быть серым): property A l l o w G r a y e d :
Boolean;
Для определения состояния переключателя, для которого включена возможность промежуточного состояния, используется перечислимое свойство State: property State:
TCheckBoxState;
Наличие данного свойства не исключает возможности использования свойства checked, а дополняет его. Свойство может принимать следующие значения: cbUnchecked cbChecked cbGrayed
компонент находится в состоянии «выключено». Свойство Checked при этом имеет значение False; компонент находится в состоянии «включено». Свойство Checked при этом имеет значение True; компонент находится в промежуточном «сером» состоянии. Свойство Checked при этом имеет значение False.
Изменения в одном из свойств State или checked автоматически ведут к изменению значения второго свойства. 337
Часть IV. Компоненты и их использование
Компонент RadioButton не может находиться в промежуточном состоянии, так как это противоречит его назначению — выбору одного значения из нескольких возможных. Несколько таких компонентов, находящихся в одном контейнере, автоматически образуют между собой группу. При включении одного переключателя из такой группы все остальные автоматически выключаются.
14.5.3. Изменение числового значения в заданном диапазоне Рассмотренный выше компонент ProgressBar отображает положение некоторого целочисленного параметра относительно заданного диапазона. Для изменения числового параметра по такому же принципу предназначены компоненты Scrollbar (страница Standard Палитры компонентов) и TrackBar (страница Win32). Компонент ScrollBar (полоса скроллинга) Компонент ScrollBar (полоса скроллинга) используется обычно для схематичного изображения видимой части некоторого большого документа и состоит из следующих элементов (см. рис. 14.46): 1. Основная часть, символизирующая весь документ. 2. Бегунок, символизирующий видимую область документа. 3. Стрелки перемещения бегунка для смещения видимой области внутри документа. Помимо нажатия на стрелки перемещения бегунка, пользователь может перемещать видимую область, нажимая кнопку мыши на основной части, не занятой бегунком. Такое перемещение бегунка называется большим, в отличие от малого, получаемого при нажатии кнопок смещения бегунка. Кнопки перемещения
Бегунок (видимая часть) Основная часть (документ)
Рис. 14.46. Компонент ScrollBar 338
Глава 14. Визуальные компоненты
Для определения длины основной части и бегунка в условных единицах используются свойства Min, Max и PageSize: property M i n : Integer; property M a x : Integer; property PageSize: Integer; Если свойство PageSize имеет нулевое значение, то размер 'бегунка выбирается автоматически. Для определения позиции бегунка используется свойство Position, которое также задается в условных единицах интервала (Min, Max): property P o s i t i o n : Integer; Минимальное значение свойства Position соответствует значению свойства Min, при этом бегунок находится в самом левом положении основной части полосы скроллинга при ее горизонтальном расположении, либо в самом верхнем положении основной части полосы при ее вертикальном расположении. Расположением компонента ScrollBar управляет перечислимое свойство Kind: property Kind:
ScrollBarKind;
Данное свойство может иметь значения sbHorizontal (горизонтальное расположение) и sbVertical (вертикальное расположение). И, наконец, для управления размером смещения бегунка, также задаваемым в относительных единицах, используются свойства SmallChange (смещение при нажатии на кнопки перемещения бегунка) и LargeChange (смещение при нажатии на основную часть полосы скроллинга): property SmallChange: TScrollBarlnc; property LargeChange: TScrollBarlnc; Тип TScrollBarlnc является поддиапазоном типа Smallint и описан в модуле Forms следующим образом: • • Type TScrollBarlnc = 1.. 32767; Компонент TrackBar — выбора числового значения из интервала Компонент TrackBar (см. рис. 14.47) имеет немного другую направленность и предоставляет пользователю возможность выбора числового значения из интервала. Данный компонент имеет гораздо больше настроек визуального представления: » горизонтальное или вертикальное расположение на форме; * шкала с рисками для более удобного перемещения бегунка, расположенная по одной из сторон компонента, либо выводимая по обе стороны компонента, либо вообще не отображаемая. Шкала состо339
Часть IV. Компоненты и их использование
шшяш
г Рис. 14.47. Компонент TrackBar
Некоторые свойства компонента TrackBar
Таблица 14.18
Тип
Описание
Orientation
TTrackBarOrientation
Определяет расположение компонента и может иметь два значения: trHorizontal (горизонтальное расположение), trVertical (вертикальное расположение)
Min
Integer
Минимальное значение интервала изменения параметра
Max
Integer
Максимальное значение интервала изменения параметра
Position
Integer
Текущая позиция бегунка (изменяемое значение) в интервале
LineSize
Integer
Задает смещение бегунка в условных единицах при его перемещении клавишами влево (вверх) или вправо (вниз)
PageSize
Integer
Задает смещение бегунка в условных единицах при его перемещении клавишами PageUp или PageDown
Slider-Visible
Boolean
Видимость бегунка
SelEnd
Integer
Начало выделенного интервала на фоне компонента
SelStart
Integer
Окончание выделенного интервала на фоне компонента
TickMarks
TTickMark
Задает сторону компонента, по которой выводится шкала, и может иметь одно из трех значений: tmBottomRight (шкала выводится справа или снизу от фоновой области), tmTopLeft (слева или сверху), tmBoth (по обеим сторонам фоновой области)
Frequency
Integer
Частота нанесения рисок на шкале, если шкала выводится
TickStyleDescription
TTickStyle
Задает расположение рисок на шкале и может иметь одно из трех значений: tsNone (риски не выводятся), tsAuto (риски выводятся в соответствии со значением свойства Frequency), tsManual (выводится первая и последняя риска, остальные могут быть добавлены во время выполнения программы вызовом метода SetMark)
ThumbLength
Integer
Ширина (при вертикальном расположении компонента) или высота (при горизонтальном расположении) бегунка в точках. При изменении значения данного свойства автоматически изменяется размер фоновой области
Свойство
340
Глава 14. Визуальные компоненты
ит из рисок без подписей, причем частота нанесения рисок может быть настроена; * заливку части фоновой области, аналогичную используемой в компоненте ProgressBar; * бегунок непрямоугольной формы, указывающий в сторону шкалы, либо прямоугольной формы, если шкала выведена по обе стороны компонента; » возможность сокрытия бегунка. . Свойства компонента TrackBar представлены в табл. 14.18.
14.5.4. Выбор даты Элементы управления, предназначенные для выбора даты, часто являются узким местом в программных продуктах. Обусловлено это тем, что при вводе пользователем даты или времени в строковом виде, например «10/03/2004», появляется проблема формата, а написание каких-либо компонентов для более строгой реализации выбора не простая задача. Однако в Windows реализованы элементы управления для ввода данных такого рода, а в Delphi присутствуют компоненты-оболочки этих элементов управления DateTimePicker (расположен на странице W i n 3 2 Палитры компонентов) и MonthCalendar (также со страницы Win32). Компоненты просты в использовании и имеют довольно гибкие цветовые настройки, что обеспечивает возможность их использования при построении пользовательского интерфейса практически любой сложности. Компонент MonthCalendar (см. левый верхний компонент на рис. 14.48) представляет собой прямоугольную область с заголовком, в которую выведен 'календарный лист на один заданный месяц заданного года. Для смены месяца и года предназначены кнопки, находящиеся на заголовке. Выбор даты осуществляется пользователем с помощью мыши, причем сразу же может быть выбран непрерывный интервал времени, если свойство MultiSelect компонента имеет значение True.
Пн Вт 1 7 8 14 21 22 28 29
Ср Чт Пт Сб Вс 2 3 9 10 16 17
А 11 18
5 12 19
Б 13 20
Пн Вт Ср Чт Пт Сб Вс 3 4 5 6
23 24 25 26 38
27
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
О Сегодня: 15.06.2004 П5
2004 г.
10 11
12
13
Сегодня: 15.06.2004
Рис. 14.48. Компоненты выбора даты и времени 341
Часть IV. Компоненты и их использование
Значение, выбранное пользователем, заносится в свойство Date, и может использоваться в различных функциях и методах объектов, принимающих параметры данного типа: property Date: TDate;
Если свойство MultiSelect имеет значение True, то есть допустим выбор интервала времени, то начало интервала будет занесено в свойство Date, а его окончание — в свойство EndDate. Целочисленное свойство MaxSelectRange устанавливает максимальную длину интервала, который может выбрать пользователь.
Рис. 14.49. Ввод времени
Установка свойства Date во время разработки приложения приводит к отображению соответствующей календарной страницы после старта программы и автоматическому выбору установленной даты.", Среди дополнительных возможностей компонента MonthCalendar отметим следующие: » Вывод номеров недель в левом столбце календарного листа (управляется логическим свойством WeekNumbers). * Смена палитры цветов, используемых для вывода компонента (свойство CalColors). » Вывод календаря с заданного дня недели (например, с воскресенья) или с дня недели, соответствующего настройкам операционной системы (СВОЙСТВО FirstDayOfWeek). » Ограничение выбора даты пользователем с помощью свойств MinDate И MaxDate. * Быстрый ввод текущей даты с помощью специальной области «Сегодня», расположенной в нижней части страницы (включение данной области управляется свойством ShowToday). Компонент DateTimePicker (см. левый нижний и правый компоненты на рис. ) представляет собой строку ввода со специальной областью в правой ее части. Внешний вид данной области и действия, выполняемые при нажатии кнопки мыши в данной области, зависят от значения свойства DateMode: property DateMode: TDTDateMode; Type TDTDateMode = (dmComboBox, dmUpDown) ; 342
Глава 14. Визуальные компоненты
Если свойство DateMode имеет значение dmComboBox, то в правой части компонента отображается стрелка вниз. При нажатии на эту стрелку мышью на экран выводится всплывающее окно, содержащее аналог компонента Moni:hCalendar. Окно будет автоматически закрыто после выбора даты, или при щелчке мышью за пределами его области. Выпадающий компонент имеет несколько меньше настроек, чем MonthCalendar, например не предусмотрена возможность выбора интервалов времени. Пример выпадающего окна как раз и показан на рис. 14.50 (правый компонент). Если свойство DateMode имеет значение dmUpDown, то в правой части компонента отображаются стрелки вверх и вниз, аналогичные компоненту UpDown (см. рис. 14.50).
П5 ...••;•
2004 г. -и .....
t?
Рис. 14.50. UpDown-режим компонента DateTimePicker
Значение даты, выведенное в строку ввода, состоит из нескольких частей, причем каждую из них можно выбрать мышью. Далее, при нажатии на кнопки вверх и вниз в правой части компонента, выделенная часть будет изменяться. В режиме dmUpDown возможен также выбор времени. Какое именно значение должен редактировать пользователь — дату или время, указывает свойство Kind: property Kind: TDateTimeKind; Type TDateTimeKind = (dtkDate, d t k T i m e ) ;
По умолчанию установлено значение dtkDate, и компонент работает в режиме ввода даты. При смене значения на dtkTime компонент переводится в режим редактирования времени, а введенное значение заносится в свойство Time. В заключение рассмотрим возможности форматирования значения в компоненте DateTimePicker. Самое простое форматирование обеспечивается перечислимым свойством DateFormat: property DateFormat: TDTDateFormat; Значением данного свойства может являться одно из двух значений: » df short — указывает на цифровое представление даты; » df Long — для расширенного представления даты, а именно вывода названия месяца и признака года (например, буквы 'г'). 343
Часть IV. Компоненты и их использование
На формат времени, выводимого в компоненте, если он находится в режиме редактирования времени, значение свойства DateFormat не влияет. Более сложные задачи можно решать с помощью свойства Format, представляющего собой строку символов, некоторые из которых могут быть управляющими (см. табл. 14.19). Управляющие символы, используемые в формате вывода компонента DateTimePicker Значение
Символ
Таблица 14.19 Пример
d
Номер дня в месяце в цифровой форме
3
dd
Номер дня в месяце в цифровой форме, причем вне зависимости от значения будут выведены две цифры. Если значение меньше десяти, то слева будет приписан символ 0
03
ddd
dddd M MM
МММ
День недели в символьной сокращенной форме День недели в символьной полной форме Месяц в цифровой форме Месяц в цифровой форме, причем вне зависимости от значения будут выведены две цифры Месяц в символьной сокращенной форме
MMMM • Месяц в символьной полной форме УУ
УУУУ
Последние две цифры года Год в четыре цифры
Вт
Вторник 3 03
июн
июня 04
2004
h
Час в цифровой форме в 12-ти часовом исчислении
4
hh
Час в цифровой форме в 12-ти часовом исчислении, причем вне зависимости от значения будут выведены две цифры
04
Час в цифровой форме в 24-ех часовом исчислении
16
HH-
Час в цифровой форме в 24-ех часовом исчислении, причем вне зависимости от значения будут выведены две цифры
16
m
Минуты в цифровой форме
9
Минуты в цифровой форме, причем вне зависимости от значения будут выведены две цифры
09
H
mm
t
Один символ аббревиатуры АМ/РМ
tt
Аббревиатура АМ/РМ полностью
Р РМ
На рис. 14.51 представлен компонент DateTimePicker, свойство Format которого имеет следующее значение: dd MMMM y y y y
(dddd),
HH:mm tt
|17 октября ЭЗШЗ (понедельник), 16:59
Рис. 14.51. Использование сложного форматирования даты и времени 344
Глава 14. Визуальные компоненты
Отметим, что скобки и запятая, использованные в форматной строке, выводятся в строку ввода времени и даты без изменения и упрощают восприятие пользователем сложных данных. Еще одной особенностью такого представления даты и времени является невозможность одновременного редактирования и даты и времени. В приведенном примере свойство Kind имеет значение dtkDate, и компонент может изменять только значение даты.
14.6. Выбор значения с помощью списков 14.6.1. Виды компонентов выбора из списка В Delphi предусмотрена целая линейка компонентов, которые применяются для организации выбора пользователем одного или нескольких значений из некоторого списка. Основные из них перечислены в табл. 14.20. Компоненты выбора из списков
Компонент
Название
Таблица 14.20 Описание
ш
ListBox
Список с возможностью выбора одного значения из списка
Я!
CheckListBox
Список с возможностью выбора нескольких значений из списка
Ш
ComboBox
Выпадающий список с возможностью выбора одного значения из списка
Hi
ColorBox
Выпадающий список для выбора цвета
Заметим, что выбор значения из списка во многом аналогичен, по сути, использованию зависимых переключателей в виде набора компонентов RadioButton или SpeedButton, либо одного'компонента RadioGroup. Однако такие решения ограничены для некоторых видов задач. Например, если список значений может изменяться во время выполнения программы, то поддержка группы компонентов RadioButton подразумевает программное создание и разрушение этих компонентов, а также еще множество действий по их настройке, например, позиционирование или определение размеров. Компонент RadioGroup имеет ограниченное применение еще и в связи с отсутствием в нем возможности прокрутки видимой области в случаях, когда не весь список значений может быть выведен в пределах компонента, в силу своих размеров. Заметим также, что некоторые списковые компоненты имеют возможность выбора нескольких значений из
Часть IV. Компоненты и их использование 55
списка , что делает их еще более привлекательными для использования по сравнению с компонентами RadioButton и RadioGroup. Таким образом, применение списковых компонентов удобно, в основном, в случаях списков переменного состава, но имеются и еще некоторые полезные возможности. Так, все приведенные компоненты имеют логическое свойство AutoComplete, определяющее, должно ли меняться состояние списка при нажатии пользователем каких-либо клавиш: property AutoComplete: Boolean; Если данное свойство имеет значение True (установлено по умолчанию), то при нажатии пользователем клавиши в списке автоматически выбирается элемент, первый символ которого соответствует нажатой клавише. Это может существенно ускорить работу пользователя, особенно в случае больших списков. Иерархия списковых компонентов представлена на рис. 14.52. Заметим, что все списковые компоненты так или иначе являются наследниками класса T W i n C o n t r o l , то есть представляют собой оболочки вокруг стандартных элементов управления Windows. Соответственно, все они имеют описанные нами ранее свойства, характерные для таких компонентов.
TComponent TControl TWinControl TCustomListBox
TCustomComhoBox —
TComboBox —|
TColorBox
Рис. 14.52. Иерархия списковых компонентов
Возможность множественного выбора можно реализовать с помощью группы компонентов SpeedButton, однако такое поведение будет сложно изменить во время выполнения программы, тогда как в списковых компонентах это делается изменением значения одного свойства (MultiSelect).
Глава 14. Визуальные компоненты
14.6.2. Компонент ListBox Компонент ListBox (страница Standard Палитры компонентов) предназначен для выбора пользователем одного значения из представленного списка возможных значений. Список, из которого пользователь выбирает значение, находится в свойстве Items данного компонента: property Items:
TStrings;
По умолчанию пользователь может выбрать только одно из значений, представленных в списке, однако компонент поддерживает и множественный выбор. Такой возможностью управляет логическое свойство MultiSelect: property MultiSelect:
Boolean;
Если данное свойство имеет значение True, то пользователь имеет возможность выбрать несколько элементов из списка одновременно (см. рис. 14.53), удерживая при выборе очередного элемента клавишу Ctrl. Если клавиша Ctrl не нажата, то все выделенные на данный момент элементы теряют выделение, а выделенным будет только тот элемент, который был'Выбран пользователем. .' -JPI х|
Рис. 14.53. Компоненты ComboBox (слева) и ListBox (справа)
Любой фрагмент программы, имеющий доступ к ссылке на компонент ListBox, может определить состояние какого-либо элемента списка, обратившись к свойству Selected, описанному следующим образом: property Selected[Index: Integer]: Boolean;
Для проверки, является ли некоторый элемент выбранным, можно использовать конструкцию следующего вида: • If <Ссылка на компонент>.Selected[
] Then <Необходимые действия>;
Часть IV. Компоненты и их использование
Также свойство Selected может быть использовано для программного выделения одного или нескольких элементов списка во время выполнения программы: <Ссылка на компонент>.Selected[<Номер элемента>] := True; При установленном значении True свойства MultiSelect программное выделение элементов с помощью свойства Selected не приводит к потере выделения другими элементами. Для снятия выделения следует установить значение False соответствующему элементу свойства Selected: ' <Ссылка на компонент>.Selected[] := False; В случае, когда включена возможность множественного выбора, количество выбранных элементов определяется свойством SelCount: property SelCount: Integer;
14.6.3. Компонент CheckListBox Компонент CheckListBox, расположенный на странице Additional Палитры компонентов, аналогичен компоненту ListBox, однако предполагает наличие значка «выбран» слева от элементов списка (см. рис. 14.54).
] Linux П MacOS S5 OS/2 S>: Windows
Рис. 14.54. Компонент CheckListBox В данном компоненте не реализовано свойство MultiSelect, так как он изначально предназначен для множественного выбора элементов. В остальном все свойства компонента L i s t B o x , описанные в предыдущем разделе, применимы и к компоненту CheckListBox. Анализ состояния элементов в списке происходит так же, как и в компоненте ListBox, — на основе свойства Selected. Однако заметим, что в этом свойстве находится не состояние переключателей, расположенных слева от элементов списка, а номер элемента, который в данный момент выбран как активный. В ситуации, показанной на рисунке, при обращении к свойству Selected будет получено значение True только если задан индекс 0: If CheckListBoxl.Selected[0] Then <Выполняемая команда>; 348
Глава 14. Визуальные компоненты
Для анализа состояния переключателя «выбрано», находящегося слева от элементов списка, следует применять свойство State: property S t a t e f l n d e x :
Integer]: TCheckBoxState;
Тип данных TCheckBoxState подробно рассмотрен при описании независимых переключателей. Для компонента CheckListBox предусмотрено еще одно состояние элементов, кроме «выбран» и «не выбран» — «не важно». Такое состояние возможно, если свойство AllowGrayed компонента имеет значение True: property AllowGrayed:
Boolean;
14.6.4. Компонент ComboBox Компонент ComboBox (закладка Standard Палитры компонентов) реализован в виде выпадающего списка, аналогичного ListBox, но является фактически строкой ввода, и ориентирован в основном на ввод текстового значения с клавиатуры, но с помощью списка. Далее введенное пользователем значение, вне зависимости от того, выбрано ли оно из списка, или введено вручную, заносится в свойство Text: property Text:
AnsiString;
Для настройки метода работы компонента предусмотрено свойство style, определяющее может ли пользователь вводить значение свойства Text с помощью клавиатуры, или следует использовать только выпадающий список: property Style: TComboBoxStyle;
Свойство style является перечислимым и может иметь одно из пяти значений, однако мы рассмотрим всего два из них: 1. Значение csDropDown, установленное свойству Style, указывает на то, JITO пользователь может выбрать значение из списка и (или) отредактировать его в строке ввода (см. рис. ). 2. Значение csDropDownList, установленное свойству style, указывает на то, что пользователь может выбрать значение из списка и не имеет возможности изменить его в строке ввода. В момент выбора пользователем одного из значений в списке или изменения текстового значения с помощью клавиатуры, компонент инициирует событие OnChange, в обработчике которого можно реализовать реакцию на изменение значения: property OnChange:
TNotifyEvent;
Часть IV. Компоненты и их использование
JDelphi is|Cool! С C++
_*]
c#
Pascal Visual C++
Рис. 14.55. Компонент ComboBox в режиме csDropDown Помимо свойства Text для анализа введенного значения используется целочисленное свойство itemlndex, содержащее номер выбранного элемента списка, доступное и для установки, как во время визуальной разработки, так и во время выполнения программы, что приведет к автоматическому выбору элемента с данным номером: property Itemlndex: Integer;
14.6.5. Компонент ColorBox Для выбора цветовых характеристик в Delphi предусмотрен специализированный компонент ColorBox, аналогичный по механизму компоненту ComboBox, и расположенный на странице Additional Палитры компонентов (см. рис. 14.56). Изначально в списке представлен некоторый набор стандартных цветов, для которых указаны идентификаторы. Далее набор цветов можно изменить с помощью множественного свойства Style: property Style: TColorBoxStyle; Type TColorBoxStyles = (cbStandardColors, cbExtendedColors, cbSystemColors, cbIncludeNone r ^ cblncludeDefault, cbCustomColor, cbPrettyNames); TColorBoxStyle = Set Of TColorBoxStyles; Элементы множества, которые могут быть включены в свойство style, представлены в табл. 14.21. В момент выбора цвета компонент инициирует событие OnChange: property OnChange:
TNotifyEvent;
Результат выбора, произведенного пользователем, заносится в свойство Color: property C o l o r : TColor;
Глава 14. Визуальные компоненты
т г г г~ г ир т- -шягтя l
Рис. 14.56. Компонент выбора цвета ColorBox
Элементы множества Style компонента ColorBox Элемент
Таблица 14.21 Набор цветов
cbStandardColors
В список добавляются 16 цветов, соответствующие некоторым константам, описанным в модуле Graphics: clBlack, cIMaroon, cIGreen, clOlive, cINavy, cIPurple, cITeal, cIGray, cISilver, cIRed, cILime, clYellow, clBlue, clFuchsia, clAqua и clWhite
cbExtendedColors
В список добавляются цвета cIMoneyGreen, cISkyBlue, cICream и cIMedGray, также описанные в модуле Graphics
cbSystemColors
В список добавляются цвета, используемые в панели управления Windows для цветовых настроек интерфейсных элементов, например clBackground — вет рабочего стола
cblncludeNone
В список добавляется цвет, задаваемый в модуле Graphics константой cINone. Данный элемент может присутствовать в множестве только при наличии элемента cbSystemColors
cblncludeDefault
В список добавляется цвет, задаваемый в модуле Graphics константой cIDefault. Данный элемент может присутствовать в множестве только при наличии элемента cbSystemColors
cbCustomColor
Первой строкой в списке выбора выводится "Custom...", при выборе которой на экране появляется стандартный (системный) диалог выбора цвета. В данном диалоге пользователь может самостоятельно сформировать цвет на основе составляющих или выбрать его из палитры
cbPrettyNames
Вместо констант в списке выводятся названия цветов, что обычно более удобно для пользователя
351
Часть IV. Компоненты и их использование
14.7. Компоненты для редактирования многострочных данных 14.7.1. Многострочные редакторы Memo и RichEdit Компоненты для редактирования многострочных данных Memo (страница standard Палитры компонентов) и RichEdit (страница Win32) являются логическим продолжением идеи компонентов типа Edit и предоставляют пользователю возможность просмотра и изменения текстовой информации, представленной в виде нескольких строк («текстовых документов»): jgl —г Компонент Memo. Многострочный редактор Ц] — Компонент RichEdit. Многострочный редактор с поддержкой форматирования Отличие компонентов Memo и RichEdit между собой заключается в объеме предоставляемых ими возможностей, связанных с ориентацией компонента RichEdit на работу с текстовыми данными в формате RTF (от англ. RTF — Rich Text Format — Текстовый формат с широкими возможностями), принятым в операционной системе Windows за основу в обмене текстовой информацией между приложениями. На рис. 14.57 представлены компоненты Memo (верхний компонент) и RichEdit (нижний компонент). Причем текст, выведенный в компоненте RichEdit подготовлен в текстовом редакторе Microsoft Word и скопирован в компонент через буфер обмена-. Вторая строка текста набрана символами четырех различных шрифтов одновременно с использованием разных атрибутов полужирности и наклона.
Компонент Memo Компонент Rich DIT Рис. 14.57. Компоненты для редактирования многострочного текста
352
Глава 14. Визуальные компоненты
Символы, выводимые в компоненте Memo, всегда имеют одинаковые для всего текста характеристики в каждый момент времени работы программы. Заметим, что компоненты Edit, LabeledEdit и MaskEdit, рассмотренные выше, и компоненты Memo и RichEdit являются наследниками одного и того же класса T C u s t o m E d i t , поэтому имеют множество одинаковых свойств и методов. При этом компоненты Memo и RichEdit унаследованы от этого класса не прямо, а через класс TCustomMemo (см. рис. 14.58), в котором описаны основы поведения компонентов, предназначенных для отображения и редактирования именно многострочных данных. TComponent TControl TWinControl TCustomEdit
— Компоненты Edit
TCustomMemo
TMemo
TRichEdit
Рис. 14.58. Иерархия компонентов для редактирования многострочных данных
Рассмотрим сначала свойства, общие для компонентов Memo и RichEdit, а затем перейдем к подробному описанию особенностей компонента RichEdit, как значительно более сложного и предоставляющего гораздо больше возможностей для представления текстовой информации. Массив строк, отображаемых и редактируемых в компонентах Memo и R i c h E d i t , содержится в свойстве L i n e s , описанном в классе TCustomMemo следующим образом: property L i n e s :
TStrings;
Таким образом, данное свойство является ссылкой на объект базового класса TStrings, а конкретная его реализация выбирается в конструкторе компонентов Memo и RichEdit в соответствии с потребностями этих компонентов. 12 Зак. 867
353
Часть IV. Компоненты и их использование
В классе TMemo свойство Lines на самом деле является ссылкой на экземпляр класса TMemoStrings, а в классе TRiehEdit — на экземпляр класса TRichEditstrings. Естественно, оба они, так или иначе, являются наследниками TStrings, реализуют заявленную в нем функциональность и добавляют особенные черты, характерные для представления текстовой информации в каждом из компонентов. Заметим, что описания классов TMemoStrings и TRichEditstrings находятся в секции implementation модуля stdctrls, поэтому использование их расширенных функций в прикладной программе невозможно. Однако это и не нужно, так как методы, доступные через ссылку на класс TStrings, переопределены в каждом из компонентов таким образом, что предоставляют доступ ко всем необходимым в программе возможностям. Так, например, методы SaveToFile и LoadFromFile компонента RichEdit используют формат RTF, а одноименные методы компонента Memo — обычный текстовый формат. Несмотря на это, вызовы этих методов из разных компонентов внешне не отличаются друг от друга: Memol.Lines.SaveToFile(ЛС:\TMP\text.txt') ; RichEdit1.Lines.SaveToFile('С:\TMP\text.rtf');
Выводом текста в компоненте управляют свойства W o r d w r a p и Alignment, аналогичные одноименным свойствам компонента Label. Данные свойства указывают соответственно на необходимость автоматического разбиения текста на строки по границам компонентов, а также выравнивание текста в компоненте: property W o r d w r a p : Boolean; property A l i g n m e n t : T A l i g n m e n t ;
При установке какого-либо значения свойству Alignment компонента Memo, весь текст выравнивается в соответствии с установленным значением. В компоненте RichEdit выравнивание текста может задаваться еще и для каждого абзаца индивидуально 56. Для установки шрифта, используемого для вывода текста, как и во многих других визуальных компонентах, предназначено свойство Font 57: property F o n t :
56
TFont;
Для установки выравнивания текста в компоненте RichEdit вообще не рекомендуется использование свойства Alignment, так как при этом теряется форматирование, специфичное для каждого абзаца текстовой информации. Причем потеря форматирования касается не только выравнивания, но и, например, настроек шрифтов. Характеристики шрифта в компоненте RichEdit могут быть установлены для каждого символа в отдельности.
354
Глава 14. Визуальные компоненты
Свойства WantReturns (от англ. То Want Returns — обрабатывать символы перевода строки) и WantTabs (от англ. То Want Tabs — обрабатывать символы табуляции) указывают, может ли пользователь вводить в качестве текстовой информации символы перевода строки и символы табуляции: property W a n t R e t u r n s : B o o l e a n ; property W a n t T a b s : B o o l e a n ;
Если свойство WantTabs имеет значение False, то при нажатии пользователем во время редактирования текста клавиши Tab, это действие будет рассматриваться системой как попытка переключения фокуса ввода между оконными компонентами данной формы. При значении True свойства WantTabs нажатие клавиши Tab приведет к вставке символа табуляции в текст. Если свойство WantReturns имеет значение False, то нажатие пользователем клавиши Enter будет игнорироваться, иначе в многострочном редакторе будет появляться новая строка. Для ограничения длины вводимого в компонент текста или запрета на изменение информации вообще, предусмотрены, соответственно, свойства MaxLength и Readonly: property MaxLength: I n t e g e r ; property Readonly: Boolean;
Если текст, содержащийся в свойстве Lines, не может быть полностью выведен в силу его больших размеров, пользователь имеет возможность прокрутки его видимой области с помощью полос скроллинга, расположенных по краям компонентов Memo и RichEdit. По умолчанию такая возможность не предоставляется, однако отображение полос скроллинга может быть включено с помощью перечислимого свойства ScrollBars: property ScrollBars:
TScrollStyle;
В каждый момент времени работы программы на экране может отображаться только один из следующих вариантов: * вертикальная полоса скроллинга — значение ssVertical свойства ScrollBars; * горизонтальная полоса скроллинга — значение ssHorizontal; * обе полосы одновременно — значение ssBoth; * полосы скроллинга могут не отображаться вообще — значение ssNone. Заметим, что даже при установке режима вывода полос скроллинга, подразумевающего их наличие, в компоненте RichEdit полосы появляются только в случае необходимости. Если редактируемый текст мо355
Часть IV. Компоненты и их использование
жет быть полностью выведен во внутреннюю область компонента, полосы скроллинга отображены не будут. Компоненты Memo и RichEdit поддерживают возможность отмены изменений, внесенных в текст пользователем. Определить, готов ли компонент произвести отмену, можно с помощью логического свойства CanUndo, доступного во время выполнения программы: property CanUndo: Boolean; Сам процесс отката запускается вызовом метода Undo: Procedure Undo; Заметим, что свойство C a n U n d o и метод Undo описаны в классе TCustomEdit и доступны также из компонентов Edit, LabeledEdit и MaskEdit, однако их применение в компонентах редактирования многострочного текста намного более полезно.
14.7.2. Особенности компонента RichEdit. RTF-формат Как было отмечено ранее, компонент RichEdit ориентирован на работу с текстовой информацией, представленной в RTF-формате. Данный формат имеет широкие возможности форматирования текста, такие как установку различных шрифтов и стилей выравнивания для каждого абзаца или его части, а также использование автоматической нумерации абзацев. Файлы, получаемые в результате вызова метода SaveToFile свойства Lines, также имеют формат RTF. Причем указание расширения файла не влияет на его формат. Таким образом, текстовая информация, набранная и отформатированная в компоненте RichEdit, может быть прочитана без потерь содержания и форматирования практически любым текстовым редактором, например, Microsoft Word. Точно так же файлы, созданные в редакторе, могут быть отображены программой, реализованной в Delphi. Использование RTF-формата установлено для компонента RichEdit автоматически, однако его можно перевести в обычный режим единых настроек для всего текста с помощью установки значения True свойству PlainText: property
PlainText:
Boolean;
Рассмотрим основы представления информации в RTF-формате. Информация в данном формате представляется в обычном текстовом виде, а форматирование определяет набор команд, управляющих программой отображения текста. В нашем случае такой «программой» является ком-
356
Глава 14. Визуальные компоненты
понент RichEdit. Управляющая команда задает какой-либо режим вывода текста, или отменяет его. Для отделения команд от текстовой информации все команды начинаются с символа '\' (обратный слеш, Back Slash), например команда, переводящая программу отображения в режим вывода полужирных символов, выглядит следующим образом:
\ь
,
Так как обратный слеш может использоваться и в качестве текстовой информации, то есть может быть введен пользователем, в формат RTF добавлена специальная команда для его вывода:
\\ RTF-файлы имеют немалый объем в связи с тем, что управляющие команды используются в нем довольно часто. Это является существенным недостатком данного формата. Например, при записи файла, содержащего в качестве текстовой информации символ «А», выводимый полужирным наклонным шрифтом MS Sans Serif размером 72, создается следующий документ: {\rtfl\ansi\ansicpgl251\deffO\deflangl049 {\fonttbl{\fO\fnil MS Sans Serif;}} \viewkind4\ucl\pard\b\i\fO\fsl44 A} Следует отметить, что аналогичный файл, записанный в формате DOC из Microsoft Word 2000, занимает более 19 килобайт, но при увеличении полезной информации и количества используемых атрибутов формат RTF будет существенно уступать любым двоичным форматам. Однако такой подход к хранению текста имеет и огромное преимущество, связанное с совместимостью программ чтения. Так как команда жестко отделяется от остального текста, то она всегда может быть распознана программой чтения файла, вне зависимости, известна ли эта команда программе, или нет. Таким образом, программы, использующие более ранний формат RTF, могут прочитать файлы, записанные в более позднем формате. При этом не будет выполнена часть управляющих команд, то есть некоторые элементы форматирования будут утеряны, но зато вся текстовая информация будет отображена. Для определения формата предназначена команда \rtf, содержащаяся в начале всех RTF-файлов, в которой указывается еще и версия форт мата, например: \rtfl Некоторые части информации, представленной в RTF-формате, могут быть объединены в группы с помощью фигурных скобок (см. пример выше).
357
Часть IV. Компоненты и их использование
14.7.3. Использование форматирования Параметры отображения текста в компоненте RichEdit устанавливаются двумя способами — для текущего абзаца и для текущего выделения. Абзацем называется одна строка текста с точки зрения ее внутреннего представления, то есть строка, не разделенная символами перевода строки. При этом на экране абзац может отображаться в несколько строк, автоматически разбиваясь по ширине компонента RichEdit, если свойство Wordwrap имеет значение True. Выделение может включать в себя целый абзац, его часть, несколько абзацев и вообще весь документ. Firstlndent
Rightlndenl
Lefilndent
Выделение ф{ агмента текста устанавливается пользователем с помощью мыши или клавиатуры. Характеристики выделения заносятся в свойства SelStart и Sel Length, аналогичные одноименным свойствам компонентов Edit. • Программная установка этих свойств влияет н|а текущее выделение. Если\ компоненте W R T c h E d i t нет выделения в какой-либо момент времени, то свойство \e1Start ^ содержит позицию, в которой находится курсор, а свойство. SelLengt\ имеет V Щ нулевое значение.
\
Примак злеменнш нумерации
Выравнивание по центру
Выравнивание по левому крат
Рис. 14.59. Атрибуты форматирования абзаца
Доступ к атрибутам абзаца осуществляется через свойство Paragraph (англ. Paragraph — абзац): property Paragraph:
TParaAttributes;
Класс TParaAttributes (от англ. Paragraph Attributes — Атрибуты абзаца) является наследником TPersistent и описывает свойства, необходимые для форматирования абзаца текста (см. табл. 14.22). Свойство SelAttributes (от англ. Selection Attributes — атрибуты выделения) определяет настройки шрифта и применяется к выделенному фрагменту текста: property
SelAttributes:
TTextAttributes;
Выделение фрагмента текста устанавливается пользователем с помощью мыши или клавиатуры. Характеристики выделения заносятся в свойства SelStart и SelLength, аналогичные одноименным свойствам компонентов Edit. Программная установка этих свойств влияет на текущее выделение. Если в компоненте R i c h E d i t нет выделения в какой-либо момент времени, то свойство SelStart содержит позицию, 358
Глава 14. Визуальные компоненты
в которой находится курсор, а свойство SelLength имеет нулевое значение. Свойства класса TTextAttributes приведены в табл. 14.23. Пример использования компонента RichEdit приведен в конце настоящей главы. Некоторые свойства класса TParaAttributes Свойство
Таблица 14.22 Описание
Тип
Alignment
TAIignment
Аналогично свойству Alignment самого компонента, однако применяется только к текущему абзацу. См. рис.
Firstlndent
Longint
Задает смещение первой строки абзаца (в точках компонента RichEdit) относительно левого края компонента. См. рис.
Leftlndent
Longinl
Задает смещение абзаца (в точках компонента RichEdit) относительно его первой строки для организации "красных" строк. См. рис.
Rightlndent
Longint
Задает расстояние (в точках компонента RichEdit) от правой части компонента до правой части абзаца. См. рис.
Numbering
TNumberingStyle
Указывает, является ли данный абзац элементом в списке (nsBullet), или обычным абзацем (nsNone). Список состоит из нескольких абзацев, помеченных у левой границы специальным символом. См. рис.
Свойства класса TTextAttributes Тип
Свойство
Таблица 14.23 Описание
Charset
TFontCharset
Аналогично одноименному свойству класса TFont
Color
TColor
Аналогично одноименному свойству класса TFont
Height
Integer
Аналогично одноименному свойству класса TFont
Name
String
Аналогично одноименному свойству класса TFont
Pitch
TFontPitch
Аналогично одноименному свойству класса TFont
Size
Integer
Аналогично одноименному свойству класса TFont
Style
TFontStyles
Аналогично одноименному свойству класса TFont
Set Of TConsistentAttribute
Используется во время выполнения программы для определения однородности выделенного текста по нескольким критериям, соответствующим элементам множества: • caBold — полужирное начертание (либо все символы в выделении имеют полужирное начертание, либо все символы имеют обычное начертание) caColor — цвет символов caFace — шрифт по названию caltalic — наклонное начертание caSize — размер символов caStrikeOut — перечеркнутое начертание caUnderline — подчеркнутое начертание caProtected — текст выделения защищен от изменения пользователем (см. описание свойства Protected ниже)
Boolean
Фрагмент текста, помеченный таким стилем, по умолчанию является недоступным для пользователя. При попытке его редактирования возникает событие OnProtectChange, который в качестве переменного параметра AllowChange может вернуть значение True, если редактирование защищенного фрагмента в данный момент допустимо
ConsistentAttributes
Protected
359
Часть IV. Компоненты и их использование
14.8. Компоненты для редактирования данных в табличной форме 14.8.1. Вид и возможности компонент редактирования данных в табличной форме Данная группа компонентов предназначена для просмотра и редактирования данных, представленных в виде прямоугольных таблиц, состоящих из столбцов и строк, количество и характеристики которых могут изменяться во время выполнения программы с помощью соответствующих property-свойств, реализованных в этих компонентах: И,, — Компонент stringGrid. Компонент для редактирования данных в табличной форме. Dfy — Компонент DrawGrid. Компонент для отображения данных в табличной форме. Ц — Компонент ValueListEditor. Компонент для редактирования данных, представленных в виде «ключ-значение». Все перечисленные компоненты поддерживают (см. рис. 14.60) следующие возможности: * отображение таблиц с возможностью изменения видимой области, если вся информация не может быть одновременно выведена на экран; * редактирование текстовых значений в ячейках; * изменение высоты строк и ширины столбцов, а также изменение их положения в таблице; * организацию «фиксированных» строк и столбцов, не перемещаемых во время прокрутки видимой области58; » гибкую прорисовку ячеек, что позволяет выводить в ячейки таблицы сложные графические данные. Компоненты StringGrid и DrawGrid, расположенные на странице Additional Палитры компонентов (верхний левый и правый компоненты на рис. 14.60, соответственно) во многом схожи между собой. Оба компонента представляют пользователю возможность просмотра каких-либо данных в табличной форме на основе вызова обработчика события OnDrawCeli (от англ. «On Draw Cell» — «При прорисовке ячейки»), возникающего при необходимости перерисовки очередной ячейки таблицы.
— 58
Фиксированные строки и столбцы применяются для организации заголовков в таблицах.
360
Глава 14. Визуальные компоненты
Прямоугол шое выделение
Сщюка ввода
4 б 8 ;ю ;12 14 Т
8 9 12 |15 il8 21 24 8
27 •
:
12:16 :20 24;28 32 .36 ' j
10 15:20 '25J30 "!з5 40'45 ! 12-ЦЗ^ЗсГзб 42 4 8 ' 5 4 ] 14 21 28'35 42:49 56 63 i 16 '24 32:40 48 56
64 72;
18 27 : 36 45 54 S3 72 81 Переменная [Значение
Выделенная ячейка
•ч
Полосы скроллинга
Рис. 14.60. Табличные компоненты
t
В обработчик в качестве параметров передаются номера строки и столбца, идентифицирующие ячейку, экранные координаты охватывающего прямоугольника для этой ячейки, и состояние ячейки (выделенная, фиксированная, находящаяся в фокусе или простая): property OnDrawCell: TDrawCellEvent; Type TDrawCellEvent = procedure (Sender: TObject; ACol, ARow: Longint; Rect: TRect; State: TGridDrawState ) of object; На основе информации, переданной в обработчик, программа, использующая компонент, имеет возможность вывести изображение ячейки на область отображения компонента с помощью его метода Canvas: property Canvas:
TCanvas;
По умолчанию обработчик события OnDrawCell не вызывается, и компоненты прорисовываются автоматически. Для управления возможностью специальной перерисовки предусмотрено свойство DefaultDrawing (от англ. Default drawing — Автоматическая прорисовка): property DefaultDrawing: Boolean; Если данное свойство имеет значение True, которое установлено, по умолчанию, то прорисовка компонента происходит автоматически. При значении False данного свойства вызывается обработчик OnDrawCell.
361
Часть IV. Компоненты и их использование
Все выше сказанное относится и к компоненту ValueListEditor, расположенному на странице Additional Палитры компонентов. Отличие его от первых двух компонентов состоит в другой направленности отображаемых и редактируемых данных. Если компоненты stringGrid и DrawGrid предназначены для представления табличных данных любого размера, то компонент ValueListEditor предоставляет возможности работы с информацией типа «ключ-значение», то есть имеет всего два столбца.
14.8.2. Структура классов табличных компонентов Все компоненты, предназначенные для работы с информацией в табличной форме, являются наследниками класса TCustofnGrid (см. рис. 14.61), в котором определено общее поведение компонентов такого рода в виде protected-свойств и методов, которые переносятся в области более широкой видимости public и published в классах-наследниках. При этом компонент StringGrid является прямым наследником DrawGrid. Рассмотрим основные свойства класса TCustomGrid: 1. Количество столбцов (ColCount) и строк (RowCount) в таблице. Данные свойства доступны для компонентов StringGrid и DrawGrid через Инспектор объектов. При изменении их значений автоматически изменяется количество строк и столбцов в таблице. Использование этих свойств в компоненте ValueListEditor возможно, но приводит к некорректной его работе.
TComponent TControl TWinControl TCustomGrid
TDrawGrid
I TStrmgGnd TValueListEditor ' Рис. 14.61. Иерархия табличных компонентов
362
Глава 14. Визуальные компоненты
2. Количество фиксированных строк (FixedRows) и столбцов (FixedCols), отображаемых на экране. Фиксированные столбцы и строки находятся соответственно в левой и верхней части компонентов. 3.Ширина столбца ( D e f a u l t C o l W i d t h ) и высота строки (DefaultRowHeight), устанавливаемые по умолчанию при инициализации компонента. В дальнейшем эти параметры могут быть изменены пользователем для каждого из столбцов и строк индивидуально. 4. Номера столбца (Col) и ячейки (Row), которая в данный момент является выделенной. Если выделено несколько ячеек (используется прямоугольное выделение), то его координаты находятся в свойстве Selection. 5. Полосы скроллинга, с помощью которых пользователь может изменять положение видимой области таблицы, настраиваются с помощью свойства ScrollBars перечислимого типа TScrollStyle, рассмотренного при обсуждении аналогичных элементов компонентов Memo и RichEdit. 6. Множественное свойство Options типа TGridoptions управляет несколькими дополнительными возможностями табличных компонентов. Элементы, которые могут входить в множество TGridoptions, представлены в табл. 14.24. Возможные элементы множества Options табличных компонентов Элемент
Таблица 14.24
Назначение
goEditing
Определяет, может ли пользователь (значение True) изменять содержимое ячеек
goFixedVertLine
Определяет, следует ли (значение True) выводить вертикальные линии, разделяющие ячейки таблицы, принадлежащие фиксированным столбцам и линиям
goFixedHorzLine
Определяет, следует ли (значение True) выводить горизонтальные линии, разделяющие ячейки таблицы, принадлежащие фиксированным столбцам и линиям
goVertLine
Определяет, следует ли (значение True) выводить вертикальные линии, разделяющие обычные (не фиксированные) ячейки таблицы
goHorzLine
Определяет, следует ли (значение True) выводить горизонтальные линии, разделяющие обычные (не фиксированные) ячейки таблицы
goRangeSelect
Определяет, имеет ли пользователь возможность (значение True) выделять несколько ячеек одновременно (использовать прямоугольное выделение). Элемент игнорируется, если в множестве присутствует элемент goEditing. В этом режиме прямоугольное выделение невозможно
goDrawFocusSelected
Определяет, следует ли (значение True) использовать для выделенной ячейки специальный цвет фона. Иначе выделенная ячейка только обводится фокусной рамкой
goRowSizing
Определяет, может ли пользователь (значение True) изменять высоту строк в таблице. Высота строки может быть изменена с помощью мыши и только при наличии хотя бы одного фиксированного столбца
363
Часть IV. Компоненты и их использование Таблица 14.24 (продолжение) Назначение
Элемент goColSizing
Определяет, может ли пользователь (значение True) изменять ширину столбцов в таблице. Ширина столбца может быть изменена с помощью мыши и только при наличии хотя бы одной фиксированной строки
goRowMoving
Определяет, может ли пользователь (значение True) изменять порядок строк в таблице. Порядок строк может быть изменен с помощью мыши и только при наличии хотя бы одного фиксированного столбца
goColMoving
Определяет, может ли пользователь (значение True) изменять порядок столбцов в таблице. Порядок столбцов может быть изменен с помощью мыши и только при наличии хотя бы одной фиксированной строки
goTabs
Определяет, может ли пользователь (значение True) перемещаться по ячейкам таблицы с помощью клавиши Tab (Shift-Tab). В противном случае нажатия на данные клавиши будут приводить к перемещению фокуса между компонентами на форме
goRowSelect
Определяет, должно ли текущее выделение (значение True) занимать всю ширину таблицы. Если элемент включен в множество, то при попытке выбора пользователем одной ячейки в качестве активной, будет выбрана вся строка, которой принадлежит эта ячейка. При прямоугольном выделении будет выделены все строки, которые охвачены этим выделением
goAlwaysShowEditor
Если таблица поддерживает редактирование (включен элемент goEditing), то включение в множество Options данного элемента заставляет компонент всегда отображать строку ввода. Выделение ячейки или нескольких ячеек в данном режиме невозможно
goThumbTracking
Определяет, следует ли немедленно (значение True) обновлять видимую область таблицы при перемещении бегунка на полосе скроллинга. Иначе обновление области произойдет только после того, как кнопка мыши будет отпущена
14.8.3. Особенности компонентов StringGrid и DrawGrid Компонент StringGrid построен на основе (является наследником) DrawGrid и реализует возможности редактирования текста с его автоматическим сохранением в свойстве Cells, представляющим собой интерфейс к двумерному массиву строковых значений: property C e l l s [ A C o l , ARow: I n t e g e r ] : String;
Данное свойство доступно во время работы программы для чтения и записи. При изменении какого-либо элемента свойства Cells изображение таблицы автоматически обновляется для отображения нового значения: . .
S t r i n g G r i d l . C e l l s [ 2 , 3] :=
N
6';
При выполнении такой команды в ячейку, которая находится в третьем столбце четвертой строки компонента stringGridl, будет занесено значение '6' (столбцы и строки табличных компонентов нумеруются от нуля). 364
Глава 14. Визуальные компоненты
Аналогично классу TString, совместно с текстовыми значениями, отображаемыми в таблице, компонент stringGrid имеет возможность хранить ссылки на объекты-наследники класса TObject, доступ к которым обеспечивается через свойство Objects: property Objects [ACol, ARow: Integer]: TObject;
Также имеется возможность получить список значений и связанных с этими значениями объектов для какой-либо строки или столбца с помощью свойств Rows и Cols соответственно: property C o l s [ I n d e x : I n t e g e r ] : T S t r i n g s ; property R o w s [ I n d e x : Integer]: TStrings;
Методы прорисовки компонента StringGrid переопределены таким образом, что для вывода текстовых значений не требуется реализация обработчика события OnDrawCell, вывод текста производится автоматически. Заметим также, что возможностью редактирования обладает и компонент DrawGrid, однако информация, введенная пользователем, не сохраняется в компоненте, так как для этого не предусмотрено какихлибо свойств. Таким образом, компонент DrawGrid используется в основном для отображения какой-либо сложной графической информации, а компонент stringGrid для отображения и редактирования текстовой информации. При необходимости в компоненте DrawGrid также можно организовать хранение текстовой информации, вводимой пользователем. Однако это потребует значительных усилий. Перед отображением в ячейке таблицы редактора для ввода значения компонент инициирует событие OnGetEditText, в обработчик которого передается параметр-переменная Value. Значение данного параметра после завершения метода-обработчика будет являться начальным значением, отображенным в строке ввода. Аналогично после завершения пользователем редактирования инициируется событие OnSetEditText, в обработчик которого передается параметр Value, содержащий введенный пользователем текст. property OnSetEditText: TSetEditEvent; property OnGetEditText: TGetEditEvent; Type TSetEditEvent = procedure (Sender: TObject; ACol', ARow: Longint; var V a l u e : s t r i n g ) of object; TGetEditEvent = procedure (Sender: TObject; ACol, ARow: Longint; var Value: string) of object;
365
Часть IV. Компоненты и их использование
14.8.4. Компонент ValueListEditor и его особенности Компонент V a l u e L i s t E d i t o r предназначен для редактирования специфической информации, представленной в виде пар текстовых строк «Ключ-значение». В связи с этим он имеет некоторые ограничения относительно DrawGrid и stringGrid, а также обладает дополнительными возможностями. Компонент ValueListEditor имеет одну фиксированную строку, в которой выводится заголовок таблицы, причем свойство FixedRows для данного компонента недоступно. Также недоступно свойство Cols, так как таблица должна содержать ровно два столбца. Заголовки столбцов хранятся в свойстве TitleCaptions, которое может содержать и более двух строк, однако лишние строки будут проигнорированы компонентом: property T i t l e C a p t i o n s :
TStrings;
В примере, приведенном на рис. 14.60, в качестве значения свойства TitleCaptions было указано три строки:,«Переменная», «Значение» и «Примечание». Как видно, строка «Примечание» никак не повлияла на внешний вид компонента. Фиксированные столбцы в таблице компонента ValueListEditor могут либо не присутствовать вообще, либо возможно использование одного фиксированного столбца, в котором отображаются «ключи». Организация фиксированного столбца имеет смысл, если пользователь не должен редактировать названия «ключей». Возможность редактирования ключа пользователем, и вообще поведение компонента в отношении работы с ключами, определяется множественным свойством KeyOptions, возможные элементы которого приведены в табл. 14.25: property K e y O p t i o n s :
TKeyOptions;
Поскольку компонент ValueListEditor часто применяется для редактирования списка значений по заранее известным на этапе разработки ключам, то в отличие от компонента StringGrid предусмотрена возможность формирования таблицы с помощью Инспектора объектов. Содержимое таблицы содержится в свойстве Strings и изменяется с помощью специального редактора, показанного на рис. 14.62. 59: property S t r i n g s : T S t r i n g s ;
И, наконец, множественное свойство DisplayOptions определяет некоторые особые характеристики отображения компонента: property DisplayOptions:
TDisplayOptions;
Заметим, что данный редактор реализован, видимо, с помощью самого компонента ValueListEditor. 366
Глава 14. Визуальные компоненты Возможные элементы множества KeyOptions компонента ValueListEditor Элемент
Таблица 14.25
Назначение
keyEdit
Определяет возможность (значение True) выбора пользователем ячейки из первого столбца («ключа») и его редактирование. В противном случае невозможен даже выбор ячейки. Вне зависимости от состояния данного элемента таблица должна поддерживать редактирование, то есть в множество Options должен быть включен элемент goEditing
keyAdd
Определяет возможность (значение True) добавления пользователем новой пары «Ключ-значение» в таблицу. Пользователь добавляет строку таблицы с помощью клавиши Insert. Если после добавления строки и ключ, и его значение оставлены пустыми, то при выделении ячейки, не принадлежащей данной строке^ новая строка автоматически удаляется
keyDelete
Определяет возможность (значение True) удаления пользователем строки из таблицы с помощью комбинации клавиш Ctrl-Delete
keyUnique
Определяет возможность (значение False) создания в таблице строк с одинаковыми ключами. Если пользователь изменяет значение ключа таким образом, что в таблице становится несколько одинаковых ключей, то операция редактирования прерывается с помощью исключения (класса Exception). Если исключительная ситуация не будет обработана программно, то пользователю будет выведено сообщение о недопустимости ввода такого ключа
Кем
[Value
X
;ю
Y
10
Ширина
;'ню
Высота
40С|
Cancel
Рис, 14.62. Редактор Инспектора объектов для свойства Strings компонента ValueListEditor
Элементы, которые могут быть включены в множество Displayoptions представлены в табл. 14.26. Возможные элементы множества DisplayOptions компонента ValueListEditor Элемент
Таблица 14.26
Назначение
doColumnTitles
Следует ли (значение true) отображать верхнюю строку таблицы с заголовками столбцов
doAutoColResize
Включает автоматическое изменение ширины столбцов при изменении размеров компонента. Столбцам устанавливается одинаковая ширина, если в данное множество не включен элемент doKeyColFixed
doKeyColFixed
Запрещает автоматическое изменение ширины столбца, содержащего «ключи» при изменении размеров компонента. Изменение ширины данного столбца пользователем возможно при наличии элемента goColSizing в множестве Options
367
I
Часть IV. Компоненты и их использование
Так как данные, представляемые компонентом V a l u e L i s t E d i t o r , являются связанными попарно, то и доступ к ним осуществляется более сложным образом, чем к информации, редактируемой в других визуальных компонентах. В свойстве Strings, редактирование которого в Инспекторе объектов выполнено с помощью сложного диалога, хранятся строки вида «Ключ=3начение». Соответственно, разбор таких строк основывается на поиске первого вхождения в каждую строку символа «равно». Существенно облегчает задачу .тот факт, что среди символов, составляющих название ключа, не может использоваться данный символ. Для упрощения анализа информации представленной в таблице, предусмотрены свойства Keys и Values, представляющие соответственно списки ключей и значений для строки с заданным номером: property Keys[Index: Integer]: string; property Values[const Key: string]: string; Таким образом, для определения названия ключа, который находится в строке таблицы с номером N, следует воспользоваться следующей конструкцией: <Переменная типа String> := ValueListEditorl.Keys[N]; Заметим, что данное свойство всего лишь отображает левый столбец таблицы. Поэтому, если в компоненте присутствует строка с заголовками столбцов, то заголовок столбца с названиями ключей будет выдан при обращении к свойству Keys с индексом 0: ValueListEditorl.Keys[0]. Количество элементов, содержащихся в свойстве Keys, соответствует количеству строк в таблице, которое можно получить с помощью свойства RowCount, как и для других табличных компонентов. Свойство Keys может быть полезно в случаях, когда пользователь имеет возможность изменения значений в ключевом столбце таблицы. В противном случае список ключей и так известен программе. Более интересной задачей является определение значения, сопоставленного какому-либо ключу, с помощью свойства Values, которое индексировано не целыми числами, а строковыми значениями. В качестве индекса данному свойству передается название ключа, компонент производит поиск значения, соответствующего заданному ключу, и выдает его. Если задано название ключа, не соответствующее ключам списка, то выдается пустая строка. Если заданному названию соответствует несколько ключей из списка, то выдается значение, сопоставленное первому из найденных ключей. Использование свойства Values может иметь следующий вид: <Переменная типа String>
368
:= ValueListEditorl.Values['Ширина'];
Глава 14. Визуальные компоненты
Чтобы получить значение, сопоставленное какому-либо ключу в строке таблицы с номером N, можно использовать обращение одновременно и к свойству Keys, и к свойству Values: <Переменная типа S t r i n g > : = . V a l u e L i s t E d i t o r l . V a l u e s [ValueListEditorl.Keys[N]]; Ч
14.9. Пример использования визуальных компонентов. Пишем текстовый редактор В качестве примера использования некоторых визуальных компонентов, описанных в настоящей главе, реализуем несложный текстовый редактор (см. рис. 14.63), построенный на основе компонента RichEdit, со следующими возможностями: » выбор размера и атрибутов шрифта (полужирный, наклонный, подчеркнутый, перечеркнутый), а также его цвета. Возможность выбора названия шрифта реализована не будет; » установка выравнивания текста абзаца по левому и правому краю, а также по центру; « организация списков; » установка отступов для абзацев;
f Размер tupwra: |i0 "Т£| В Undo
/ Ц S
Цвет: )• Maroon
Redo ){Отступ слева: И ? ~ 4 Справа | 0 - H
VJ- < - l -
> ||;1^3..^
Первая строка: И7
~4\
Пример В качестве примера ис.ти.шьзова.Т'нги^которьк визуальных компонентов, опггсднны^в настоящей плаве, реализуем нзслез&ный текстовый редактор ( ), построенный н основе компонента /? 7 chEdf t, со ет'дутоп^яли возможностями; • />ыбор размера и атрибутов шрифта (полужирный, наклонный, подчёркнутый, перечеркнутый), а также его цвета. Возможность выбора названия шрифта реализована не будет; • Л' становка выравнивания текста абзаца по левому и правому краю, а также по центру; • С/рганизацяя списков; • «У становка отступов для абзацев; Сохранение и печать..,""""" : ; Формат: <•* Rich Text Format!*.ttf) f То/ькотекст ("txtj
Загрузить
Сохранить
'
"!"
'.""'-"
Печатать
РИС. 14.63. Текстовый редактор на основе стандартных компонентов
369
Часть IV. Компоненты и их использование
* чтение и запись файлов с текстовой информацией в формате RTF или в текстовом виде (по выбору пользователя); •» возможность отмены последних изменений и возврата к исходному состоянию текста; » печать редактируемого документа на принтер, установленный в операционной системе по умолчанию. Для обеспечения изменения пользователем всех перечисленных атрибутов текстового документа будем использовать стандартные визуальные компоненты, объединенные в панель инструментов. Для всех элементов управления, с которыми может работать пользователь, установим специальные курсоры мыши и всплывающие подсказки. Создание заготовки панели инструментов Начнем с построения панели инструментов. Расположим на форме компонент ControlBar и установим с помощью Инспектора объектов значение alTop его свойству Align. Временно установим высоту компонента, заведомо большую, чем это необходимо, например 150 точек, чтобы было удобно добавлять в нее подпанели. Изменение высоты панели инструментов можно произвести через Инспектор объектов (свойство Height), либо с помощью мыши в визуальном построителе. Добавим в панель инструментов ControlBarl три панели типа Panel и одну кнопку SpeedButton. Установим их размеры, как показано в табл. 14.27. Таблица 14.27
Параметры панелей Название
Назначение
Размеры (Width, Height)
Panel!
Характеристики шрифта
(22, 462)
Panel2
Отступы
(22, 406)
Panels
Undo / Redo
(22, 106)
SpeedButtonl
Кнопка "Нумерация"
(22, 50)
Изменение размеров перечисленных компонентов удобно произвести, когда каждая подпанель компонента ControlBarl находится на отдельной строке (см. рис. 14.64). В противном случае размеры для некоторых компонентов могут быть установлены некорректно, так как панели будут обрезаны под размер охватывающего контейнера ControlBarl. Например, на рис. 14.65 показана форма, на которой панели Рапе 11 и Рапе 12 расположены в одну строку, и сумма их необходимых размеров превышает размер компонента ControlBarl. В результате размер панели Рапе12 автоматически уменьшен. 370
Глава 14. Визуальные компоненты
РапеЕ РапЫЗ
Рис. 14.64. Заготовка панели инструментов
Panel2 j „ . , :,: J
Рис. 14.65. Некорректная установка размеров панелей
Поскольку в разрабатываемом приложении будет множество одинаковых элементов управления, то имеет смысл изменять названия компонентов, устанавливаемые визуальным построителем по умолчанию. Это необязательно делать для панелей и подписей, так как программа не обращается к ним во время работы. Однако просто необходимо иметь понятные названия для кнопок и всех остальных неконтейнерных компонентов. Мы будем применять названия компонентов, состоящие из префикса, указывающего на тип компонента, и основной части, определяющей его назначение. В табл. 14.28 приведен список типов компонентов, используемых в программе, и префиксов, которые будут соответствовать им в названиях элементов управления на форме. Также в нашей программе будет использовано таблица 14.28 ,-. гСписок компонент и соответнесколько компонентов UpDown. Однако обра- - ствующих им префиксов щение к ним во время выполнения программы Компонент Префикс не требуется, следовательно, их названия можRichEdit re но оставить установленными по умолчанию. В соответствии с принятым соглашением об именовании сформируем новое название для компонента SpeedButtonl. Данный компонент предназначен для указания того, что абзац является частью некоторого списка, организованного в текстовом документе, и при этом имеет тип SpeedButton. Поэтому
ComboBox
cb
SpeedButton
sb
ColorBox
clb
Edit
e
LabeledEdit
e
RadioButton
rb
371
Часть IV. Компоненты и их использование
логично изменить его название на sbNumbering, где sb является префиксом, определяющим принадлежность компонента к классу TSpeedButton, a Numbering — основной частью названия, указывающей, что данный компонент предназначен для назначения абзацу атрибута, делающего его элементом списка. Напомним, что название компонента задается его свойством Name и может быть изменено во время разработки программы с помощью Инспектора объектов. В результате произведенных действий нами сформирована заготовка для панели инструментов. Теперь можно запустить программу и проверить ее функциональность. Заметим, что при различных перемещениях панелей внутри компонента ControlBarl их размеры нарушаются, что недопустимо для нашего приложения. Поскольку такое поведение компонентов ControlBar невозможно устранить с помощью их свойств, то в качестве решения проблемы можно предложить использование свойства C o n s t r a i n t s каждого из компонентов, содержащихся в панели ControlBarl. Максимальная и минимальная ширина каждого из таких компонентов должна соответствовать начальному значению ширины, указанному в свойстве Width. Например, панель Panell, предназначенная для изменения характеристик шрифта, имеет ширину 462, то есть полям MinWidth и MaxWidth свойства Constraints данного компонента следует также установить значение 462. Аналогично следует поступить и с компонентами Panel2, Panels и sbNumbering. Теперь пользователь не сможет переместить панель на новое место, если ее размеры не позволяют этого сделать. Изменения размеров подпанелей при этом происходить не будет. После установки пределов изменения размеров подпанели панели инструментов будут иметь фиксированную ширину, и все элементы управления, расположенные на них, всегда будут доступны пользователю. Теперь установим свойству A u t o S i z e компонента ControlBarl значение True. Компонент ControlBarl будет располагаться в верхней части главного окна программы (как и ранее) но его высота будет определяться расположением панелей, которые он содержит. В визуальном построителе с помощью мыши расположим компоненты, находящиеся в панели инструментов, следующим образом (см. рис. 14.66): 1. Панель Panell и кнопка sbNumbering в верхнем ряду. 2. Панели Рапе12 и Panels во втором ряду. Ширина формы во время выполнения этой операции должна позволять такое расположение компонентов, для чего временно ее можно сделать заведомо более широкой, чем это требуется. Отметим, что при перемещениях подпанелей внутри панели инструментов ее размер автоматически изменяется в соответствии со значением свойства AutoSize, установленным нами ранее. 372
Глава 14. Визуальные компоненты
РапеВ
Рис. 14.66. Настройка панели инструментов
На данный момент главная форма приложения содержит заготовку панели инструментов, имеющую все необходимые характеристики для расположения на ней элементов управления. Однако обычно подпанели панелей инструментов не имеют трехмерный вид, так как это негативно сказывается на внешнем виде приложения и уменьшает область панелей, предназначенную, собственно, для «инструментов» — элементов управления. Изменим значение свойства BevelOuter панелей Panell, Panel2 и Panels для придания данным панелям «плоского» (flat) стиля на значение bvNone (по умолчанию установлено значение bvRaised). Свойству Flat компонента cbNumbering установим значение True. Отметим, что во время визуальной разработки значение свойства Flat не учитывается, поэтому для проверки поведения кнопки следует запустить программу. Наполнение подпанели управления шрифтом Добавим на панель Panell элементы управления, необходимые для управления параметрами шрифта, в порядке, указанном в табл. 14.29, при этом изменяя после добавления очередного компонента его название, положение и размеры.
Элементы управления шрифтом
Таблица 14.29 Название (Name)
Тип компонента
Положение (Left, Top)
Размеры (Width, Height)
Label
Label! (по умолчанию)
0, 5
84, 13
ComboBox
cbFontSize
96, 1
49,21
SpeedButton
sbBold
152, 1
20,20
SpeedButton
sbltalic
176, 1
20, 20
SpeedButton
sbllnderlined
200, 1
20, 20
SpeedButton
sbStrikedOut
224, 1
20,20
Label
Label2 (no умолчанию)
256,5
28, 13
ColorBox
clbColor
290,0
95, 22
SpeedButton
sbLeftAlign
392, 1
20, 20
SpeedButton
sbCenter Align
416, 1
20, 20
SpeedButton
sbRightAlign
440, 1
20, 20
373
Часть IV. Компоненты и их использование
Теперь настроим добавленные на панель Panell элементы управления. Для начала изменим свойства Caption меток Labell и Label2 на соответственно 'Размер шрифта:' и 'Цвет:'. Кнопкам атрибутов шрифта sbBold, s b l t a l i c , s b U n d e r l i n e d и sbStrikedOut назначим подписи 'В', Т, 'U' и 'S'. При желании можно повысить информативность этих кнопок, добавив атрибуты, которыми они управляют в характеристики шрифта их же подписей. Например, для кнопки sbBold можно установить флаг f sBold в подсвойстве Style свойства Font. Аналогично поступим и с остальными кнопками. Кнопки управления атрибутами шрифта должны иметь возможность фиксации в нажатом состоянии, причем независимо друг от друга. Для обеспечения такой возможности следует расставить в свойства Group index данных компонентов различные значения, отличные от нуля, например 1, 2, 3 и 4. Таким образом, из четырех кнопок SpeedButton организованы четыре группы зависимых компонентов. Для того, чтобы кнопки можно было перевести в состояние «не нажато», следует установить во всех этих компонентах значение True свойствам AllowAllUp. Компонент ComboBox с названием cbFontsize должен содержать список наиболее используемых размеров шрифтов, поэтому изменим содержимое его свойства items, поместив в него следующие строки: 8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72. Начальный размер шрифта можно установить с помощью свойства itemindex (выбранный элемент). Если данному свойству присвоить значение 2, то в строке ввода будет отображено число 10. Настройка компонента выбора цвета сводится к установке используемых наборов цветов с помощью свойства Style в Инспекторе объектов. В нашем примере установлены все элементы данного множества, за исключением cbSystemColors, так как этот набор цветов используется в основном для работы с системой, а не с текстовыми документами. Для того, чтобы в выпадающем списке отображались удобные для пользователя названия цветов, в множество Style добавлен также элемент cbPrettyNames. . Теперь перейдем к настройке трех кнопок SpeedButton, предназначенных для изменения выравнивания текста в абзаце. По своей сути данные кнопки зависимы друг от друга, причем в каждый момент времени одна из них должна быть нажата, так как абзац всегда имеет какое-либо выравнивание. Для организации такого поведения установим свойству Grouplndex всех трех кнопок значение 5, а свойству AllowAllUp значение False. Также изменим подписи на кнопках: '<', '-' и '>'. Для придания всем кнопкам SpeedButton, расположенным на данной панели, плоского вида, установим значения True их свойствам Flat. 374
Глава 14. Визуальные компоненты
Наполнение подпанелей Numbering и Undo/Redo Для придания необходимого внешнего вида и соответствующего поведения подпанели Numbering установим значения свойств компонента sbNumbering, как указано в табл. 14.30. В результате такой настройки свойств, кнопка sbNumbering будет иметь возможность фиксации в нажатом состоянии (свойство Grouplndex положительно) и ненажатом состоянии (свойство AllowAllUp имеет значение True).
Таблица 14.30 Значения свойств компонента sbNumbering
Для создания панели отмены произведенного редактирования добавим на панель Рапе12 два компонента SpeedButton с характеристиками, приведенными в табл. 14.31.
Свойство
Значение
Caption
1,2,3...
Flat
True
Grouplndex
6
AllowAllUp
True
Все остальные свойства компонентов следует оставить без изменения. Характеристики компонент отмены произведенного редактирования
Таблица 14,31
Положение (Left, Top)
Размеры (Width, Height)
Подпись (Caption)
Стиль (Flat)
sbUndo
0, 1
49, 20
Undo
True
sbRedo
56, 1
49, 20
Redo
Trae
Название
Наполнение подпанели управления отступами Разместим равномерно на панели Рапе13 три компонента LabeledEdit, установив их свойствам LabelPosition значение IpLeft и подписи на метках с помощью подсвойства Caption свойства EditLabel: 'Отступ слева:', 'Справа:' и 'Первая строка:'. Изменим названия компонентов (слева направо) на eLeftlndent, eRightlndent и eFirstlndent. Для обеспечения удобства работы с числовыми значениями добавим на данную панель три компонента Up Down, сопоставив каждый из них одной из строк ввода с помощью выбора названия строки ввода в редакторе свойства Associate Инспектора объектов. При назначении ассоциированной строки ввода компонент автоматически располагается около ее правой стороны и принимает соответствующие размеры. Диапазон значений, изменяемых компонентом UpDown по умолчанию ограничен числами 0 и 100, а в текстовом редакторе не должно быть ограничений на отступы абзацев. Отменить ограничения компонента UpDown невозможно, однако можно их существенно смягчить. Для этого следует изменить значения свойств Min и Мах компонентов UpDown. Так как данные свойства имеют тип Smalllnt, то свойству Min можно установить значение —32768, а свойству Мах — значение 32767. 375
Часть IV. Компоненты и их использование
Изменение названий компонентов UpDown возможно, но необязательно, так как эти компоненты не используются в тексте программы. Закончим формирование панели инструментов очисткой свойств Caption панелей Panell, Panel2 и Рапе13. Также для визуального выделения кнопок на панели установим их подписям полужирное начертание (подсвойство style свойства Font). Таким образом, разработанная нами панель эквивалентна требуемой, показанной на рис. Установим форме необходимый размер и перейдем к созданию остальных интерфейсных элементов главного окна. Создание области редактирования и панели управления файлом
Для создания панели управления файлом используем контейнерный компонент GroupBox, привязав его к нижней, части формы (для этого свойству A l i g n следует установить значение alBottom). Свойству Caption установим значение «Сохранение и печать...». Размер контейнера следует установить заведомо большим, чем это требуется, для удобства размещения в нем элементов управления. Добавим на панель GroupBoxl компоненты, перечисленные в табл. 14.32. При этом по мере добавления будем изменять некоторые их свойства на указанные в таблице. После расположения компонентов на панели установим ее вертикальный размер (свойство Height): 90. В свободную область формы добавим компонент RichEdit, сразу же изменив его название на reEditor. Данный компонент должен занимать всю свободную область, оставшуюся от панели инструментов и панели управления файлом, поэтому присвоим свойству Align компонента reEditor значение alClient.
Таблица 14.32
Перечень компонентов на панель GroupBoxl
Подпись (Caption)
Название
Положение (Left, Top)
Размеры (Width, Height)
Label
Labels (по умолчанию)
8, 17
53, 13
Формат:
Label
Label4 (по умолчанию)
8,38
38, 13
Файл:
RadioButton
rbRTF
72, 16
137, 17
Rich Text Format (*.rtf)
RadioButton
rbTextOnly
208, 16
121, 17
Только текст (*txt)
Edit
eFileName
56,39
473, 19
SpeedButton
sbLoad
16,64
105, 21
Загрузить
SpeedButton
sbsave
128,64
105, 21
Сохранить
SpeedButton
sbPrint
240, 64
105, 21
Печатать
Тип компонента
376
Глава 14. Визуальные компоненты
Дружественный интерфейс Поскольку на форме расположено довольно много элементов управления, причем назначение некоторых из них может быть не ясно для пользователя, мы будем использовать быстрые подсказки, всплывающие при наведении указателя мыши на компонент. Чтобы показать, каким именно образом следует работать с компонентом, установим для каждого из них специальный курсор мыши. Строки ввода автоматически поддерживают изменение указателя мыши, поэтому изменение курсора для них не требуется. Всем элементам управления, которые подразумевают нажатие кнопки мыши (кнопкам SpeedButton, спискам выбора ComboBox и ColorBox, вспомогательным компонентам UpDown и зависимым переключателям RadioButton), установим курсор (свойство Cursor) crHandPoint. Заполним значения свойств Hint для компонентов, перечисленных в табл. 14.33.
Список компонент, для которых нужно заполнить значения свойства Hint
Таблица 14.33
Функция
Компонент cbFontSize
Размер шрифта, который следует установить для выделения, в пунктах (1/72 дюйма)
sbBold
Применить к шрифту выделения атрибут «Полужирный»
sbltalic
Применить к шрифту выделения атрибут «Наклонный»
sbllnderlined
Применить к шрифту выделения атрибут «Подчеркнутый»
sbStrikedOut
Применить к шрифту выделения атрибут «Перечеркнутый»
clbColor
Выбор цвета для шрифта выделения
sbLeftAlign
Установить для текущего абзаца выравнивание по левому краю
sbCenter Align
Установить для текущего абзаца выравнивание по центру
sbRightAlign
Установить для текущего абзаца выравнивание по правому краю
sbNumbering
Сделать текущий абзац элементом списка
sbUndo
Отменить последние изменения
sbRedo
Отменить операцию Undo
eLeftlndent
Отступ текущего абзаца относительно его первой строки
eRightlndent
Отступ текущего абзаца от правой границы
eFirstlndent
Отступ первой строки текущего абзаца от левой границы
rbRTF
Документ сохраняется и считывается в формате RTF
rbTextOnly
Документ сохраняется и считывается в простом текстовом формате
eFileName
Название файла для чтения и записи документа с указанием полного пути
SbLoad
Считать документ с диска
SbSave
Записать документ на диск
sbPrint
Напечатать документ на принтер, установленный по умолчанию
377
Часть IV. Компоненты и их использование
Для того, чтобы подсказки отображались, следует установить свойству ShowHint всех перечисленных компонентов значение True. Итак, нами полностью сформирован пользовательский интерфейс, необходимый для решения поставленной задачи. На этом собственно визуальная разработка заканчивается, и в дальнейшем будет применяться только для назначения обработчиков событиям интерфейсных элементов, расположенных на форме. Далее рассматривается наполнение приложения функциональностью. Наполнение функциональностью Рассмотрим основные действия, которые пользователь производит в текстовом редакторе. 1. Открытие файла выполняется нажатием на кнопку «Загрузить» панели управления файлом. При этом используется имя файла, указанное в строке ввода «Файл:». 2. Перемещение текстового курсора внутри редактора RichEdit с помощью мыши или стрелочных клавиш клавиатуры. Так как при перемещении по тексту курсор попадает во фрагменты текста, использующие разные настройки форматирования, то программа должна реагировать на изменение форматирования текущего абзаца и выделения изменением состояния интерфейсных компонентов. Например, при попадании курсора в текст, имеющий полужирное начертание, кнопка «В» должна принять нажатое состояние. 3. Изменение состояния элементов управления с целью изменения форматирования текущего абзаца или выделения. 4. Сохранение файла с помощью нажатия на кнопку «Сохранить». При этом, так же как и при чтении документа из файла, используется имя файла, указанное в строке ввода «Файл:». Начнем с чтения документа из файла. Для обеспечения корректности данного процесса следует использовать состояние зависимых переключателей на панели управления файлом, чтобы правильно определить тип документа, с который собирается работать пользователь. Тип документа заносится в свойство PlainText компонента RichEdit, после чего вызывается его метод LoadFromFile с указанием имени файла, введенного пользователем в строке редактирования. Сделаем компонент sbLoad активным, перейдем на закладку Events Инспектора объектов и создадим обработчик события Onclick. Реализуем его как показано в листинге 14.3.
378
Глава 14. Визуальные компоненты Листинг 14.3. Чтение документа procedure TForml.sbLoadClick(Sender: TObject); begin reEditor.PlainText := rbTextOnly.Checked; {Установка типа документа. Свойство PlainText получает значение, в зависимости от того, выбран или нет переключатель rbTextOnly. Если rbTextOnly находится в состоянии «включен», то документ представляется как текстовый, без использования расширенного форматирования} reEditor.Lines.LoadFromFile(eFileName.Text) ; {Чтение документа из файла. В качестве имени файла в метод LoadFromFile передается значение свойства Text компонента eFileName} end; Аналогичным образом производится запись документа в файл (см. листинг 14.4.). Листинг 14.4. Запись документа в файл procedure TForml.sbSaveClick(Sender: TObject); begin reEditor.PlainText := rbTextOnly.Checked; {Установка типа документа в соответствии с состоянием переключателя rbTextOnly} reEditor.Lines.SaveToFile(eFileName.Text); {Запись документа в файл. Формат при этом соответствует свойству PlainText} end; Как видно, чтение и сохранение документа реализуется крайне просто, так как поддерживается свойством Lines компонента RichEdit. В соответствии с пунктом 2 программа должна иметь возможность контроля за состоянием текущих абзаца и выделения. Форматирование таких элементов документа можно определить с помощью свойств компонента RichEdit. Отреагировать на смену самого абзаца или выделения можно, реализовав обработчик события OnSelectionChange, который вызывается всякий раз, когда текстовый курсор перемещается внутри документа. Создадим обработчик события O n S e l e c t i o n C h a n g e компонента reEditor и реализуем его, как показано в листинге 14.5.
Часть IV. Компоненты и их использование Листинг 14.5, Контроль за состоянием текущих абзаца и выделения procedure TForml .reEditorSelectionChange(Sender: TObject) ; begin cbFontSize.Text := IntToStr(reEditor.SelAttributes.Size) ; {В компонент выбора размера шрифта переносится высота шрифта текущего выделения. Для преобразования целочисленного значения высоты шрифта в текстовое представление использована функция IntToStr} sbBold.Down := (fsBold in reEditor.SelAttributes.Style); sbltalic.Down := (fsltalic in reEditor.SelAttributes.Style) ; sbUnderlined.Down := (fsUnderline in reEditor.SelAttributes. Style) ; sbStrikedOut. Down : = (f sStrikeOut in reEditor. SelAttributes. Style) ; {Атрибуты шрифта (полужирный и т.д.) переносятся в состояние кнопок управления атрибутами на основе проверки типа <атрибут> in <множество атрибутов текущего выделения>. Если атрибут присутствует в множестве, то соответствующая кнопка фиксируется в нажатом состоянии, иначе в ненажатом} clbColor.Selected := reEditor.SelAttributes.Color; {Цвет выделенного в данный момент текста заносится в свойство Selected компонента выбора цвета} sbLeftAlign.Down := (reEditor.Paragraph.Alignment = taLeftJustify); sbCenterAlign.Down := (reEditor.Paragraph.Alignment = taCenter) ; sbRightAlign.Down := (reEditor.Paragraph.Alignment = taRightJustify) ; {Выравнивание абзаца переносится в состояние кнопок управления выравниванием. Какая-либо кнопка фиксируется в нажатом состоянии, если соответствующее ей выравнивание использовано в текущем абзаце.} sbNumbering.Down : = (reEditor.Paragraph.Numbering = nsBullet); {Кнопка «Элемент нумерации» фиксируется в нажатом состоянии, если текущий абзац является элементом нумерации} eLeftlndent.Text := IntToStr(reEditor.Paragraph.Leftlndent); eRightlndent.Text := IntToStr(reEditor.Paragraph.Rightlndent); eFirstlndent.Text := IntToStr (reEditor. Paragraph. Firstlndent) ; {Значения отступов заносятся в строки ввода в соответствии с отступами текущего абзаца} end;
380
Глава 14. Визуальные компоненты
Остановимся не некоторых интересных моментах данного обработчика. Как было указано ранее, формат RTF предоставляет возможность безошибочной работы приложения, поддерживающего менее широкий круг атрибутов, чем используемый документ. При этом компоненты, предназначенные для чтения таких файлов, пропускают неизвестные им команды форматирования. В Delphi поддерживается не самая последняя версия Rich Text формата, поэтому отсутствуют некоторые возможности. Одной из них является выравнивание текста по ширине, которое можно установить, например, в Microsoft Word. Если такой файл будет прочитан в нашем приложении, то текст, выровненный по ширине, будет иметь выравнивание по умолчанию, а свойство reEditor.Paragraph.Alignment будет иметь некорректное значение. Переменные перечислимых типов (в данном случае свойство reEditor.Paragraph .Alignment) представляются в памяти в виде целочисленных значений от нулевого и далее по порядку. Нулевое значение соответствует первой константе из описания типа, единичное — второй, и так далее. Таким образом, свойство Alignment на самом деле принимает значение 0 (константа t a L e f t J u s t i f y ) , 1 (константа taCenter) или 2 (константа taRightJustify). При чтении документа, использующего выравнивание по ширине, данное свойство имеет значение 3, что, впрочем, не приводит к ошибке в программе. Таким образом, используя прямое приведение типов, можно определить выравнивание по ширине в текущем абзаце и с помощью дополнительного элемента управления сообщить об этом пользователю: if I n t e g e r ( r e E d i t o r . P a r a g r a p h . A l i g n m e n t ) = 3 then begin {Обнаружено выравнивание абзаца по ширине!} end;
Однако представление абзаца на экране все равно не будет соответствовать заданному выравниванию, так как оно не поддерживается компонентом RichEdit. На данный момент можно запустить приложение, загрузить в него документ, созданный, например, в другом редакторе, изменить его информационное содержимое с помощью компонента RichEdit, и сохранить его в файле на диске. При перемещении по документу атрибуты форматирования, использованные в документе, будут корректно отображаться элементами управления панели инструментов. Для изменения форматирования выделения или абзаца следует реализовывать обработчики элементов управления, в которых устанавливаются соответствующие атрибуты текста с помощью свойств SelAttributes и Paragraph компонента reEditor. 381
Часть IV. Компоненты и их использование
Например, для установки атрибута полужирности шрифту текущего выделения, можно реализовать обработчик события Onclick компонента sbBold как показано в листинге 14.6. Листинг 14.6. Установка атрибута полужирности procedure TForml.sbBoldClick(Sender: TObject); begin if sbBold.Down {Если кнопка «В» находится в нажатом состоянии...} then reEditor.SelAttributes.Style := reEditor.SelAttributes.Style + [fsBold] { . . . то в множество атрибутов добавляется элемент fsBold} else reEditor.SelAttributes.Style := reEditor.SelAttributes.Style - [fsBold]; {... иначе элемент fsBold из множества атрибутов удаляется} end;
Числовые атрибуты, задаваемые, например, в строках ввода, могут быть установлены следующим образом: procedure TForml.eLeftlndentChange(Sender: TObject); begin reEditor.Paragraph.Leftlndent := StrToInt(eLeftlndent.Text); {Установка отступа на основе свойства Text компонента eLeftlndent. Для перевода строкового значения свойства Text в числовое, необходимое для свойства Leftlndent, используется функция StrToInt} end;
Поскольку значение в данное поле ввода может быть занесено пользователем некорректно, то такой вызов должен быть заключен в блок обработки исключительной ситуации: procedure TForml.eLeftlndentChange(Sender: TObject); begin try {Начало блока обработки} reEditor.Paragraph.Leftlndent := StrToInt(eLeftlndent.Text); except end;{B случае ошибки никаких действий не производится и сообщение пользователю не выдается, так как использован блок обработки Try..Except, а не Try..Finally} end; 382
Глава 14. Визуальные компоненты
Подобным образом реализовываются обработчики всех остальных элементов управления, использованных в приложении. И, наконец, остановимся на возможности печати, заявленной для нашей программы. Вывод изображения на принтер является сложным процессом, причем сопутствующие задачи, например, выбор принтера и типа бумаги, еще более сложны. Несколько более подробно возможности печати в Delphi изложены ниже, а пока мы будем использовать метод Print компонента RichEdit, который позволяет вывести редактируемый документ на принтер, установленный в системе по умолчанию, без каких-либо предварительных настроек: procedure Print(const Caption: string);
В качестве единственного параметра данному методу передается строка, которая будет идентифицировать документ в очереди печати Windows. Модуль, соответствующий главной форме приложения, приведен в листинге 14.7. Для повышения читабельности ссылки, автоматически добавленные визуальным построителем в заголовок класса формы, объединены в группы, снабженные комментариями: Листинг 14.7. Модуль главной формы unit Unitl; interface
•
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, Buttons, StdCtrls, ExtCtrls, ComCtrls; type TForml = class(TForm) {Текстовый редактор RichEdit} reEditor: TRichEdit; {Панель инструментов} ControlBarl: TControlBar; {Элементы управления панели «Настройка шрифта и выравнивания»} Panell: TPanel; Labell: TLabel; cbFontS i z e: TC omboBox; sbBold: TSpeedButton; sbltalic: TSpeedButton; , 383
Часть IV. Компоненты и их использование sbUnderlined: TSpeedButton; sbStrikedOut: TSpeedButton; Label2: TLabel; clbColor: TColorBox; sbLeftAlign: TSpeedButton; sbCenterAlign: TSpeedButton; sbRightAlign: TSpeedButton; {Элементы управления панели «Отступы»} Panel2: TPanel; eLeftlndent: TLabeledEdit; UpDownl: TOpDown; eRightlndent: TLabeledEdit; UpDovm2: TUpDown; eFirstlndent: TLabeledEdit; DpD оwn 3: TOpD оwn; {Элементы управления панели «Отменить/повторить»} Panels: TPanel; sbtJndo: TSpeedButton ; sbRedo: TSpeedButton; {Кнопка «Нумерация» sbNumbering: TSpeedButton; {Элементы управления панели «Сохранение и печать»} GroupBoxl: TGroupBox; LabelS: TLabel; rbRTF: TRadioButton; rbTextOnly: TRadioButton; Label4: TLabel; eFileName: TEdit; sbLoad: TSpeedButton; sbSave: TSpeedButton; sbPrint: TSpeedButton; {Обработчики событий элементов управления} procedure sbLoadClick(Sender: TObject); procedure sbSaveClick(Sender: TObject); procedure reEditorSelectionChange(Sender: TObject); procedure sbBoldClick(Sender: TObject); procedure sbltalicClick(Sender: TObject); procedure sbUnderlinedClick(Sender: TObject); procedure sbStrikedOutClick(Sender: TObject); procedure clbColorChange(Sender: TObject); procedure sbLeftAlignClick(Sender: TObject); procedure sbCenterAlignClick(Sender: TObject); procedure sbRightAlignClick(Sender: TObject); procedure eLeftlndentChange(Sender: TObject);
384
Глава 14. Визуальные компоненты procedure eRightlndentChange(Sender: TObject); procedure eFirstlndentChange(Sender: TObject); procedure sbUndoClick(Sender: TObject); procedure sbRedoClick(Sender: TObject); procedure sbNumberingClick(Sender: TObject); procedure cbFontSizeChange(Sender: TObject); procedure sbPrintClick(Sender: TObject); private { Private declarations ) public { Public, declarations } end;
var Forml: TForml; implementation {$R *.dfm}
t
i
{Нажатие на кнопку «Загрузить» — чтение текста из файла с учетом формата, определяемого зависимыми переключателями «RTF» и «ТХТ»} procedure TForml.sbLoadClick(Sender: TObject); begin reEditor.PlainText := rbTextOnly.Checked; reEditor.Lines.LoadFromFile(eFileName.Text) ; sbRedo.Enabled := False; end; {Нажатие на кнопку «Сохранить» — запись текста в файл с учетом формата, определяемого зависимыми переключателями «RTF» и «ТХТ»} procedure TForml.sbSaveClick(Sender: TObject); begin reEditor.PlainText := rbTextOnly.Checked; reEditor.Lines.SaveToFile(eFileName.Text); end; {Обработчик события изменения выделения — установка состояния элементов управления в состояние, соответствующее новому выделению} procedure TForml.reEditorSelectionChange(Sender: TObject); begin 13 Зак. 867
385
Часть IV. Компоненты и их использование {Характеристики шрифта} cbFontSize.Text := IntToStr(reEditor.SelAttributes.Size) ; sbBold.Down := (fsBold in reEditor.SelAttributes.Style); sbltalic.Down := (fsltalic in reEditor.SelAttributes.Style); sbUnderlined.Down := (fsUnderline in reEditor. SelAttributes. Style); sbStrikedOut.Down := (fsStrikeOut in reEditor.SelAttributes.Style) ; clbColor.Selected := reEditor.SelAttributes.Color; {Настройки абзаца} sbLeftAlign.Down := (reEditor.Paragraph.Alignment = taLeftrustify) ;
sbCenterAlign.Down := (reEditor.Paragraph.Alignment = taCenter); sbRightAlign.Down := (reEditor.Paragraph.Alignment = taRightJustify) ; sbNumbering.Down := (reEditor.Paragraph.Numbering = nsBullet); eLeftlndent.Text : = IntToStr(reEditor.Paragraph.Leftlndent); eRightlndent.Text := IntToStr(reEditor.Paragraph.Rightlndent); eFirstlndent.Text := IntToStr(reEditor.Paragraph.Firstlndent); end; {Нажатие на кнопку «Полужирный» — установка соответствующего атрибута текущему выделению) procedure TForml.sbBoldClick(Sender: TObject); begin if sbBold.Down then reEditor.SelAttributes.Style := reEditor.SelAttributes.Style + [fsBold] else reEditor.SelAttributes.Style := reEditor.SelAttributes.Style - [fsBold]; end; {Нажатие на кнопку «Наклонный» — установка соответствующего атрибута текущему выделению} procedure TForml.sbltalicClick(Sender: TObject); begin if sbltalic.Down then reEditor.SelAttributes.Style := reEditor.SelAttributes.Style + [fsltalic] else reEditor.SelAttributes.Style := reEditor.SelAttributes.Style - [fsltalic]; end; {Нажатие на кнопку «Подчеркнутый» — установка соответствующего атрибута текущему выделению} procedure TForml. sbllnderlinedClick (Sender: TObject); begin if sbUnderlined.Down then reEditor.SelAttributes.Style := reEditor.SelAttributes.Style + [fsUnderline]
386
Глава 14. Визуальные компоненты else reEditor.SelAttributes.Style := reEditor.SelAttributes.Style - [fsUnderline]; end; {Нажатие на кнопку «Перечеркнутый» -- установка соответствующего атрибута текущему выделению} procedure TForml.sbStrikedOutClick(Sender: TObject); begin if sbStrikedOut.Down then reEditor.SelAttributes.Style := reEditor.SelAttributes.Style + [fsStrikeOut] else reEditor.SelAttributes.Style := reEditor.SelAttributes.Style - [fsStrikeOut]; end; {Выбор цвета пользователем — установка соответствующего атрибута текущему выделению} procedure TForml.clbColorChange(Sender: TObject); begin reEditor.SelAttributes.Color := clbColor.Selected; end; • j {Выбор размера шрифта пользователем} procedure TForml.cbFontSizeChange(Sender: TObject); begin try reEditor.SelAttributes.Size := StrToInt(cbFontSize.Text); except end; end; / {Нажатие на кнопку «Выравнивание по левому краю»} procedure TForml.sbLeftAlignClick(Sender: TObject); begin If sbLeftAlign.Down Then reEditor.Paragraph.Alignment := taLeftJustify; end; {Нажатие на кнопку «Выравнивание по центру») procedure TForml.sbCenterAlignClick(Sender: TObject); begin If sbCenterAlign.Down Then reEditor.Paragraph.Alignment := taCenter; end; i
387
Часть IV. Компоненты и их использование
(Нажатие на кнопку «Выравнивание по правому краю»} procedure TForml.sbRightAlignClick(Sender: TObject); begin If sbRightAlign.Down Then reEditor.Paragraph.Alignment := taRightJustify; end; , {Изменение значения в строке ввода «Отступ слева» -- установка отступа для текущего абзаца} procedure TForml.eLeftlndentChange(Sender: TObject); begin try reEditor.Paragraph.Leftlndent := StrToInt(eLeftlndent.Text); except end; end; {Изменение значения в строке ввода «Отступ справа»} procedure TForml.eRightlndentChange(Sender: TObject); begin try reEditor.Paragraph.Rightlndent := StrToInt(eRightlndent.Text); except end; end;
\
{Изменение значения в строке ввода «Отступ первой строки»} procedure TForml.eFirstlndentChange(Sender: TObject); begin try reEditor.Paragraph.Firstlndent := StrToInt(eFirstlndent.Text); except end; end; {Нажатие на кнопку «Undo» — вызов метода Undo} procedure TForml.sbUndoClick(Sender : TObject); begin reEditor.Undo; sbRedo.Enabled := True ; end; {Нажатие на кнопку «Redo» — вызов метода Undo}
388
Глава 14. Визуальные компоненты procedure TForml.sbRedoClick(Sender: TObject); begin reEditor.Undo; end; {Нажатие на кнопку «Нумерация»} procedure TForml.sbNumberingClick(Sender: TObject); begin If sbNumbering.Down Then reEditor.Paragraph.Numbering := nsBullet Else reEditor.Paragraph.Numbering := nsNone; end; {Нажатие на кнопку «Печать» — вызов метода Print} procedure TForml.sbPrintClick(Sender: TObject); begin reEditor.Print(ЛФайл: Л + eFileName.Text); end; : . .... .•
fc-
,/.;.:,
end.
Таким образом, создание довольно сложного приложения, насыщенного элементами управления, не представляет в Delphi практически никакого труда при четком понимании цели и некоторой ориентации в системе компонентов. Однако реализованная нами программа имеет ряд недостатков, таких, как неудобная для пользователя форма выбора названия файла, в котором хранится текстовый документ, отсутствие возможности поиска подстроки в документе, невозможность настройки принтера и параметров страницы, и так далее. Большинство перечисленных проблем решаются с помощью использования стандартных диалогов Windows, представленных в Delphi в виде компонентов. Диалоговые компоненты помогают существенно упростить некоторые стандартные операции, выполняемые пользователем при работе с документами, особенно текстовыми. 'Второе достоинство диалоговых компонентов состоит в том, что диалоги занимают место на экране только в тот момент времени, когда они действительно нужны пользователю. Исходя из вышесказанного, качество приведенной программы можно существенно повысить с помощью диалоговых компонентов, описанных в следующей главе.
389
Часть IV. Компоненты и их использование
Вопросы с ответами для повторения по главе 14 Расскажите об организации структуры элементов управления в окне Ответ: Компоненты в окнах Windows делятся на две категории — компоненты-контейнеры, которые могут содержать другие компоненты, и обычные элементы управления. Самым первым контейнером в окне является само окно, а при добавлении других оконных компонентов они визуально упорядочиваются по слоям в порядке добавления. Создание очередного элемента управления добавляет новый слой, который является самым «близким» (визуально) к пользователю. Очередные оконные элементы управления могут частично'или полностью перекрывать ранее созданные. В Delphi реализована группа визуальных компонентов, которые являются неоконными, то есть легковесными. При добавлении их в какой-либо контейнер в оконной структуре приложения не происходит каких-либо изменений. При перерисовке контейнера, в котором находятся легковесные компоненты, их изображения выводятся на область отображения контейнера с помощью передачи им свойства Canvas этого контейнера. Таким образом, все легковесные компоненты, принадлежащие какому-либо контейнеру, будут перекрыты всеми оконными компонентами, принадлежащими этому же контейнеру. х Расскажите об методах расположения визуальных компонентов на экране и управления их размерами Ответ: Расположение визуальных компонентов на экране вне зависимости от их разновидности в любой момент времени выполнения программы определяется целочисленными свойствами Left, Top, width и Height. Положение компонентов задается относительно контейнера, которому они принадлежат. Помимо прямого указания положения и размеров элементов управления предусмотрено их автоматическое выравнивание относительно контейнера с помощью свойства Align, позволяющего расположить элемент управления вдоль какой-либо из сторон контейнера, либо развернуть его на всю область контейнера, и свойства Anchors, указывающего привязку отдельных сторон компонента к сторонам контейнера. Вне зависимости от наличия и типа автоматического выравнивания положения компонентов, свойства Left, Top, width и Height всегда содержат корректные координаты компонентов и их размеры. Расскажите обо свойствах видимости и доступности визуальных компонентов Ответ: Визуальные компоненты, как наследники класса TControl, имеют свойство видимости Visible, которое указывает, должно ли изображение элемента управления отображаться на экране. С точки зрения пользователя установка значения False свойству visible эквивалентно уничто-
390
Глава 14. Визуальные компоненты жению элемента управления. Во время визуальной разработки значение данного свойства игнорируется, и все компоненты, размещенные на форме, видны. Использование свойства Visible может использоваться, например, для организации элементов управления в постраничном виде в сочетании с компонентом Tabcontrol. Свойство доступности Enabled использует другой механизм. При установке этому свойству значения Ealse компонент остается видимым пользователю, но не допускает взаимодействия. Все события, такие, как перемещения и нажатия мыши, передаются следующим компонентам, находящимся под недоступным, даже если они визуально скрыты. ,
Расскажите о компонентах, предназначенных для вывода на экран текстовой информации Ответ: Основные компоненты, предназначенные для вывода текстовой информации — это текстовые метки Label и staticText. Оба компонента имеют свойства для управления шрифтом и цветом выводимых символов, а также возможность автоматического разбиения текста на строки. К недостаткам компонентов Label и staticText можно отнести отсутствие прокрутки текста в случаях, когда он не может быть выведен в пределах компонента полностью. Однако предусмотрена возможность автоматического подбора ширины компонента. В случаях, когда на экран должны быть выведены большие объемы текстовой информации, либо выводимый документ имеет сложное форматирование, могут использоваться компоненты Memo и RichEdit. Расскажите о компонентах, предназначенных для ввода текстовой информации Ответ: Для ввода текстовой информации в зависимости от ее сложности можно использовать различные компоненты: » компоненты Edit, LabeledEdit и MaskEdit, применяются для ввода одной строки текста; » компоненты Memo и RichEdit можно использовать для ввода многострочных текстовых документов, причем данными компонентами поддерживается работа с файлами, а компонент RichEdit позволяет устанавливать гибкое форматирование документа; » компоненты StringGrid, DrawGrid, и ValueListEditor предназначены для ввода информации, представленной двумерными таблицами. Заметим, что с помощью свойства Enabled данных компонентов можно временно ограничивать доступ пользователя к изменению информации. При помощи каких компонентов можно обеспечить возможность ввода пользователем числовых значений? Ответ: Для ввода числовой информации применяются элементы управления, предназначенные для ввода текстовой информации, преимущественно Edit и LabeledEdit, либо StringGrid, если данные должны быть
391
Часть IV. Компоненты и их использование представлены в табличной форме. При получении информации из элементов управления строки, введенные пользователем, переводятся в числовые значения средствами программы, например, с помощью функции StrToInt или StrToFloat. Для обеспечения более удобной работы пользователя и повышения корректности вводимой им информации предусмотрен визуальный компонент UpDown, который управляет числовым значением, находящимся в строковрм свойстве какого-либо другого компонента.
' , При помощи каких компонентов можно вывести на экран какоелибо графическое изображение? Ответ: Стандартные компоненты Delphi поддерживают возможности вывода следующих видов графической информации: 1. Числовые параметры могут быть представлены в графическом виде с помощью компонента ProgressBar, показывающего положение параметра в некотором интервале возможных значений. Для представления нескольких параметров возможно использование компонента chart, выводящего на экран диаграммы и графики. 2. Для вывода одиночных графических изображений используется компонент Image, отображающий содержимое графических файлов, записанных в форматах, поддерживаемых классом TPicture. 3. Вывод анимационных avi-файлов обеспечивается компонентом Animate. 4. Компонент paintBox представляет область отображения, на которой может быть выведена любая графическая информация. Расскажите о возможностях элементов управления — кнопок Ответ: Кнопки представлены в стандартных компонентах тремя классами — Button, BitBtn и SpeedButton. Компоненты Button и BitButton реализованы в виде оболочек Windowsэлементов управления, причем класс TBitBtn является наследником TButton и добавляет к возможностям родительского класса вывод изображений на поверхность кнопок и автоматическую установку стиля компонента на основе некоторого набора сочетаний изображений и подписей. Данные компоненты поддерживают также возможность автоматического разбиения подписей на слова и вывод их в несколько строк. Компонент SpeedButton является легковесным компонентом, реализованным исключительно средствами Delphi. Из возможностей компонентов Button и TBitBtn, отсутствующих в SpeedButton, можно отметить только разбиение длинных подписей. Компоненты SpeedButton могут организовывать между собой группы, аналогичные группам зависимых переключателей.
392
Глава 14. Визуальные компоненты Приведите возможные методы организации выбора пользователем одного или нескольких значений из заданного списка Ответ: Для организации списков значений, из которых пользователь может выбрать не более одного, применяются следующие интерфейсные конструкции: » группы компонентов RadioButton, расположенные в одном контейнере, или компонент RadioGroup; * группы компонентов SpeedButton с одинаковым значением свойства Grouplndex; «
СПИСКИ выбора ListBox
И ComboBox.
Для организации списков значений, из которых пользователь может выбрать более одного значения, применяются следующие интерфейсные конструкции: * группы компонентов CheckBox; * компоненты CheckListBox. Опишите основные возможности компонента RichEdit Ответ: Компонент RichEdit предназначен для использования документов, хранимых в формате RTF, что и определяет его основные возможности, направленные на гибкое форматирование текстовой информации. Единицей форматирования может выступать весь документ, один абзац, для которого можно установить характеристики отступов и выравнивания, либо некоторое выделение, состоящее из одного или нескольких символов одного или нескольких абзацев. Для выделения устанавливаются характеристики шрифта и цвета, а также некоторые другие атрибуты.
393
tf
Невизуальные компоненты
Как было сказано ранее, невизуальные компоненты предназначены не для отображения какой-либо информации на экране, и не подразумевают взаимодействия с пользователем. Задача невизуальных компонентов — добавление в программу какой-либо дополнительной функциональности. Количество стандартных невизуальных компонентов, поставляемых с Delphi, существенно выше, чем количество визуальных, однако мы рассмотрим всего один из них: ©1 — Компонент Timer. Служит для генерации событий через заданные интервалы времени. Компонент Timer, расположенный на странице System Палитры компонентов, является одним из самых простых невизуальных компонентов и предназначен для выполнения некоторого фрагмента программы через заданные интервалы времени. Вызываемый фрагмент является обработчиком события OnTimer: property OnTimer: TNotifyEvent; Для настройки интервала времени, через которые происходит вызов обработчика события OnTimer, используются свойства: Interval, задающее длительность интервала в миллисекундах, и Enabled, определяющее, следует ли вызывать обработчик: property Interval: Cardinal; property Enabled: Boolean;
394
Глава 15. Невизуальные компоненты
Механизм, используемый компонентом Timer, полностью построен на возможностях операционной системы, которая предоставляет набор функций для работы с таймером на основе рассылки сообщений WM_TIMER в оконные функции приложений. Таким образом, появление в системе таймерного события и непосредственный вызов его обработчика в программе разделяет некоторое время, зависящее от нагрузки на очередь сообщений и от общей загрузки системы. Существенное влияние оказывают, также, характеристики машины, на которой выполняется программа. Таким образом, компонент Timer предназначен не для отсчета точных интервалов времени, а, скорее, для выполнения каких-либо периодических действий через более или менее одинаковые интервалы времени. В качестве примера можно привести программу, реализующую часы. Стрелки на часах перемещаются один раз в секунду, следовательно, свойству interval следует установить значение 1000. Однако чем дольше работает такая программа, тем больше будет отклонение показаний часов от реального времени из-за задержек при вызовах обработчика. Поэтому в программах такого рода таймер используется не для отсчета времени, а для получения управления обработчиком OnTimer, который определяет реальное время с помощью соответствующих функций (например, функции Time, описанной в модуле Sysutils), определяет на основе этой информации корректное положение стрелок, и выполняет прорисовку, если это необходимо.
395
?б
Создание компонентов во время выполнения программы
Работа в визуальном построителе удобна и проста, однако иногда на этапе разработки программы неизвестно, какие именно компоненты должны быть созданы. Для этого предусмотрена возможность создания компонента во время выполнения, программы.
16.1. Основные действия, выполняемые при создании компонентов Создание компонента без визуального построителя аналогично созданию экземпляра какого-либо класса, так как каждый компонент является классом. Для того, чтобы компонент стал частью общей структуры программы, он должен подчиняться описанным ранее требованиям, основные из которых указаны ниже: 1.Для каждого компонента должно быть установлено свойство принадлежности — ссылка на какой-либо компонент (компонент-владелец), который несет ответственность за разрушение данного компонента при разрушении самого себя; 2. Для визуальных компонентов должно быть установлено свойство визуальной принадлежности — ссылка на компонент-контейнер (компонент-родитель), в котором должен отображаться данный компонент. Ссылка на компонент-владелец передается в качестве параметра при вызове конструктора компонента. Напомним, что конструктор имеет одинаковый вид для всех компонентов60 и описан в классе TComponent: constructor Create(AOwner: TComponent); '" Такое требование имеет место, так как компоненты используются визуальным построителем, соответственно, должны создаваться заранее известным образом. 396
Глава 16. Создание компонентов во время выполнения программы
Например, создание компонента Timer может выглядеть следующим образом: Var MyTimer: TTimer;{Описание ссылки на компонент Timer} Begin MyTimer := TTimer.Create(Panel!) ; {Создание экземпляра компонента Timer. Ответственность за его разрушение возложена на компонент Panell, соответственно при вызове метода Free компонента Panell сначала будет автоматически вызван метод Free компонента M y T i m e r }
Заметим, что в качестве ссылки на компонент-владелец может быть передано значение nil (пустая ссылка), которая будет корректно распознана конструктором компонента. В таком случае, программа, создавшая компонент, должна его самостоятельно разрушить с помощью вызова метода Free, перед закрытием, либо при отсутствии необходимости его дальнейшего использования: Var MyTimer: TTimer; {Описание ссылки на компонент timer} Begin MyTimer := TTimer.Create(nil); {Создание экземпляра компонента Timer. Ссылка на компонент-владелец не передается, следовательно, ответственность за разрушение компонента берет на себя программа} MyTimer.Free;
{Разрушение компонента}
В первом примере прямое разрушение компонента не требуется, но возможно. При этом компонент-владелец Panell автоматически удалит компонент MyTimer из списка зависимых компонентов, получив уведомление о его разрушении с помощью механизма обязательных уведомлений, описанного ранее.
397
Часть IV. Компоненты и их использование
16.2. Создание визуальных компонентов Процесс создания визуальных компонентов должен содержать в себе еще один дополнительный шаг, помимо установки свойства принадлежности — указание компонента, который является контейнером (визуальным владельцем) для создаваемого. Ссылка на контейнер заносится в property-свойство Parent: property P a r e n t : T W i n C o n t r o l ;
Присвоение ссылки данному свойству автоматически перемещает компонент из одного контейнера в другой. Рассмотрим, например, создание визуального компонента (метки Label) во время выполнения программы (листинг 16.1). Листинг 16.1. Создание визуального компонента (метки Label) во время выполнения программы
Var MyLabel: TLabel;
{Описание ссылки на компонент Timer}
Begin MyLabel := TLabel.Create(Panell); {Создание экземпляра компонента Label. Ответственность за его разрушение возложена на компонент P a n e l l , соответственно при вызове метода Free компонента Panell сначала будет автоматически вызван метод Free компонента M y L a b e l } Label.Parent := Panell; {Указание визуальной принадлежности компонента MyLabel — в качестве контейнера для него указывается компонент Panell, который является еще и его владельцем}
Еще одной существенной особенностью создания визуальных компонентов является необходимость настройки свойств, управляющих положением компонента, его размерами, и другими свойствами, определяющими представление элемента управления на экране. Такие действия выполняются обычным образом, с помощью изменения значений соответствующих свойств компонентов. В качестве примера создания компонентов во время выполнения программы, приведем приложение (см. рис. 16.1), создающее при нажатии кнопки 'Создать' экземпляр компонента Edit, и располагающее его в панели (см. рис 16.2). 398
Глава 16. Создание компонентов во время выполнения программы
Panell
Рис. 16.1. Создание компонентов во время выполнения программы. Форма в процессе разработки
Создать !|
Перенести 1
Разрушить панель
Рис. 16.2. Создание компонентов во время выполнения программы. Вид приложения после нажатия кнопки «Создать»
При нажатии второй кнопки ('Перенести') компонент, созданный ранее, перемещается в другой контейнер — главное окно приложения (см. рис. 16.3). Для визуального переноса компонента в новый контейнер достаточно просто изменить свойство визуальной принадлежности
Panell
Создать
Рис. 16.3. Создание компонентов во время выполнения программы. Вид приложения после нажатия кнопки «Перенести»
399
Часть IV. Компоненты и их использование
Предусмотрим еще одну кнопку — 'Разрушить панель' — и в обработчике нажатия на нее будем вызывать метод Free панели Panel 1. В результате панель будет удаляться с экрана, разрушая при этом строку ввода, вне зависимости от того, где именно она расположена — на панели или в окне формы. Для создания описанного приложения запустим среду разработчика Delphi, и расположим на форме компонент Panel, которому будет присвоено название Panel 1. Далее расположим на форме три компонента Button, которым визуальный построитель назначит имена B u t t o n l , Button2 и Buttons. Реализуем обработчики события Onclick для кнопок соответствующим образом. Программный код модуля главной формы приложения приведен далее, в листинге 16.2. Листинг 16.2. Практика создания визуальных компонентов unit Unitl; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls; type TForml = class(TForm) Panell: TPanel; Buttonl: TButton; Button2: TButton; ButtonS: TButton; procedure ButtonlClick(Sender: TObject); procedure Button2Click(Sender: TObject); procedure ButtonSClick(Sender: TObject); private Editl: TEdit; {Описание ссылки на экземпляр класса TEdit — строку ввода, которая будет создана во время выполнения программы} public { Public declarations } end;
var
Forml: TForml; implementation {$R *.dfm)
400
Глава 16. Создание компонентов во время выполнения программы procedure TForml.ButtonlClick(Sender: TObject);
Begin {Обработчик события нажатия на кнопку ^Создать'} Editl := TEdit.Create(Panell) ; {Создание экземпляра класса TEdit, в качестве объекта-владельца указан компонент Panell} Edit!.Left := 10; Editl.Top := 10; Editl.Width := Panell.ClientWidth - 20; Editl.Height := Panell.ClientHeight - 20; {Установка свойств положения и размеров созданного компонента} Editl.Parent := Panell; {Указание визуальной принадлежности компонента} end; procedure TForml.Button2Click(Sender: TObject); begin .{Обработчик события нажатия на кнопку 'Перенести'} Editl.Parent := Self; {Изменение свойства визуальной принадлежности. В качестве нового контейнера указывается ссылка на форму с помощью ключевого слова Self} end; procedure TForml .ButtonSClick(Sender: TObject); begin {Обработчик события нажатия, на кнопку Л Разрушить панель'} Рапе11.Free; {Разрушение панели вызовом метода Free. Перед непосредственным разрушением Panell будет вызван метод Free компонента Editl, созданного во время выполнения программы, в результате чегоон исчезнет с экрана, а обращение к нему через ссылку Editl станет невозможным} end; end. Заметим, что при смене контейнера компонент перемещается относительно верхнего левого угла формы. Это связано с тем, что его положение, заданное свойствами Left и Тор? не изменяется, тогда как эти значения рассматриваются относительно контейнера. Теперь представим себе ситуацию, когда кнопки Buttonl, Button2 и Button3 были бы размещены в панели Panell. В этом случае при разрушении панели они будут удалены из окна приложения, так как разрушается контейнер, в котором они находятся. При этом владельцем всех трех компонентов является форма, поэтому реально кнопки будут разрушены только при закрытии программы.
401
Часть IV. Компоненты и их использование
16.3. Назначение обработчиков событий Вне зависимости от того, каким образом был создан компонент — в визуальном построителе, или во время выполнения программы, он одинаковым образом интегрируется в общую схему работы, в том числе поддерживается и возможность использования событий. Ссылка на обработчик события компонента представляет собой property-свойство, которое имеет какой-либо процедурный тип данных. Например, в нашем приложении можно организовать обработку события OnKeyPress компонента E d i t l , создаваемого во время выполнения программы. Процедурный тип свойства OnKeyPress описан следующим образом: Туре TKeyPressEvent = procedure (Sender: TObject; var Key: Char) of object; Следовательно, обработчик события должен являться методом какоголибо класса (так как использована конструкция of object) и иметь два параметра: Sender типа TObject, и параметр-переменную Key типа Char. Опишем такой метод в классе формы, поместив его заголовок в секцию p r i v a t e интерфейсной части, и реализовав его в описательной части модуля: TForml = class(TForm) private Editl: TEdit; Procedure EditEvent(Sender: TObject; var Key: Char) ; Procedure TForml.EditEvent(Sender: TObject; var Key: Char); begin If Key = 'x' Then Key := #0^ end; (Описание метода EditEvent, заставляющий игнорировать нажатия на клавишу Лх' } Для назначения данного обработчика элементу управления следует занести ссылку на метод в свойство OnKeyPress, используя название метода: procedure TForml.ButtonlClick(Sender: begin Editl := TEdit.Create(Panell); 402
TObject);
Глава 16. Создание компонентов во время выполнения программы
Editl.Left := 10; Editl.Top := 10; Editl.Width := Panell.ClientWidth - 20; Editl.Height := Panell.ClientHeight - 20; Edit!.OnKeyPress := EditEvent; {Присвоение свойству OnKeyPress ссылки на метод EditEvent. Данный метод будет вызываться в качестве обработчика события OnKeyPress} Editl.Parent := Panell; end;
16.4. Использование массивов компонентов Использование ссылок на компоненты не имеет каких-либо ограничений, поэтому существует возможность организации массивов таких ссылок: Var RTComponents: Array[1..10] Of TComponent;
Массивы компонентов могут применяться для автоматизированной обработки набора компонентов, например, изменения размеров или положения, назначения обработчиков каких-либо событий, или анализа объекта-источника сообщения в обработчиках: Var
RTComponents: Array[1..10] Of TComponent; procedure TForml.OnKeyPress(Sender: TObject; var Key: Char); Var
i: Integer; begin For i := 1 To 100 do
If Sender = RTComponents[i]; Then end;
403
Часть IV. Компоненты и их использование
Вопросы с ответами для повторения по главе 16 Приведите правила создания компонентов во время выполнения программы Ответ: Для создания компонента во время выполнения программы следует выполнить такие действия: » создать экземпляр компонента с помощью вызова конструктора create, передав, при необходимости, в качестве параметра ссылку на компонент, который будет нести ответственность за разрушение создаваемого компонента. Если компонент будет разрушен в тексте программы, то ссылку можно не передавать, использовав значение nil; » для визуальных компонентов установить свойство визуальной принадлежности Parent, присвоив ему ссылку на контейнер, в котором отображается создаваемый компонент; « для визуальных компонентов установить свойства, определяющие положение, размеры и другие необходимые визуальные характеристики элемента управления; » при необходимости реализовать в виде методов какого-либо класса и назначить компоненту обработчики событий; • если при создании компонента не был указан владелец Owner, разрушить компонент, вызвав его метод Free. Если компонент имеет владельца, то он будет разрушен автоматически.
404
Использование диалоговых компонентов
17.1. Общие методы и события диалоговых компонентов Как отмечалось ранее, диалоговые компоненты являются наследниками класса TCommonDialog, в котором описан метод Execute, предназначенный для вывода диалогового окна на экран. Во время разработки приложения диалоговые компоненты отображаются на форме так же, как и н евизуальные — в виде иконок соответствующих им в Палитре компонентов. Метод Execute является функцией, возвращающей логическое значение, соответствующее тому, как пользователь завершил работу с диалогом. Значение True возвращается, если пользователь выполнил все необходимые действия, подразумеваемые данным диалоговым окном, и нажал, например, на кнопку Ok. Значение False возвращается, если пользователь отказался от работы с диалогом. Функция Execute описана в компоненте TCommonDialog следующим образом: Function Execute: Boolean; virtual; abstract; Как видно из описания данного метода, он является абстрактным, соответственно, компонент предназначен не для работы с ним, а для построения других компонентов на его основе. В классе TCommonDialog определены два события, которые поддерживаются всеми диалоговыми окнами — QnShow, обработчик которого, если такой задан, вызывается при выводе диалогового окна на экран, и OnClose, обработчик которого вызывается при скрытии диалога с экрана.
405
Часть IV. Компоненты и их использование
17.2. Общие свойства диалоговых компонентов Помимо метода Execute в классе TCommonDialog описано несколько свойств, которые могут использоваться для более гибкой работы с диалогом. Property-свойство ctl3D присутствует в диалоговых компонентах для обратной совместимости с шестнадцатиразрядными версиями Windows и определяет стиль диалогового окна и элементов управления в нем. Значение True указывает на «трехмерный» вид диалога, а значение False, соответственно, на «плоский». . property Ctl3D: Boolean; В property-свойстве H a n d l e содержится идентификатор диалогового окна, выведенного на экран. Соответственно значение свойства Handle имеет смысл, пока окно находится на экране, а после его закрытия Handle обнуляется. property Handle: HWnd; Необходимо заметить, что внешний вид диалога, вызываемого командой Execute, формируется не самим диалоговым компонентом, а операционной системой, вследствие чего существенно зависит от версии Windows, в которой выполняется программа.
17.3. Стандартные диалоговые компоненты Диалоговые компоненты представлены на закладке Dialogs Палитры компонентов Delphi и могут быть разделены на несколько логических групп: 1. Диалоги для работы с файлами (открытие или сохранение). В данную группу входят диалоги открытия и сохранения файлов OpenDialog и SaveDialog, а также диалоги открытия и сохранения графических файлов OpenPictureDialog и SavePictureDialog. 2.Диалоги выбора шрифта и цвета FontDialog и ColorDialog. 3. Диалоги настройки параметров печати EjrintDialog, PrinterSetupDialog И PageSetupDialog. 4. Диалоги текстового поиска и замены FindDialog и ReplaceDialog. Все представленные диалоги готовы к работе, не требуют установки дополнительных свойств и вызова каких-либо методов, и тесно интегрированы со средой. Так, например, в диалоге выбора принтера PrintDialog присутствует список всех принтеров, доступных системе, а в диалоге выбора шрифта FontDialog список шрифтов автоматически заполняется установленными Ё Windows шрифтами. Рассмотрим подробно каждую группу компонентов. 406
Глава 17. Использование диалоговых компонентов
17.4. Диалоги для работы с файлами В Delphi предусмотрены следующие диалоговые компоненты для работы с файлами: Щ; OpenDialog
— диалог открытия файла (см. рис. 17.1).
Щ SaveDialog
- диалог сохранения файла (см. рис. 17.2).
|Ц] OpenPictureDialog — диалог открытия графического файла (см. рис. 17.3). {Щ SavePictureDialog — диалог сохранения графического файла (см. рис. 17.4).
£) •if-ActnColorMaps
AppEvnts
' ActnCtrls
^Consts
5s DBCIientActnRes
if Controls
^DBCIientActns
_ ActnList
if DBCommon
if ActnMan
Spbdeconst
Sf CtlPanel
^DBConnAdmin
ActnMenus
if bdemts
^CustomiseDIg
jf DBConsts
Sf'ActnPopup
3P Buttons
•' CustomizeDIg
j^ActnRes
if CheckLst
if Dbtxcept
^Clipbrd ipADOConst
if'CmAdmCtl
if ADODB
^ComCtrls
jf dbcgrids
0
^DBCIient
ComStrs
Имя Файла: Тип файлов:
Рис. 17.1. Диалог открытия файла
а в- и-
Папка- |tj|Vd 1^'ActnColorMaps
Jf AppEvnts
^AclnCtrls Ф ActnList
jf'AxCtrls ^BandActn
^ActnMan
Sf^bdeconst
SJp ActnMenus
1$ DBCIientActnRes
Jf Consts tf Controls
flf'DBCIientActns
^CtlConsts
^ DBCommon
•J^CtlPanel
if DBConnAdmin If DBConsts
^CustomizeDIg
Sf^ActnPopup
^bdomts 1]^ Buttons
§ CustomizeDIg
I^ActnRes
^CheckLst
Sf DataBkr
^'DBCtrls Sf'DbExcept
$ActnRes
^Clipbrd
Sf^DB
if DBGrids
if'ADOConst
^CmAdmCtl
jf'DBActns
Hbei
if ComCtrls
if' dbcgrids
if DBLocal
%$ ComStrs
If
if'DBLocall
^ADODB If ADOInt
1
DBCIient
.'•-'
ТипФ-эйла:
Рис. 77.2. Диалог сохранения файла
407
Часть IV. Компоненты и их использование
Папка:
:
"jl Рабочий стол
а ш-
ZQ Мой компьютер
Picture:
Г""""""
Мое сетевое окружение
(None)
Имя файла:
|^ VamUSimulato
Тип Файлов:
JAII(".jpg; .ipeg; '.bmp;''.tco;*.emf;' .wm
K
1
t
Рис. 17.3. Диалог открытия графического файла
Папка:
£У Vcl
Имя Файла: Тип Файла:
JAII(:'.ipgj>'.ipeg;*.bmp;''.ico;''.emf;''.wrfif)
^j
Рис. 17.4. Диалог сохранения графического файла
Все диалоги для работы с файлами имеют одинаковые свойства, так как компоненты S a v e D i a l o g , O p e n P i с t u r e D i a 1 од и SavePictureDialog являются наследниками класса TOpenDialog (см. рис. 17.5). Диалоги открытия и сохранения графических файлов принципиально отличаются от обычных файловых диалогов только возможностью просмотра открываемого файла в самом диалоге. Помимо свойств, унаследованных от классов TObject, TPersistent, TComponent и TCommonDialog, в классе TOpenDialog описаны свойства настройки начального вида диалога для более удобной работы пользователя, в основном, связанные с фильтрацией файлов, представленных в диалогах открытия и сохранения файлов. Рассмотрим свойства и события диалогов открытия и сохранения. 408
Глава 17. Использование диалоговых компонентов TCommonDialog TOpenDialog TSaveDialog
TOpenPictureDiulog TSavePictureDialog
Рис. 17.5. Иерархия файловых диалогов
17.4.1. Свойства файловых диалогов Свойство InitialDir При выводе окон диалогов на экран пользователю предоставляется список файлов в некотором каталоге, определяемом свойством InitialDir: property InitialDir: String; Значением этого свойства может являться строка, содержащая любой допустимый в операционной системе путь к какому-либо каталогу, в том числе и сетевому. Если значением свойства InitialDir является пустая строка, то поведение диалога определяется значением глобальной логической переменной ForceCurrentDirectory. Данная переменная описана в модуле D i a l o g s , автоматически включаемом в список используемых модулей при добавлении диалогового компонента на форму. Если переменная ForceCurrentDirectory имеет значение True, то при открытии диалога в нем отображается текущий рабочий каталог, в противном случае будет отображено содержимое каталога «Мои документы». Заметим, что при повторном открытии диалога в нем будет выбран каталог, который был отображен при закрытии диалога, несмотря на свойство InitialDir. При отказе пользователя от работы с диалогом начальный каталог не изменяется. Свойство Filter Стандартные диалоги для работы с файлами поддерживают возможность фильтрации выводимых файлов по маске. Механизм фильтрации реализован на основе списка групп файлов, которые следует выводить одновременно друг с другом. Используя выпадающий список в файловых диалогах, пользователь может просматривать разные группы файлов, в зависимости от строки, выбранной в этом списке. 409
Часть IV. Компоненты и их использование
Количество и состав групп файлов определяется свойством Filter: property Filter: String; Значением свойства должна являться строка следующего вида: <Название группы 1>|<Маска 1; ...; Маска N>|. . . |<Название группы М>|<Маска I; ...; Маска К> Пользователь выбирает группы файлов, руководствуясь Названиями групп, после чего файлы, отображаемые в диалоге, будут перечитаны с учетом масок, соответствующих выбранной группе. В качестве примера приведем возможное значение свойства F i l t e r для двух групп файлов, в одной из которых будут находиться файлы с расширением pas, a в другой — с и срр. В Выпадающем списке диалога пользователь будет видеть две строки: «Pascal and Delphi f i l e s ( * . p a s ) » и «С and C++ f i l e s ( * . c , *.cpp)>>: Delphi and Pascal f i l e s ( * . p a s ) | * . p a s | C and C++ files (*.c, *.cpp)|*.c; *.cpp Справа от поля редактирования данного свойства в Инспекторе объектов находится кнопка вызова диалога для более удобного формирования значения, данные в котором представляются в табличном виде (в левой части таблицы представлены названия групп файлов, а в правой — маски). Заметим, что маски файлов формируются по обычным правилам и могут включать в себя не только символы «*» и «.», но и все другие конструкции, используемые обычно для формирования масок файлов. Например маска «??.срр» указывает на то, что в диалоге должны быть отображены файлы с расширением срр, имя которых состоит точно из двух букв. Настройка функциональности диалогов Файловые диалоги позволяют пользователю только указать на какойлибо файл и по умолчанию не проверяют, существует ли выбранный файл на самом деле, и допускается ли' в него запись. Таким образом, на программу ложится довольно сложная процедура определения корректности выбора пользователя. Однако действия, которые в таких случаях должны выполняться, стандартны для любого приложения, поэтому они уже реализованы операционной системой. Для гибкой настройки функциональности д и а л о г о в в классе TOpenDialog описаны множественные свойства Options и OptionEx: property Options: TOpenOptions; property OptionsEx: TOpenOptionsEx;
410
Глава 17. Использование диалоговых компонентов
Оба свойства аналогичны по своей структуре — это множества, каждый из элементов которых является флагом, указывающим необходимость выполнения тех или иных проверок при работе пользователя с диалогом. В Инспекторе объектов эти свойства разворачиваются таким образом, что каждое из значений отображается в виде логического значения. Значение True указывает на то, что элемент следует включить в множество, a False — что элемент не должен присутствовать в множестве. Свойство optionEx более простое и может содержать всего один элемент — ofExNoPlacesBar, наличие которого в множестве отменяет левую часть диалогов, содержащую кнопки быстрого доступа к журналу, рабочему столу, сетевому окружению и папке Мой компьютер. Свойство tiptions имеет значительно больше возможных значений (см. табл. 17.1). Состав свойства Options файловых диалогов Элемент
Изменение функциональности диалога при включении элемента в множество
Таблица 17.1 Область применения
ofHideReadOnly
Удаляет из диалога переключатель «Только для чтения»
Используется только в диалогах открытия
ofReadOnly
Устанавливает переключатель «Только для чтения» в выбранное состояние
Используется только в диалогах открытия
ofOverwritePrompt
Если в диалоге сохранения пользователь выбирает уже существующий файл, на экран выводится диалог с предупреждением, в котором можно отказаться от произведенного выбора и продолжить работу с диалогом
Используется только в диалогах сохранения
ofNoChangeDir
После выбора файла устанавливает текущий каталог на выбранный до вызова диалога
ofShowHelp
Отображает кнопку с символом «?» рядом с кнопкой закрытия диалога
ofNoValidate
Отключает проверку на наличие недопустимых символов в выбранном имени файла
Используется обычно в диалогах сохранения
ofAllowMultiSelect
Включает возможность выбора пользователем нескольких файлов
Используется обычно только в диалогах открытия
ofExtensionDifferent
Элемент добавляется в множество Options, в случае, если расширение выбранного файла не соответствует указанному в свойстве DefaultExt
Используется только во время выполнения программы
ofPathMustExist
Включает проверку на существование каталога при выборе файла. Не допускает выбор файлов в некорректном каталоге
Используется обычно в диалогах сохранения
ofFileMustExist
Включает проверку на существование выбранного файла. Не допускает выбор несуществующих файлов
Используется обычно в диалогах открытия
ofNoReadOnlyReturn
Включает проверку на возможность записи в выбранный файл. Не допускает выбор файлов, имеющих атрибут Readonly
Используется обычно в диалогах сохранения
ofCreate Prompt
Если в диалоге сохранения пользователь выбирает несуществующий файл, на экран выводится диалог с предупреждением, в котором можно отказаться от произведенного выбора и продолжить работу с диалогом
'
411
Часть IV. Компоненты и их использование Таблица 17.1 (продолжение) '
' '
Элемент
Изменение функциональности диалога при включении элемента в множество
Область применения
ofShareAware
Отключает проверку на возможность доступа к файлу при использовании его другими программами и разрешает выбор таких файлов
ofNoTestFileCreate
Отключает проверку на возможность изменения файлов при использовании сетевых путей
ofOldStyleDialog
Придает диалогу внешний вид, который использовался в более ранних версиях Windows
ofNoNetworkButton
Удаляет из диалога старого образца кнопку обращения к сетевому окружению
Используется только совместно с элементом ofOldStyleDialog
ofNoLongNames
Отображает в списке файлов только файлы с короткими именами (8 символов в имени и 3 в расширении)
Используется только совместно с элементом ofOldStyleDialog x
ofNoDereferenceLinks
Запрещает внутреннюю переадресацию при выборе ярлыков, например, на рабочем столе. Вместо файла, на который указывает ярлык, возвращается имя ярлыка (файл с расширением Ink)
ofEnablelncludeNotify
Указывает Windows на необходимость посылки сообщения CDNJNCLUDEITEM в диалоговое окно при выборе пользователем очередного каталога. Таким образом, программа может контролировать список файлов, видимых пользователю
ofEnableSizing
Разрешает изменение размеров диалогового окна пользователем
ofDontAddToRecent
Запрещает добавление выбранных файлов в список часто используемых
ofForceShowHidden
Разрешает просмотр и выбор файлов, имеющих атрибут Hidden
Используется только в диалогах сохранения
Используется только в Windows 2000 и более поздних версиях, в остальных игнорируется
Результаты работы файловых диалогов Файловые диалоги являются модальными окнами, то есть приостанавливают выполнение программы с момента своего появления на экране, и до завершения работы с диалогом. В зависимости от того, выбран ли пользователем какой-либо файл, или нет, метод E x e c u t e возвращает значение True или False. Поэтому вызов данного метода обычно выглядит следующим образом: If
Л <Имя компонента>.Execute Then <Действия в случае корректного выбора> Else <Действия в случае некорректного выбора>;
Если пользователем выбран файл, то есть метод Execute возвратил значение True, то программа может получить доступ к названию выбранного файла через свойство FileName (название файла возвращается с указанием полного пути): property FileName: 412
TFileName;
Глава 17. Использование диалоговых компонентов
Если в свойстве Options компонента установлен флаг ofAllowMultiSelect, то в свойстве FileName содержится название первого файла из списка, а для доступа к названиям всех выбранных файлов следует использовать свойство Files: property F i l e s :
TStrings;
Свойство Files имеет область видимости public, а не published, поэтому может быть доступно только во время выполнения программы.
17.4.2. События файловых диалогов В диалоговых компонентах, предназначенных для работы с файлами, определены события, позволяющие более гибко контролировать процесс работы пользователя. 1. Событие OnCanClose возникает для выполнения дополнительной проверки возможности закрытия диалога обработчиком данного события после обычной проверки средствами самих диалогов. Обработчик события OnCanClose имеет возможность обратиться к свойству FileName и Files компонента и вернуть флаг закрытия диалога, передаваемый обработчику через параметр-переменную CanClose: property OnCanClose:
TCloseQueryEvent;
Процедурный тип TCloseQueryEvent описан следующим образом: Type TCloseQueryEvent = procedure(Sender: TObject; var CanClose: Boolean) of object;
В случае, если диалоговое окно следует закрыть, обработчик должен возвратить значение True в параметре-переменной CanClose, если же по результатам дополнительной проверки диалоговое окно не должно быть закрыто, значение параметра CanClose устанавливается в False. 2. Событие OnFolderChange возникает при смене пользователем текущего каталога: property OnFolderChange:
TNotifyEvent;
3. Событие onincludeitem возникает при выводе в диалоговое окно очередного имени файла и может использоваться для более сложной фильтрации списка файлов, чем стандартная фильтрация по маске. property O n i n c l u d e i t e m :
TIncludeltemEvent;
Механизм фильтрации состоит в следующем. Обработчик вызывается для каждого файла, который диалог собирается вывести на экран 413
Часть IV. Компоненты и их использование
в соответствие с масками, заданными для выбранной пользователем группы. Методу передаются два параметра: один с описанием файла, который включается в список, а второй — параметр-переменная, в которой следует вернуть логическое значение, указывающее, включать имя файла в список или нет. Мы не будем подробно рассматривать использование данного события, но заметим, что оно возникает только если в свойство Options включен элемент ofEnablelncludeNotify. 4. Событие OnSelectionChange возникает при попытке пользователя изменить список файлов в диалоге, например, создать каталог или изменить его название, выбрать другой файл или изменить состав списка файлов, выбрать новый групповой фильтр, и так далее. property OnSelectionChange:
TNotifyEvent;
В обработчике события возможен доступ к свойствам компонента, чтобы определить, какие именно действия выполняет пользователь с диалогом. 5. Событие OnTypeChange возникает при выборе пользователем нового группового фильтра. property OnSelectionChange:
TNotifyEvent;
17.4.3. Пример использования файловых диалогов В качестве примера использования файловых диалогов разработаем программу, позволяющую с помощью диалога OpenDialog выбрать несколько файлов, а затем выводящую список файлов на экран в визуальный компонент TMemo. Для этого создадим новый проект и добавим на форму следующие компоненты: 1.OpenDialog с закладки Dialogs, 2. Button с закладки Standard, З.Мето с закладки Standard. , Форма с добавленными компонентами представлена на рис. 17.6. Развернем свойство Options в окне Инспектора объектов, найдем элемент ofAllowMultiselect, и присвоим ему значение True, чтобы дать пользователю возможность выбора нескольких файлов одновременно. Установим свойству ScrollBars компонента Memol значение ssBoth, чтобы пользователь мог просматривать список файлов, даже если он не поместится полностью в окно многострочного редактора.
414
Глава 17. Использование диалоговых компонентов
Button!
Рис. 17.6. Форма в процессе разработки
Создадим обработчик события Onclick компонента Buttoni, и реализуем его, как показано в листинге 17.1. Листинг 17.1. Использование диалога открытия файлов procedure TForml.ButtonlClick(Sender: TObject); begin if not OpenDialogl.Execute Then Exit; {Вызов метода Execute с одновременной проверкой результата работы пользователя. Если метод Execute возвратил значение False, то есть пользователь отказался от работы с диалогом, обработчик заканчивается} Memol.Lines.Assign(OpenDialogl.Files); {Копирование списка файлов из диалогового компонента (свойство Files) в многострочный редактор (свойство Lines) с помощью метода Assign} end;
Запустим программу и нажмем на кнопку, в результате чего на экран будет выведен диалог (см. рис. 17.7). Поскольку диалог открытия файла является модальным, то пользователь не может переключиться в окно программы и должен работать с диалогом. Если вместо выбора файла или файлов, или вместе с выбором пользователь нажмет клавишу Отмена, то диалог будет закрыт, а метод Execute, вызванный в обработчике ButtonlClick вернет в вызвавшую подпрограмму значение False, в результате чего метод ButtonlClick завершится вызовом процедуры Exit. Если же пользователь выберет один или несколько файлов и нажмет кнопку Открыть, то метод Execute, вызванный в обработчике, вернет в вызвавшую подпрограмму значение True, метод ButtonlClick не завершится процедурой Exit, а выполнит копирование списка файлов в компонент Memol. 415
Часть IV. Компоненты и их использование
2]amt221 ZlEraseMe llFromSDS SlFunct !lj ostrog IjSmallPas 3oi -AudtoTrackOl 3 02 - AudioTrack 02 3 03 -AudioTrack 03 J w - AudioTrack 04 3 05 -AudioTrack 05
Имя Файла:
Зоб AudioTrack 06 Щ 17 - AudioTrack 17 jju7 AudioTrack 07 018 -AudioTrack 18
2|06 AudioTrack 08 EU.C |09 AudioTrack 09 Sja.cp |10 AudioTrack 10 m> |H AudtoTrackli
3s |и
J16
AudioTrack 12 AudioTrack 13 AudioTrack 14 AudioTrack 15 AudioTrack 16
9> ipb
1! Graphics ^JnoExtl ^Попса ^расписанне
j|
Тип файлов:
Рис. 17.7. Выполнение программы
17.5. Диалоги выбора шрифта и цвета В Delphi предусмотрены следующие диалоговые компоненты для выбора шрифтового и цветового оформления документов: .. • ., *
р£|| — компонент FontDialog. Диалог выбора шрифта (см. рис. 17.9). Л — компонент ColorDialog. Диалог выбора цвета (см. рис. 17.8). Оба рассматриваемых в данном разделе диалоговых компонента независимы друг от друга и объединены в связи с их схожим предназначением — выбором атрибутов представления какой-либо информации (обычно текстовой). Компоненты являются прямыми наследниками класса TCommonDialog, переопределяют метод Execute этого класса для вывода диалогового окна на экран, и реализуют свойства, необходимые для настройки самих диалогов и доступа вызывающей подпрограммы к результатам работы пользователя с ними.
17.5.1. Работа с компонентом ColorDialog Компонент C o l o r D i a l o g отображает диалоговое окно, в котором пользователь может выбрать один из заданных цветов, разбитых на два набора — «Основные цвета», формируемый операционной системой, и «Дополнительные цвета», формируемый программно. Также предусмотрена возможность формирования цвета пользователем на основе широкой палитры, представленной в правой части диалогового окна, если это допустимо, в соответствии с настройками компонента, описанными ниже.
416
Глава 17. Использование диалоговых компонентов
Основные цвета:
"' |Ц1
Шггггттг Шггггшшт mi^rmmmmm т о тттятт 'Ш.шшшшт~яг Ы.
:
Дополнительные цвета:
• ;
гшшшгЩт 'т яягтгя
Sjjf
Отгенок:}бс
Красный
в" ;
Отмена
. Добавить в набор
Рис. 17.8. Диалог выбора цвета
Вместо выбора цвета из палитры можно ввести его составляющие на основе одной из двух моделей представления цвета — RGB или HSB. Если пользователь отказался от работы с диалогом, закрыв его нажатием кнопки Отмена или кнопки закрытия диалога в правом верхнем углу окна, метод Execute возвращает значение False и вызывающая подпрограмма не должна рассматривать результаты работы диалога. При выборе пользователем цвета и нажатии кнопки ОК, метод Execute возвращает значение T r u e , а выбранный цвет заносится в свойство Color компонента, описанное в классе T C o l o r D i a l o g следующим образом: property Color:
TColor;
Для настройки диалога выбора цвета предусмотрено свойство Options: property O p t i o n s :
TColorDialogOptions;
Тип TColorDialogOptions является множественным, и аналогичен типу TOpenOptions, используемому для настройки файловых диалогов. Элементы, входящие в данное множество, и влияние, которое они оказывают на функциональность диалога выбора цвета, представлены в табл. 17.2. Теперь рассмотрим, как можно настроить дополнительную группу цветов, расположенную в левой нижней части диалога. Изначально все цвета в этой группе черные, но их можно переопределить с помощью свойства CustomColors, имеющего тип TStrings, являющегося списком строк следующего вида: Со1ог<Х>=<Шестнадцатеричное RGB-представление цвета> 14 Зак. 867
417
Часть IV. Компоненты и их использование Состав свойства Options диалога выбора цвета Изменение функциональности диалога при включении элемента в множество
Элемент cdFullOpen
Диалоговое окно отображается в расширенном виде — с палитрой для формирования пользовательского цвета
cdPreventFullOpen
Не разрешается нажатие на кнопку «Определить цвет» для перевода диалога в расширенный или обычный режим
cdShowHelp
Рядом с кнопкой закрытия окна отображается кнопка помощи
cdSolidColor
В диалоге выбирается сплошной цвет, ближайший к выбранному пользователем
cdAnyColor
Разрешает пользователю выбирать любой цвет
Таблица 17.2 Примечание
Вид диалога определяется элементом cdFullOpen
Выбранный цвет может быть смешан из нескольких
Шестнадцатеричное RGB-представление цвета является 32-ух битным числом, в котором RGB-составляющие записаны наоборот и на каждую составляющую отводится два разряда, каждый из которых представляется шестнадцатеричной цифрой от 0 до F. Причем признак шестнадцатеричного числа $ при этом не указывается. Например, самый яркий красный цвет будет выглядеть в таком представлении следующим образом: OOOOFF. Символ <Х> определяет одну из шестнадцати позиций, в которых могут находиться дополнительные цвета. Верхний ряд цветов поименован символами А, В, С, D, E, F, G и Н, а нижний, символами I, J, К, L, M, N, О и Р: Для назначения цветов позициям свойство CustomColors может быть использовано как во время разработки программы с помощью Инспектора объектов, так и во время ее работы с помощью методов класса TStrings: property C u s t o m C o l o r s :
TStrings;
Например, чтобы заполнить угЛовые клетки синим, красным, зеленым и белым цветами, можно задать в Инспекторе объектов следующий список строк для свойства CustomColors: ColorA=FFOOOO ColorH=OOOOFF ColorI=OOFFOO ColorP=FFFFFF Отметим, что использование пробелов в строках недопустимо. Строки, содержащие пробелы и символы названий цветовых ячеек, выходящих за пределы ряда А..Р, игнорируются.
418
Глава 17. Использование диалоговых компонентов
17.5.2. Работа с компонентом FontDialog Свойства FontDialog Диалог выбора шрифта позволяет пользователю указать атрибуты некоторого текстового документа, такие, как шрифт, начертание, размер, цвет и другие. Диалог тесно интегрирован с операционной системой и самостоятельно выполняет множество функций, облегчающих работу с ним. Например, список шрифтов, доступных системе, определяется автоматически, а в список начертаний и размеров выводятся только те, которые поддерживаются шрифтом, выбранным в списке шрифтов. В случае выбора пользователем шрифта и нажатия кнопки ok метод Execute возвращает значение True и фрагмент программы, вызвавший диалоговое окно, может получить доступ к характеристикам шрифта, установленным пользователем, с помощью свойства Font: property Font:
TFont;
Далее это свойство может быть присвоено соответствующему свойству какого-либо другого объекта, либо использовано по частям с помощью свойств и методов класса TFont. При первом запуске диалогового окна текущими характеристиками шрифта являются те, которые были установлены для свойства Font во время визуального проектирования приложения с помощью Инспектора объектов. При последующих вызовах диалога устанавливаются текущие характеристики, соответствующие последнему сеансу работы диалога, при условии, что пользователь после выбора характеристик шрифта не отказался от работы с диалогом, а нажал на кнопку ok. Шрифт:
Начертание;
[MS Sans Serif MS Ser» Ч? МТ Extra % OzHandicraft ВТ 0 Palatino Linotype Ч PosteiBodoni ВТ Roman :
В идоизменение -г Г" Зачеркнутый Г" Подчеркнутый
Образец АаВЬБбФФ
Цвет:: )И Черный
| Набор символов: | Кириллица
Рис. 17.9. Диалог выбора шрифта 419
Часть IV. Компоненты и их использование
В зависимости от назначения документа можно ограничить выбор пользователя наборами шрифтов, применяемыми для вывода только на экран или принтер, либо в списке шрифтов могут присутствовать шрифты для вывода и на экран и на принтер. Формированием списка управляет свойство Device перечислимого типа TFontDialogDevice: property
Device:
TFontDialogDevice;
Свойство Device может принимать одно из трех значений: f dScreen
— отображать шрифты для вывода на экран (устанавливается по умолчанию); f d P r i n t e r — отображать шрифты для вывода на принтер; f dBoth — отображать шрифты для вывода и на экран и на принтер. Для ограничения размеров шрифта, которые пользователь может выбрать в диалоге, предусмотрены целочисленные свойства MinFontSize и MaxFontSize: property M i n F o n t S i z e : property M a x F o n t S i z e :
Integer; Integer;
Помимо того, что эти свойства должны иметь положительные значения, в свойстве Options, аналогичном одноименным свойствам рассматриваемых ранее диалогов, должен быть включен флаг fdLimitSize. Другие возможные настройки диалога, доступные через свойство Options, перечислены в табл. 17.3. Состав свойства Options диалога выбора цвета Элемент
Таблица 17.3
Изменение функциональности диалога при включении элемента в множество
fdApplyButton
Отображается кнопка «Применить» для подтверждения выбранного шрифта без закрытия диалога. Возникает событие OnApply
fdAnsiOnly
В списке шрифтов отображаются только те, которые поддерживают символьную кодировку Windows
fdNoOEMFonts
Удаляет OEM-шрифты из списка шрифтов
fdRxedPitchOnly
В списке шрифтов отображаются только те, которые являются моноширинными
fdScalableOnly
Отображает только масштабируемые шрифты в списке шрифтов
fdNoVectorFonts
Удаляет векторные шрифты Windows 1 .0 из списка шрифтов
fdTrueTypeOnly
Отображает в списке шрифтов только TrueType шрифты
fdWysiwyg
Отображает в списке шрифтов только те, которые могут использоваться одновременно и на экране и на принтере. Шрифты, зависимые от устройства выводаТне отображаются
fdNoSimulations
Отображает в списке шрифтов и стилей только те варианты, которые поддерживаются шрифтами напрямую, исходя из их описаний. Программная возможность придания шрифтам начертаний bold и italic не учитывается
fdEffects
Отображаются зависимые переключатели «Зачеркнутый» и «Подчеркнутый»
fdLimitSize
Разрешает использование свойств MinFontSize и MaxFontSize
420
Глава 17. Использование диалоговых компонентов Таблица 17.3 (продолжение) Элемент
Изменение функциональности диалога при включении элемента в множество
fdForceFontExist
Не допускает выбор шрифта, название которого не представлено в списке шрифтов. При попытке выбора выдается предупреждение о необходимости выбора шрифта только из списка
fdNoFaceSel
Запрещает автоматический выбор названия шрифта при открытии диалога
fdNoSizeSel
Запрещает автоматический выбор размера шрифта при открытии диалога
fdNoStyleSel
Запрещает автоматический выбор начертания шрифта при открытии диалога
fdShowHelp
Отображает кнопку помощи рядом с кнопкой закрытия окна
События FontDialog В диалоговом компоненте FontDialog реализовано событие OnApply, возникающее при нажатии кнопки «Применить» в диалоговом окне, если это допустимо в соответствии со свойством Options данного компонента. Работа этого свойства обычно аналогична обычному анализу диалога, просто в этот момент само диалоговое окно находится на экране, что позволяет пользователю просмотреть результат выбора шрифта, а программе реализовать возможность отката изменений, если после одного или нескольких вызовов обработчика O n A p p l y пользователь отказался от работы с диалогом.
17.5.3. Пример использования диалогов выбора цвета и шрифта В качестве примера использования описываемых диалогов создадим программу, которая будет предоставлять пользователю возможность изменять цвет формы и характеристики шрифта текстовой метки. Для этого запустим IDE Delphi, создадим новый проект, и поместим на главную форму следующие компоненты: * » » »
текстовая метка Label; две кнопки Button; диалоговый компонент ColorDialog; диалоговый компонент FontDialog.
Изменим значение свойства Caption текстовой метки Labell на «Текстовая метка», свойство Text компонента в-uttoni на «Цвет», а свойство Text компонента Button2 на «Шрифт». В результате получим форму, представленную на рис. 17.10. Создадим обработчики событий Onclick для кнопок Buttonl (изменение цвета формы) и But ton 2 (изменение шрифта метки, как показано в листинге 17.2).
421
Часть IV. Компоненты и их использование
Текстовая метка Цвет
Шрифт
Рис. 17.10. Форма в разработке Листинг 17.2. Обработчики событий OnClick для кнопок Buttonl Procedure TForml.ButtonlClick(Sender: TObject); Begin {Обработчик OnClick компонента-кнопки Buttonl} If ColorDialogl.Execute Then Color := ColorDialogl.Color; {В случае успешного завершения работы пользователя с диалоговым окном выбора цвета, выведенным методом Execute компонента ColorDialogl, цве* из свойства Color диалогового компонента копируется в свойство Color формы} End; Procedure TForml.Button2Click(Sender: TObject); Begin {Обработчик OnClick компонента-кнопки Button!} If FontDialogl.Execute Then Labell.Font.Assign(FontDialogl.Font); {В случае успешного завершения работы пользователя с диалоговым окном выбора шрифта, выведенным методом Execute компонента FontDialogl, характеристики шрифта из свойства Font диалогового компонента копируются в свойство Font текстовой метки Labell с помощью метода Assign} end; Результат работы программы представлен на рис. 17.11.
7- For-ml
Текстовая мотка Цвет
Шрифт
Рис. 17.11. Результат работы программы
422
Глава 17. Использование диалоговых компонентов
17.6. Диалоги текстового поиска и замены В Delphi предусмотрены следующие диалоговые компоненты для реализации поиска и замены текстовых фрагментов в документах: Щ — компонент FindDialog. Диалог поиска подстроки (см. рис. 17.12 ); gp! — компонент ReplaceDialog. Диалог замены подстроки (см. рис. 17.13 ). Рассматриваемые диалоги предназначены для предоставления возможности пользователю быстрого поиска подстроки в некотором текстовом документе, либо ее замены на другую подстроку. Сами диалоги не выполняют таких действий, а представляют собой пользовательский интерфейс, позволяющий указать, какую подстроку следует искать в документе, на какую строку следует заменить найденное вхождение (в случае диалога замены), и некоторые полезные характеристики поиска для более удобной работы пользователя. Каким образом использовать информацию, введенную пользователем, решает непосредственно программа, вызвавшая диалог.
•Что;
Найти далее
[Наука и Техника]
Г" Только слово целиком
"г С учетом регистра
г Н аправяение
Отмена
-.
I
1 *"* Bgepx f*1 ' Вниз ;
Рис. 17.12. Диалог текстового поиска JLJ.XJ Найти далее I Чей: '|Наука и Техника
Заменить Заменить все |
Г" Только слово целиком Р* 1C учетом регистра!
Отмена
Рис. 17.13. Диалог замены подстроки Диалоги поиска и замены подстроки, в отличие от других описываемых диалоговых компонентов, не являются модальными, то есть вызов диалога не приостанавливает работу программы, а метод Execute сразу же, не дожидаясь окончания работы с диалогом, возвращает значение True. При этом диалоговое окно находится на экране, пока пользователь его не закроет61. 61
Программа, вызвавшая диалог, также может его закрыть.
423
Часть IV. Компоненты и их использование
17.6.1. Работа с компонентами поиска и замены Механизм работы с такими компонентами несколько отличен от работы с модальными диалогами, в которых результат действий пользователя рассматривается обычно после закрытия диалогового окна, кроме случаев расширенного программного контроля файловых диалогов. Поскольку диалоги поиска и замены работают параллельно с программой, ее реакция на изменения в них сводится к обработке некоторых событий. ' TCotnmonDialog TFindDialog TReplaceDialog Рис. 17.14. Иерархия диалогов поиска и замены
В классе T F i n d D i a l o g реализована поддержка события OnFind, обработчик которого вызывается при нажатии пользователем на кнопку «Найти далее». Компонент ReplaceDialog наследуется от TFindDialog (см. рис. 17.14), поэтому также имеет это событие. Событие OnFind является чисто уведомительным, то есть в обработчик передаются только данные о его источнике, чего, тем не менее, вполне достаточно для анализа действий пользователя: property O n F i n d :
TNotifyEvent;
В компоненте R e p l a c e D i a l o g реализовано еще одно событие — OnReplace, вызываемое при нажатии пользователем кнопки «Заменить». Аналогично событию OnFind в обработчик OnReplace также не передается никакой дополнительной информации о событии: property OnReplace:
TNotifyEvent;
Таким образом, для использования компонента текстового поиска FindDialog необходимо: 1. Вызвать диалоговое окно методом Execute компонента. 2. Реализовать обработчик события OnF.ind для реакции на действия пользователя. З.При необходимости реализовать обработчик события OnClose, возникающего при закрытии диалогового окна. Использование компонента ReplaceDialog аналогично, но также следует реализовать обработчик события OnReplace. 424
Глава 17. Использование диалоговых компонентов
17.6.2. Настройка функциональности диалогов FindDialog и ReplaceDialog В компоненте F i n d D i a l o g реализовано множественное свойство Options, аналогичное по сути одноименным свойствам других диалоговых компонентов: property O p t i o n s :
TFindOptions;
Данное свойство используется для настройки внешнего вида диалога, то есть состава элементов управления в нем, а также для анализа состояния введенной пользователем дополнительной информации. Элементы множества Options приведены в табл. 17.4. Заметим, что компонент ReplaceDialog также использует это свойство, так как является наследником FindDialog, более того, некоторые элементы множества Options используются только в диалоге замены. Состав свойства Options диалогов текстового поиска и замены Элемент frDisableMatchCase frHideMatchCase frDisableWholeWord frHideWholeWord frDisableUpDow frHidellpDown frDown frMatchCase
frWholeWord
frRndNext
frReplace frReplaceAII
fdShowHelp
Изменение функциональности диалога при включении элемента в множество Отключает (не допускает изменение состояния) независимый переключатель «С учетом регистра» Удаляет независимый переключатель «С учетом регистра» Отключает (не допускает изменение состояния) независимый переключатель «Только слово целиком» Удаляет независимый переключатель «Только слово целиком» Отключает (не допускает изменение состояния) зависимый переключатель «Вверх - Вниз» Удаляет зависимый переключатель «Вверх - Вниз» Выбирает по умолчанию направление поиска «Вниз», иначе выбирается направление поиска «Вверх» Определяет состояние независимого переключателя «С учетом регистра». Когда элемент присутствует в множестве, переключатель находится в состоянии «включено» Определяет состояние независимого переключателя «Только слово целиком». Когда элемент присутствует в множестве, переключатель находится в состоянии «включено» Элемент включается в множество автоматически во время выполнения программы, когда пользователь первый раз нажимает на кнопку «Найти далее», и исключается из множества после закрытия диалога Элемент включается в множество автоматически во время выполнения программы, когда пользователь нажимает на кнопку «Заменить» Элемент включается в множество автоматически во время выполнения программы, когда пользователь нажимает на кнопку «Заменить все» Отображает кнопку помощи рядом с кнопкой закрытия окна
Таблица 17.4 Примечание
Используется только в диалоге поиска Используется только в диалоге поиска Используется только в диалоге поиска Используется и во время разработки, и во время работы программы Используется и во время разработки, и во время работы программы
Используется только в диалоге замены Используется только в диалоге замены
425
Часть IV. Компоненты и их использование
17.6.3. Анализ данных компонента FindDialog В момент вызова обработчика события OnFind в свойство FindText компонента заносится строка, которую пользователь ввел в соответствующем поле диалога: property F i n d T e x t :
String;
Остальные настройки диалога, необходимые для выполнения запроса пользователя, обработчик события OnFind может получить из множественного свойства Options, используя обычные методы работы с множественными переменными в Delphi. Например, чтобы определить, включен ли независимый переключатель «С учетом регистра», обработчик может воспользоваться следующей конструкцией: If f r M a t c h C a s e begin
in
FindDialogl.Options
Then
end;
17.6.4. Анализ данных компонента ReplaceDialog Анализ введенных пользователем данных при работе с диалогом замены немного более сложен. Обработчик события OnFind реализуется так же, как и в случае FindDialog, а для события OnReplace метод-обработчик должен проанализировать дополнительные данные, а именно: » тип необходимой операции, зависящий от нажатой пользователем кнопки в диалоге — «Заменить» или «Заменить все»; * строка, на которую следует заменить найденное вхождение подстроки, заданной свойством FindText. Строка, на которую следует заменить искомую подстроку, находится в свойстве ReplaceText, описанном в классе TReplaceDialog: property ReplaceText: String;
Для определения типа операции, которую следует выполнить, необходимо проверить состояние свойства Options на наличие в нем элементов frReplace или frReplaceAll. Например: If frReplace in ReplaceDialogl.Options Then begin {Действия, необходимые при нажатии кнопки «Заменить»} end; If frReplaceAll in ReplaceDialogl.Options Then begin {Действия, необходимые при нажатии кнопки «Заменить все»} end; 426
Глава 17. Использование диалоговых компонентов
17.6,5. Пример использования диалога замены Диалог замены ReplaceDialog является более сложным, чем.компонент FindDialog, и практически полностью включает в себя его возможности. Поэтому мы выбрали именно его для демонстрации работы с компонентами поиска и замены. Реализуем программу, на главной форме которой находится компонент Memo с названием Memol, и кнопка Button с названием Buttonl. Также добавим на эту форму диалоговый компонент замены ReplaceDialog с названием ReplaceDialogl (см. рис. 17.15).
Диалог замены Т ReplaceDialog является более сложным, чем ;омпонент TFindDialog, и практически полностью включает в себя его возможности. Поэтому мы выбрали именно его для демонстрации работы с компонентами поиска и замены.
Поиск и замена
Рис. 17.15. Форма на этапе разработки
Воспользовавшись Инспектором объектов изменим свойство Lines компонента Memol, добавив в него несколько строк произвольного текста. В диалоговом компоненте отключим зависимый переключатель «Слово целиком», установив флаг frHide Whole Word свойства Options в значение True. Реализуем обработчик события Onclick нажатия на кнопку Buttonl, вызвав диалог замены с помощью метода Execute. Так как данный диалог является немодальным, то в обработчике нажатия на кнопку никаких других действий выполняться не будет. Предусмотрим три метода замены подстроки: 1. Поиск заданной подстроки, вызываемый в обработчике OnFind компонента ReplaceDialogl; 2. Замены подстроки на заданную, вызываемый в обработчике OnReplace компонента ReplaceDialogl; 3. Замена всех заданных подстрок, вызываемый в обработчике OnReplace компонента ReplaceDialogl при установленном флаге frReplaceAll компонента ReplaceDialogl. Все три разновидности замены основываются на одном и том же механизме поиска подстроки и ее выделения, после чего выделенную подстроку можно заменить на другую, если пользователь выполняет не 427
Часть IV. Компоненты и их использование
поиск, а именно замену. Поэтому поиск и выделение подстроки удобно свести в одну функцию FindAndSelect, возвращающую логическое значение, соответствующее результату поиска. Для поиска вхождений подстроки в строку будем использовать функцию PosEx, описанную в модуле StrUtils следующим образом: function PosEx(const SubStr, S: string; Offset: Cardinal = 1) : Integer;
Очередной этап поиска будет начинаться от позиции текстового курсора, которая определяется свойствами SelStart (начало выделения) и SelLength (длина выделенного фрагмента) компонентами Memol. Модуль формы приложения приведен в листинге 17.3. Листинг 17.3- Использование диалога замены подстрок unit Unitl; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; Type TForml = class(TForm) Memol: TMemo; Buttonl: TButton; ReplaceDialogl: TReplaceDialog; procedure ReplaceDialoglFind(Sender: TObject); procedure ReplaceDialoglReplace(Sender: TObject); procedure ButtonlClick(Sender: TObject); private Function FindAndSelect(CaseSensitive: Boolean): Boolean; {В секции private описания класса формы содержится функция поиска и выделения в компоненте Memol подстроки, заданной пользователем} public end; var Forml: TForml; implementation {$R *.dfm} Uses StrUtils; {Подключение модуля StrUtils, в котором описана функция PosEx}
428
Глава 17. Использование диалоговых компонентов Function TForml.FindAndSelect(CaseSensitive: Boolean): Boolean; Var NewPos: Integer; Str: String; SubStr: String; Begin if CaseSensitive then begin SubStr := ReplaceDialogl.FindText; Str := Memol.Text; End else begin SubStr := AnsiLowerCase(ReplaceDialogl.FindText); Str := AnsiLowerCase(Memol.Text) ; end;
{Определение строки и подстроки для поиска, с использованием параметра CaseSensitive, указывающего, следует ли учитывать регистр символов. Если не следует, то строка и подстрока переводятся в нижний регистр} NewPos := PosEx(SubStr, Str, Memol.SelStart + Memol.SelLength + 1); {Поиск подстроки в строке} Result := False; if NewPos > 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? Печать в файл Копии
Печатать
Г Все
-••—"-•<•<
—
Число копий: П ^ 1
*•* Страницы с:|1
по:|Ш
<"* В ьиеяенный Фрагмент
Рис. 17.16. Диалог, выводимый компонентом PrintDialog
Свойства PrintDialog Свойства компонента PrintDialog отражают состояние элементов управления в диалоговом окне и введенную пользователем информацию. Рассмотрим эти свойства. Настройка поддержки печати с множеством копий обеспечивается свойствами Copies и Collate, определяющими соответственно количество копий и состояние зависимого переключателя «Разобрать»: property C o p i e s : I n t e g e r ; property Collate: Boolean;
ч
Следует отметить, что обычно нет необходимости в получении такой информации, так как возможность печати множества копий поддерживается печатающим устройством. Информацию о той части документа, которую следует, в соответствии с выбором пользователя, вывести на печать, можно получить из перечислимого свойства PrintRange: property PrintRange: TPrintRange; Значениями данного свойства могут являться константы: » prAHPages — печатать все страницы документа; * prSelection — печатать выделенную часть документа; * prPageNums — печатать только некоторый диапазон страниц.
432
Глава 17. Использование диалоговых компонентов
В случае, если в диалоге выбрано положение «Страницы с ... по ...», номера начальной и конечной страниц можно получить из свойств FromPage и ТоРаде: property FromPage: I n t e g e r ; property ToPage: I n t e g e r ;
Для того, чтобы ограничить пользователя в выборе диапазона страниц, следует использовать свойства MinPage и МахРаде. Эти свойства устанавливают соответственно минимально и максимально возможные страницы диапазона. При попытке пользователя ввести номер страницы, мень^ ший, чем минимально возможный, или больший, чем максимально возможный номер, при закрытии диалогового окна будет выдано предупреждение о некорректном вводе. Закрытие диалога возможно только при условии безошибочности всех данных, используемом в нем. property M i n P a g e : property MaxPage:
Integer; Integer;
И, наконец, свойство PrintToFile определяет состояние зависимого переключателя «Печать в файл»: property P r i n t T o F i l e :
Boolean;
Настройка функциональности За внешний вид диалога PrintDialog отвечает свойство Options множественного типа T P r i n t D i a l o g O p t i o n , возможные элементы которого приведены в табл. 17.5. Состав свойства Options диалогов текстового поиска и замены Элемент
Изменение функциональности диалога при включении элемента в множество
poDisablePrintToFile
Отключает (не дает изменить состояние) независимый переключатель «Печать в файл»
poPageNums
Разрешает использование положения зависимого переключателя «Страницы с ... по ...»
poSelection
Разрешает использование положения зависимого переключателя «Выделенный фрагмент»
poWarning
Выдает предупреждение при попытке выбора недоступного принтера
poPrintToFile
Отображает независимый переключатель «Печать в файл»
poHelp
Отображает кнопку помощи рядом с кнопкой закрытия окна
Таблица 17.5 Примечание
Может не работать в некоторых версиях Windows 95
433
Часть IV. Компоненты и их использование
17.7.2. Компонент PrinterSetupDialog Компонент PrinterSetupDialog предназначен для установки следующих основных параметров печати: • печатающее устройство, выбираемое из списка доступных системе, с возможностью его настройки с помощью специализированного диалога (при использовании кнопки «Свойства»); » размер бумаги из списка доступных форматов выбранного принтера, и тип подачи; » ориентация печати — книжная или альбомная.
Имя:
j \STT_FF\Xerax 4505
Состояние:
Готов
Тип:
Хнок4505
Место:
LPI1:
Комментарий: ~- Б дмага •
:
•-
—
Размер: JA4 Подача:
[Автовыбор
Рис. 1.7.17. Диалог, выводимый компонентом PrunterSetupDialog
В компоненте не определено собственных свойств и событий, а вся введенная пользователем информация используется напрямую печатающим устройством. Использование данного диалога вместо компонента TPrintSetup целесообразно при отсутствии необходимости выбора диапазона документа, выводимого на печать. Также в данном диалоге пользователь не может выбрать перенаправление печатаемой информации в файл. Таким образом, компонент PrinterSetupDialog является дополнительным по отношению к TPrinterDialog.
17.7.3. Компонент PageSetupDialog Компонент PageSetupDialog сочетает в себе некоторые элементы ранее рассмотренных компонентов, а именно: » выбор ориентации печати (книжная или альбомная); » выбор формата бумаги из списка поддерживаемых выбранным принтером, и типа ее подачи. ' 434
Глава 17. Использование диалоговых компонентов
Свойства PageSetupDialog Основным назначением диалога является предоставление пользователю возможности ввода отступов от краев страницы, для чего в диалоговом окне предусмотрены соответствующие поля ввода. После завершения работы диалога значения, введенные пользователем в эти поля, заносятся соответственно в свойства компонента MarginLeft, MarginTop, MarginRight И MarginBottom: property property property property
MarginLeft: Integer; MarginTop: I n t e g e r ; MarginRight: I n t e g e r ; MarginBottom: Integer;
В зависимости от единицы измерения, используемой в диалоге, значения свойств хранятся либо в тысячных долях дюйма, либо в сотых долях миллиметра. Для ограничения пользователя при вводе значений полей документа предусмотрены свойства компонента M i n M a r g i n L e f t , MinMarginlop, MinMarginRight И MinMarginBottoml property property property property
MinMarginLeft: Integer; MinMarginTop: I n t e g e r ; M i n M a r g i n R i g h t : Integer; MinMarginBottom: Integer;
Параметры страницы
- Ё умага —"~~ Размер: Подача:
— JA4 210x297 мм
.. 1Автоподатчик лис
- Ориентация •-
г Поля (мм} "™~™
(* Книжная
I левое:
)25
правое: |25
<"~ Альбомная i
I верхнее: |25
нижнее: J25
Отмена
|
Принтер...
Рис. 17.18. Диалог, выводимый компонентом PageSetupDialog 435
Часть IV. Компоненты и их использование
Единицы измерения, в которых пользователь указывает настройки документа, определяются свойством Units перечислимого типа TPageMeasureUnits: property U n i t s : TPageMeasureUnits; Значением этого свойства в каждый момент работы программы может быть одна из констант: • pmDe fault — использовать единицы измерения, установленные системой; » pmMillimeters — использовать для определения размеров миллиметры; * pminches — использовать для определения размеров дюймы. Размер страницы, соответствующий выбранному в диалоге формату, содержится в свойствах PageHeight и PageWidth: property PageHeight: Integer; property PageWidth: Integer; Следует отметить, что, в отличие от свойств ранее рассматриваемых диалогов, настройки полей страницы не влияют на печатаемый документ автоматически, а могут быть либо использованы программой, либо игнорироваться. Настройка функциональности PageSetupDialog Свойство Options диалогового компонента PageSetupDialog определяет набор элементов управления в диалоговом окне: property Options: TPageSetupDialogOptions; Элементы, которые могут быть включены в множество типа TPageSetupDialogOptions, перечислены в табл. 17.6. \
События PageSetupDialog В верхней части диалога PageSetupDialog расположен макет страницы, выводимой на печать. Для удобства пользователя на макете отображается расположение страницы, ее размеры, поля и пример изображения. Диалог реализован таким образом, что некоторые из атрибутов макета могут быть выведены программно, а не автоматически, как это происходит по умолчанию. Для гибкого управления видом макета в компоненте PageSetupDialog предусмотрены соответствующие события. Первым событием такого рода является BeforePaint: property BeforePaint: '
436
TPageSetupBeforePaintEvent;
Глава 17. Использование диалоговых компонентов Состав свойства Options диалога PageSetupDialog Элемент
Таблица 17.6
Изменение функциональности диалога при включении элемента в множество
psoDefaultMinMargins
Устанавливает минимально возможные поля страницы
psoDisableMargins
Запрещает изменения полей страницы
psoDisableOrientation
Запрещает изменения ориентации печати
psoDisablePagePainting
Запрещает прорисовку образца страницы в верхней части диалога
psoDisablePaper
Отключает группу элементов управления, предназначенных для выбора размера бумаги и типа ее подачи
psoDisablePrinter
Скрывает кнопку выбора принтера
psoMargins
Устанавливает начальные значения полей страницы в значения, заданные свойствами MarginLeft, MarginRight, MarginBottom, и MarginTop. Иначе все начальные значения устанавливаются в один дюйм
psoMinMargins
Использует значения свойств MinMarginLeft, MinMarginRight, MinMarginBottom, и MinMarginTop для контроля информации, вводимой пользователем. Иначе пользователь имеет возможность вводить значения полей, меньшие, чем заданы данными свойствами
psoWarning
Запрещает вывод предупреждения об отсутствии принтера, выбранного по умолчанию
psoNoNetworkButton
Скрывает и отключает кнопку «Сеть» в дополнительном диалоге, вызываемом при нажатии кнопки «Принтер»
psoShowHelp
Отображает кнопку помощи рядом с кнопкой закрытия окна
Обработчик события B e f o r e P a i n t представляет собой процедуру (метод формы, на которой расположен диалог) следующего вида: Procedure <Название Обработчика>(3епс1ег: T O b j e c t ; PaperSize: Smalllnt; Orientation: TPrinterOrientation; PageType: T P a g e T y p e ; var D o n e D r a w i n g : B o o l e a n ) ;
Параметры, передаваемые в обработчик, позволяют подготовить внутренние данные программы, необходимые для вывода макета изображения, и содержат информацию о формате страницы (PaperSize), ориентации печати (Orientation), типе носителя (PageType). Параметр DoneDrawing предназначен для изменения обработчиком и указывает на завершенность изображения. Если обработчик события BeforePaint устанавливает значение этого параметра в True, остальные события компонента не вызываются. •* Остальные события компонента PageSetupDialog предназначены для вывода конкретных атрибутов страницы и имеют следующий заголовок: Procedure <Название Обработчика>{Sender: T O b j e c t ; Canvas: TCanvas; PageRect: TRect; var D o n e D r a w i n g : B o o l e a n ) ; 437
Часть IV. Компоненты и их использование
Обработчикам передается ссылка на объект типа TCanvas, на который следует вывести соответствующий атрибут макета страницы. Параметр PageRect определяет прямоугольник, в который атрибут должен быть выведен. Любой обработчик может прервать процесс прорисовки макета с помощью установки параметру DoneDrawing значения True. Обработчики вызываются в следующем порядке: 1. 2. 3. 4. 5. 6. 7. 8. 9.
OnShow OnBeforePaint OnDrawFullPage OnDrawMinMargin ...
после отображения диалога на экране. перед началом вывода макета. для заполнения фона макета. для отображения минимально возможных полей документа. OnDrawMargin для отображения установленных полей документа. OnDrawGreekText... для отображения примера текста. OnDrawEnvStamp для отображения «почтовой марки», если используется тип бумаги envelop (конверт). OnDrawRetAddress . для отображения «обратного адреса», если используется тип бумаги envelop (конверт). OnClose после закрытия диалогового окна.
Механизм программной прорисовки основан на проверке существования обработчика конкретного события. Если обработчик существует, то он будет вызван, если же обработчик отсутствует, то соответствующий атрибут будет прорисован автоматически. Таким образом, прикладная программа может прорисовать только некоторые атрибуты макета (обычно выводится только собственный пример текста), тогда как остальные будут выведены по умолчанию.
Вопросы с ответами для повторения по главе 17 Поясните понятие диалогового компонента Ответ: Диалоговые компоненты — это невизуальные компоненты, которые могут во время выполнения программы отображать на экране диалоговые окна для получения от пользователя какой-либо информации. Все диалоговые компоненты наследуются от класса TCommonDialog, в котором описан метод Execute, предназначенный для вывода диалогового окна на экран. Метод реализован в виде функции, возвращающей логическое значение, используемое некоторыми модальными диалогами для индикации необходимости использования информации по результатам работы пользователя. Например, метод Execute файлового диалога OpenDialog возвращает значение True, если пользователь выбрал какой-либо файл и нажал на кнопку Ok. 438
Глава 17. Использование диалоговых компонентов Поясните разницу между модальными и немодальными диалогами Ответ: С точки зрения пользователя, модальные диалоги запрещают переход к любому окну программы, из которой они вызваны, а немодальные диалоги работают параллельно с программой.. С точки зрения разработчика, модальный диалог приостанавливает приложение, которое вызвало этот диалог с помощью метода Execute, до закрытия диалогового окна. Поэтому фрагмент программы, использующий модальный диалоговый компонент, обычно локализован в одной подпрограмме и выглядит в виде последовательности действий: » » * •
отображение диалогового окна с помощью метода Execute; анализ значения функции Execute; , анализ свойств диалогового компонента; действия, необходимые для реакции на информацию, введенную пользователем.
При вызове метода Execute немодального диалога, выполнение программы продолжается, поэтому реакция на действия пользователя основывается, в основном, на обработке событий, предусмотренных данным диалоговым компонентом.
Назовите основные методы и события, описанные в базовом диалоговом компоненте Ответ: Базовым диалоговым компонентом является класс TCommonDialog, в котором описан метод Execute, отображающий диалоговое окно, и два события: » OnShow, возникающее при выводе диалогового окна на экран; * Ondose, возникающее при закрытии диалога Все диалоговые компоненты поддерживают эти события.
Перечислите виды стандартных диалоговых компонентов Ответ: В стандартной поставки Delphi 7 предусмотрены следующие диалоговые компоненты: » Модальные диалоги для работы с файлами (открытие или сохранения). В данную группу входят диалоги открытия и сохранения файлов OpenDialog и SaveDialog, а также диалоги открытия и сохранения графических файлов OpenPictureDialog и SavePictureDialog. » Модальные диалоги выбора шрифта и цвета FontDialog и ColorDialog. » Модальные диалоги настройки параметров печати PrintDialog, PrinterSetupDialog и PageSetupDialog. » Немодальные диалоги текстового поиска и замены FindDialog и ReplaceDialog. 439
Часть IV. Компоненты и их использование
Расскажите о методах настройки функциональности диалоговых окон Ответ: Во всех стандартных диалоговых компонентах предусмотрено множественное свойство Options,, тип которого отличается для разных компонентов, поэтому данное свойство описано в каждом конкретном компоненте, а не в базовом классе TCommonDialog. Свойства Options представляют собой множества каких-либо элементов, каждое из которых определяет одну настройку соответствующего диалогового окна. С помощью таких свойств можно: 1. Управлять наборами элементов управления, размещенными на диалоговых окнах. 2. Определять состояние (введенную информацию) некоторых элементов управления. 3. Включать или отключать контроль за действиями пользователя с целью автоматической корректировки диалогом вводимых данных. Дополнительные возможности настройки функциональности предусмотрены в компоненте PageSetupDialog, позволяющем приложению прорисовывать макет страницы, расположенный на нем, с помощью последовательного вызова обработчиков соответствующих событий.
440
Формы
18.1. Понятие формы. Форма как часть проекта 18.1.1. Форма как Delphi-компонент Форма — это заготовка для будущего окна приложения и, в то же время, контейнер для всех видов компонентов (визуальных, невизуальных и диалоговых), добавляемых в программу во время визуального построения. Сочетание таких возможностей вытекает из принадлежности формы к классу T W i n C o n t r o l , что позволяет форме быть оболочкой вокруг оконного компонента Windows, и соответственно, к классу TComponent, от которого наследуется T W i n C o n t r o l , благодаря чему форма может быть владельцем компонентов. Форме в Delphi соответствует класс TForm (см. рис. 18.1). TOhject
TComponent
TWinControl \
TForm Рис. 18.1. Положение класса TForm в иерархии классов Delphi
441
Часть IV. Компоненты и их использование
Добавим, что форма является не просто оболочкой элемента управления, а еще и контейнером, в котором могут располагаться другие элементы управления' Так как окно — это основа Windows-программы, то форма выступает в программе как самый крупный интерфейсный элемент. Программа начинает свою работу с создания главного окна и заканчивается, когда это окно разрушается. Таким образом, экземпляр класса формы доступен в любой момент времени выполнения программы, что делает его крайне удобным для размещения программного кода. Так и происходит на самом деле. Мы уже сталкивались с тем, что обработчики событий компонентов, расположенных на некоторой форме, формируются визуальным построителем в виде методов класса этой формы, а ссылки на эти компоненты доступны в качестве свойств класса формы. Из такого подхода к реализации функциональности приложения вытекает важное отличие использования формы от использования других компонентов Delphi: форма присутствует в программе не как экземпляр класса TForm, а как экземпляр наследника этого класса.
18.1.2. Представление формы в проекте При добавлении формы в проект автоматически создается модуль формы, в котором описывается наследник класса TForm, и файл описания формы. Модуль формы используется непосредственно для обращения к форме из программы, а описание формы — автоматически для инициализации компонентов, добавленных на форму в процессе визуальной разработки. Модуль формы, на которой нет компонентов, приведен в листинге 18.1. Листинг 18.1. Модуль формы, на которой нет компонентов unit U n i t l ; {Заголовок модуля} interface {Начало интерфейсной секции} uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs; {Подключение необходимых модулей 6 2 } type {Раздел описания типов интерфейсной секции} TForml = class(TForm) private Заметим, что действительно необходимым модулем из всего представленного списка является только модуль Forms, в котором описан класс TForm. Этот факт можно проверить, удалив ссылки на все остальные модули. По мере добавления компонентов на форму список будет автоматически расширяться в соответствии с необходимостью конкретных компонентов. Однако избыточность модулей не влияет на качество приложения, так как во время компиляции неиспользуемые модули не пристыковываются к программе. 442
Глава 18. Формы { Private declarations } public { Public declarations } end; {Описание класса TForml как наследника класса стандартной формы TForm. По умолчанию не добавляется никакой дополнительной функциональности} var {Раздел описания переменных интерфейсной секции} Forml: TForml; {Описание ссылки на экземпляр класса TForml, которая будет инициализирована автоматически} Implementation {Начало описательной секции} {$R *.dfm} {Подключение файла с ресурсами, необходимыми для работы формы} end. {Окончание модуля} Файл с ресурсами *.dfm, подключаемый к модулю формы с помощью директивы $R в первой строке описательной части модуля, содержит описание формы — значения ее свойств, а также структуры компонентов и их свойства. Этот файл формируется визуальным построителем в процессе разработки программы и имеет следующий вид63: object Forml: TForml Left = 194 Top = 103 Width = 783 Height = 540 Caption = 'Forml' Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = Л МЗ Sans Serif Font.Style = [] OldCreateOrder = False PixelsPerlnch = 96 TextHeight = 13 end
Приведенный пример соответствует описанию формы Delphi, начиная с шестой версии. В более ранних версиях описание формы хранилось в двоичном формате. 443
Часть IV. Компоненты и их использование
Если на форму добавляется какой-либо компонент, то ссылка на него помещается в класс формы. Таким образом, модуль формы автоматически изменяется для обеспечения доступа программы к новому компоненту. Например, если на форму добавить компонент Button, то модуль формы будет выглядеть так, как показано в листинге 18.2. Листинг 18.2. Модуль с добавленным на форму компонентом Button unit Unitl; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, D i a l o g s ; type TForml = class(TForm) Buttonl: TButton; {В класс формы добавлено свойство — ссылка на компонент, размещенный на форме с помощью визуального построителя} private { Private declarations } public { Public declarations } end; var Forml: TForml; Implementation {$R *.dfm} end.
Описание формы также изменится и будет включать в себя указание компонента, размещенного на форме, и его свойства: o b j e c t F o r m l : TForml L e f t = 194 Top = 103 W i d t h = 783 Height = 5 4 0 Caption = x F o r m l ' Color = clBtnFace F o n t . C h a r s e t = DEFAULT_CHARSET Font.Color = clWindowText F o n t . H e i g h t = -11 F o n t . N a m e = A MS Sans Serif F o n t . S t y l e = [] OldCreateOrder = False 444
Глава 18. Формы P i x e l s P e r l n c h = 96 T e x t H e i g h t = 13 object Buttonl: TButton L e f t = 80 Top = 120 W i d t h = 75 Height = 25 Caption = ' B u t t o n l ' TabOrder = 0 end {Описание компонента Buttonl, end
расположенного
на форме}
18.2. Использование форм 18.2.1. Жизненный цикл формы Жизненный цикл формы аналогичен жизненному циклу любого экземпляра класса в Delphi и включает в себя создание, обращение к свойствам и (или) методам, в частности к методам отображения формы на экране, и последующее разрушение. Однако в связи с тем, что формы более сложны, чем обычные компоненты, некоторые из этих действий выполняются особым образом. При этом часть механизма обеспечения жизнедеятельности формы может быть выполнена автоматически. Добавление формы в проект При создании нового проекта, приложение имеет одну форму, которая будет отображена на экране после его запуска. Эта форма является главным окном программы. В дальнейшем формы могут создаваться с помощью пункта главного меню File->New->Form среды разработчика Delphi. Классы форм, добавляемых в проект, получают названия, состоящие из слова Form и порядкового номера формы в проекте. После создания очередной формы ее название можно изменить с помощью свойства Name через Инспектор объектов. При этом файл формы будет соответствующим образом изменен. • Организация структуры форм в приложении Структурой форм в приложении и вариантами их создания управляет диалог Project Options (см. рис. 18.2), вызываемый с помощью пункта главного меню Project-XJptions. На закладке Forms этого диалога представлен список форм, добавленных в проект, с указанием формы, которая является главной, то есть выводимой на экран при запуске программы. 445
Часть IV. Компоненты и их использование Project Options for Project 1.еке Directories/Conditionals Forms
j
Main form:
Application
j
j Compiler
' Version Info ]
j
Compiler Messages
|
Linker
3
JMainForm
Auto-create forms:
Packages
Available forms:
MainForm Form2 Form3
Cancel
Help
Рис. 18.2. Диалог управления формами приложения
В левом нижнем списке (Аиto-create forms — Автоматически создаваемые формы) представлены формы, которые создаются при старте приложения. Заметим, что создание формы не означает ее автоматическое появление на экране, так как этим свойством обладает только главная форма. В качестве главной формы может быть выбрана одна из форм, представленных в списке Auto-create forms. Для этого предназначен элемент управления (выпадающий список) Main Form (главная форма). Если после назначения формы главной она будет перемещена из списка Auto-create forms (например, с помощью кнопки '>'), то вместо нее будет назначена главной другая форма, присутствующая в этом списке. Если из списка удалить все формы, то работа приложения будет невозможна, так как главное окно программы должно быть определено обязательно. Автоматическое создание формы во время выполнения программы Рассмотрим, как происходит создание форм при запуске приложения. Допустим, имеется новый проект, в котором по умолчанию присутствует одна форма. Файл проекта, который является в Delphi основной частью программы, будет выглядеть как в листинге 18.3 (текст данного файла доступен с помощью пункта главного меню Project->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
Сохранение и печать. Формат; *
Загрузить
-
Первая.етрока
<"" Таяькота;ет ('(>
Сохранить
Печатать
Рис. 18.8. Полупрозрачное окно текстового редактора на фоне Рабочего стола Windows
учитывает данную возможность с помощью свойств A l p h a B l e n d и AlphaBlendValue: property A l p h a B l e n d : B o o l e a n ; property A l p h a B l e n d V a l u e : Byte;
/
Свойство AlphaBlend указывает, следует ли (значение True) выводить форму в полупрозрачном режиме, а свойство AlphaBlendValue задает степень ее непрозрачности целочисленным значением от 0 (полностью прозрачна) до 255 (полностью непрозрачна). 472
\
Глава 18. Формы
Атрибуты прозрачности устанавливаются не только для окна, но и для всех элементов управления, которые в нем расположены. Причем при значениях непрозрачности, больших нуля, сохраняется возможность взаимодействия с окном и визуальными компонентами в полном объеме, даже когда окна не видно на экране (например, когда свойство AlphaBlendValue имеет значение 1). Полупрозрачные окна могут быть применены для организации панелей инструментов, сквозь которые видно редактируемые документы. Также предусмотрена возможность полной прозрачности точек формы, имеющих один цвет, задаваемый свойством TransparentColorValue: property T r a n s p a r e n t C o l o r V a l u e :
TColor;
Все точки формы, которые имеют заданный этим свойством цвет, могут быть полностью прозрачны, причем не только визуально, но также и для событий, связанных с манипуляциями мышью. Для включения такого поведения окна предназначено свойство TransparentColor: property T r a n s p a r e n t C o l o r :
Boolean;
На рис. 18.9 показано окно, в котором находится визуальный компонент Shape, отображающий эллипс белого цвета. Белый цвет также установлен в качестве значения свойства TransparentColorValue формы, а свойству TransparentColor присвоено значение True. При выводе формы над приложением Microsoft Word пользователь имеет возможность видеть часть окна редактора сквозь область компонента, имеющую белый цвет. Также при щелчке мышью в эту область, активным станет приложение MS Word. Заметим, что подпись формы «Forml», выведенная белым цветом, также является прозрачной. Возможность использования «прозрачного цвета» для формы позволяет легко создавать окна приложений непрямоугольной формы. Для этого •ачные окна могут быть использованы для организации ;к
те Тг
ф-
зу,-; о: тг
'которые имеют заданный этим свойство» /о, но, также, и для событий, связанных рредназначено свойство TransparentcgTor,: rv: Boolean; о, в котором находится визуальный ко
Рис. 18.9. Окно с прозрачной областью 473
Часть IV. Компоненты и их использование
следует присвоить одинаковые цвета форме (с помощью свойства Color) и свойству TransparentColorValue, расположив при этом на форме изображение, в котором не используется такой цвет. В результате область, занятая изображением, будет выводиться на экран, а остальная часть окна будет скрыта. При изменениях в изображении, происходящих во время выполнения программы, соответствующим образом будет изменяться форма окна. И, наконец, отметим, что полупрозрачность формы и полная прозрачность ее цвета не исключают друг друга, что позволяет реализовывать различные графические эффекты, например, плавную потерю видимости окна, имеющего непрямоугольную форму.
18.5. Особенности визуального проектирования форм 18.5.1. Расположение компонентов на форме В настоящем разделе мы обсудим некоторые аспекты визуального проектирования форм, предназначенных для работы в условиях размеров, изменяющихся по желанию пользователя. Такие формы наиболее часто используются в приложениях различного рода, поэтому данный вопрос заслуживает внимания. Организация контейнеров переменного размера. Компонент Splitter Автоматическое изменение положения и размеров элементов управления формы было рассмотрено ранее. Напомним, что такое поведение визуальных компонентов обеспечивается свойствами Align и Anchors, описанными в классе TControl. He все визуальные компоненты имеют доступ к данным свойствам, однако все компоненты-контейнеры, а именно их мы и собираемся рассматривать, могут использовать и свойство Align и свойство Anchors. Размеры контейнеров, расположенных на форме, могут изменяться в соответствии со свойствами A l i g n и Anchors пропорционально изменению размеров формы. Однако такое поведение не всегда оправдано, так как для пользователя может быть более приоритетно увеличение размеров одного контейнера в ущерб другим. Для добавления в программу такой возможности предусмотрен стандартный компонент Splitter (англ. Splitter — делитель), расположенный на закладке Additional Палитры компонентов. Компонент Splitter яв474
Глава 18. Формы
ляется легковесным элементом управления и представляет собой прямоугольную область, залитую сплошным цветом, который задается свойством Color: property Color: TColor; Splitter предназначен для разделения клиентской области окна на две части, представленной двумя компонентами, и работает с компонентами, использующими выравнивание вдоль края своего контейнера с помощью свойства Align. Splitter должен иметь такое же выравнивание, как у одного из компонентов, в результате чего он располагается вдоль его границы, -противоположной, той, по которой он выровнен сам. Например, на рис. 18.10 панель P a n e l l имеет выравнивание по верхнему краю и компонент Splitter 1 имеет аналогичное выравнивание. Во время работы программы пользователь имеет возможность перемещения компонента Splitter в направлении, перпендикулярном стороне, по которой он выровнен. Для расположения, показанного на рис., таким направлением будет вертикальное, то есть делитель можно перемещать вверх и вниз. Одновременно с перемещением компонента Splitter будет изменяться высота панели Panell. Если с другой стороны компонента Splitter будет присутствовать какой-либо компонент, имеющий аналогичное выравнивание, либо выравнивание по клиентской области aiciient, то его размеры будут изменяться соответственно перемещению делителя, так как это обусловлено правилами выравнивания компонентов. На рис. 18.11 панель Panell и компонент Splitter имеют выравнивание alTop, а панель Рапе12 — выравнивание aiciient. При перемещении делителя вниз размер верхней панели увеличивается, а нижней, соответственно, уменьшается. При последующих изменениях размеров формы будут изменяться размеры нижней панели, так как именно она выравнивается по клиентской области окна, а выравнивание Panell подразумевает неизменность высоты.
Рис. 18.10. Использование компонента Splitter 475
Часть IV. Компоненты и их использование
Panetl
Рис. 18.11. Изменение размеров панелей с помощью компонента Splitter
Дополнительные свойства компонента Splitter определяют методы изменения размеров зависимых компонентов. Свойство ResizeStyle определяет, в какой момент должно происходить обновление размеров контейнеров: property R e s i z e S t y l e : TResizeStyle; Данное свойство может иметь следующие значения:
l.rsNone — запрещает перемещение делителя. 2. rsLine и rsPattern — разрешают перемещение делителя. Размеры компонентов обновляются после отпускания пользователем кнопки мыши. Разница между этими двумя значениями состоит во внешнем виде делителя во время его перемещения. 3. rsUpdate — разрешает перемещение делителя. Размеры компонентов обновляются немедленно в процессе перемещения. Также предусмотрена возможность скрытия одного из зависимых компонентов (первого), если его размеры не превышают значения, задаваемого целочисленным свойством MinSize: property M i n S i z e :
1..High(Integer);
Необходимость такого поведения определяется логическим свойством Auto Snap: property A u t o S n a p :
Boolean;
Скрытие одного из взаимозависимых компонентов можно применять для форм, организованных по принципу «Список — подробности», для поочередного скрытия либо списка, представленного обычно в табличной форме, либо детальной информации, представленной набором параметров, в зависимости от того, какая часть окна более необходима пользователю в данный момент. 476
Глава 18. Формы
Открепление элементов управления Механизм открепления элементов управления позволяет автоматически создать и н с т р у м е н т а л ь н о е окно (окно, имеющее стиль р а м к и b s S i z e T o o l W i n ) , перемещаемое по экрану, и содержащее элемент управления, который во время визуальной разработки приложения находился на форме. Открепление является частью технологии Drag-And-Drop (от англ. Drag And Drop — Перетащить и бросить) и имеет название DragAnd-Dock (от англ. Drag And Dock — Перетащить и прикрепить). Все действия, необходимые для поддержки открепления, поддерживаются компонентами автоматически. Включение данной возможности производится с помощью установки значения dmAutomatic свойству DragMode (от англ. Drag Mode — Режим перетаскивания): property DragMode: TDragMode; Второе возможное значение данного свойства — dmManual (установлено по умолчанию) — запрещает автоматическое открепление компонента от своего контейнера. По умолчанию компоненты находятся не в режиме Drag-And-Dock, а в режиме Drag-And-Drop, что определяется свойством DragKind: property DragKind: TDragKind; Данное свойство может иметь одно из двух значений: l.dkDrag — перетаскиваемый компонент участвует в операции DragAnd-Drop (установлено по умолчанию). 2.dkDock — перетаскиваемый компонент участвует в операции DragAnd-Dock. Итак, для того, чтобы пользователь мог открепить элемент управления от его контейнера, следует установить свойству DragMode значение dmAutomatic, а свойству DragKind — значение dkDock. После открепления компонента для него создается отдельное окно, которое всегда находится выше главного окна программы и его дочерних окон. При этом инструментальные окна могут покидать пределы главного окна программы, даже если интерфейс приложения организован в MDI-стиле. Пользователь имеет возможность вставить открепленный компонент обратно в контейнер, либо в какой-либо другой контейнер этого же приложения. Контейнер может принять открепленный компонент, если для его свойства DockSite (от англ. Dock Site — Место для прикрепления) установлено значение True: property D o c k S i t e :
Boolean;
В качестве примера открепления элементов управления модифицируем приложение — текстовый редактор, разработанное при обсуждении ви477
Часть IV. Компоненты и их использование
зуальных компонентов. Назначим подпанелям, входящим в панель управления, характеристики, позволяющие откреплять их от родительской панели (DragMode = dmAutomatic, DragKind = dkDock), а самой панели инструментов установим значение True для свойства DockSite, чтобы открепленные элементы управления можно было прикрепить обратно. Результат работы пользователя с модифицированной программой показан на рис. 18.12. Пользователь открепил панели управления характеристиками шрифта и отмены, и переместил их в другое место. При этом панель управления шрифтом частично покинула пределы родительского окна. Панель инструментов уменьшилась в соответствии с текущим содержанием.
;Шх1 | Отступ слева: JO
Справа: |0
-;•} Первая строка: )0
jj j
1.2.3...
основе компонента
Отменить последние изменения Г
. Размер шрифта: ГШ
т]
В
ифта (полужирный, наклонный, та. Возможность
/
Ц
S
Цвет; )• Black
краю, а также по центру; Сохранение и печать..."' "—'—" Формат: (• Rich Text Fofmat (".itf) f~ Только текст ("b(tj
-Zl
Файл: |C:\TMP\File.rtf Загрузить
Рис.
Сохранить
18.12. Открепление подпанелей панели инструментов
При попытке размещения какой-либо из открепленных панелей над панелью инструментов размещаемая панель будет автоматически к ней прикреплена. Для контроля за открепленными компонентами предусмотрено несколько событий, которые могут быть обработаны обычным образом. Их рассмотрение не имеет смысла в связи с узкой применяемостью.
18.5.2. Использование фреймов На разных формах одного приложения могут использоваться одинаковые группы элементов управления. Для копирования такой группы можно воспользоваться буфером обмена с помощью команд главного меню среды разработчика Edit->Copy и Edit->Paste. Однако при дальнейших изменениях группы компонентов данную операцию придется повторить снова. Тем более, что при копировании информации таким образом не исключены ошибки вследствие неправильного выбора копируемых компонентов. 478
Глава 18. Формы
Delphi имеет возможность автоматизации использования одинаковых групп компонентов для решения как задачи копирования, так и возможности совместного изменения нескольких копий группы компонентов. Для этого предназначены специальные структуры времени построения и выполнения программы — фреймы (англ. Frame — фрейм — охватывающий, система). .
Понятие фрейма Фрейм является визуальным компонентом и наследуется от класса TWinControl (см. рис. 18.13), так же, как и формы, что обеспечивает основы его поведения. Фрейм реализован классом TFrame. Фрейм представляет собой контейнер, аналогичный в процессе разработки форме, однако его экземпляр не может быть создан в программе вне контекста формы или какого-либо другого контейнера. Для фрейма-, так же, как и для формы создается модуль и файл описания. Так же, как и форма, фрейм, реализованный классом TFrame, не имеет смысла, а в процессе визуальной разработки формируется новый класс, который является его наследником: TFrameS = Buttonl: Button2: Buttons: private
class(TFrame) TButton; TButton; TButton;
// Новый класс — наследник TFrame
{ Private declarations } public { P u b l i c declarations } end;
Фрейм является частью проекта Delphi и может быть размещен на любой форме этого проекта. TObject
Т TCotnponent
TWinControl
TFrame Рис. 18.13. Положение класса TFrame в иерархии компонентов Delphi
479
)
Часть IV. Компоненты и их использование
Создание фрейма Для создания фрейма следует выбрать пункт главного меню среды разработчика File->New->Frame, после чего на экране появится форма, на которую можно добавлять элементы управления, настраивать их характеристики с помощью инспектора объектов, а также реализовывать обработчики .событий добавленных на фрейм компонентов. При этом доступны все возможности, аналогичные используемым при работе с формами. Ссылки на компоненты, добавленные к фрейму, заносятся в описание класса фрейма (см. пример выше), а обработчики событий реализуются в виде методов фрейма. •
Добавление фрейма на форму После создания фрейма его можно расположить в любом контейнере, например, на форме, или в панели, принадлежащей какой-либо форме. Сделать это можно, выбрав на закладке standard Палитры компонентов иконку jn|J, и расположив новый экземпляр фрейма в нужном месте, как обычный" компонент. При этом на экран будет выведен диалог со списком фреймов, доступных проекту, в котором следует выбрать тот фрейм, экземпляр которого необходимо добавить на форму. Ссылка на фрейм будет добавлена в описательную часть класса формы, и с помощью нее можно получить доступ к компонентам фрейма. procedure
TForml.ButtonlGlick(Sender: T O b j e c t ) ; {Обработчик нажатия на кнопку B u t t o n l
формы} begin Frame21.Label1.Caption := 'Нажата кнопка!'; {Обращение к свойству Caption метки Labell фрейма Frame21 (экземпляра фрейма Frarne2) } end;
Связи экземпляров фреймов Экземпляр фрейма имеет связь со своим классом по свойствам и событиям компонентов, расположенных на фрейме. Изменения в классе фрейма немедленно отражаются на всех его экземплярах, если соответствующие свойства не были изменены в экземпляре фрейма после его добавления на форму. Это правило касается каждого свойства в отдельности. Допустим, в проекте создан фрейм, содержащий панель с некоторыми координатами и каким-либо заголовком. При создании экземпляра фрейма на форме данный экземпляр будет реагировать на любые изменения в его классе. Если 480
Глава 18. Формы
изменить положение панели в описании фрейма, то положение изменится и в его экземпляре, расположенном на форме. Если теперь изменить положение панели в экземпляре фрейма, то изменения координат панели в классе фрейма больше не будут влиять на состояние экземпляра. Однако изменение заголовка панели в описании фрейма вызовет соответствующее изменение заголовка в его экземпляре. Механизм обработки событий также подчиняется приведенному правилу. Обработчик события, созданный в экземпляре фрейма подменяет обработчик самого фрейма, если такой существует. При созданиЬ обработчика события Delphi автоматически формирует вызов обработчика, реализованного в классе фрейма, например: procedure T F o r m l . F r a m e S l B u t t o n l C l i c k ( S e n d e r : begin Frame31.ButtonlClick(Sender); end;
TObject);
Как видно из обработчика события, данный обработчик является методом формы Forml, вызывается при нажатии (click) кнопки Buttonl, принадлежащей экземпляру ГгатеЗГ фрейма Frames, и внутри обработчика вызывается обработчик, реализованный во фрейме. Обработчику фрейма пересылается источник возникшего сообщения.
18.5.3. Депозитарий форм Фреймы удобны для использования логически законченной группы элементов управления внутри одного проекта, тогда как иногда бывает необходимо использовать целую форму в нескольких разных проектах. Если использование нескольких одинаковых форм в одном проекте можно реализовать с помощью их динамического создания, то разделение форм между проектами крайне неудобно. Для автоматизации использования нескольких копий формы одного класса в разных проектах в Delphi предусмотрен депозитарий форм. Добавление формы в депозитарий Для добавления готовой формы в' депозитарий следует воспользоваться пунктом главного меню среды разработчика Proj ect->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
! integrated debugging
Рис. 20.3. Диалог отключения отладки исключительных ситуаций
515
Часть V. Взаимодействие приложения с операционной системой сообщения, после чего приостановка работы программы из-за возникновения исключений, представленных в списке, производиться не будет. Взаимодействие с сервером на уровне документа Для создания нового документа следует добавить элемент в семейство Documents объекта «Word.Application» с помощью функции Add, которая вернет ссылку на созданный документ: Add(Template: String, NewTemplate: Boolean): Document; Строковый параметр Template определяет, на основе какого шаблона должен быть создан новый документ, а параметр NewTemplate указывает на то, что создаваемый документ сам должен являться шаблоном. Если метод вызывается без параметров, то новый документ создается на основе шаблона Normal («обычный») и является обычным документом. Ссылку на вновь созданный документ, возвращаемую методом Add, следует сохранить в переменной типа Variant для дальнейшего доступа к документу с целью вывода информации в него:
Var WordDoc: Variant; WordDoc := WordApp.Documents.Add; При необходимости вывода информации в сложные формы новый документ можно создать на основе некоторого «шаблона» — ранее созданного и сохраненного документа. Параметры могут передаваться методу Add в обычной форме:
Var WordDoc: Variant; WordDoc := WordApp,. Documents .Add ('Template . doc' ) ; Также можно использовать название параметра в строке вызова, что обычно более удобно при наличии в вызываемых методах параметров по умолчанию: Var
WordDoc: Variant;
516
___
Глава 20. Вывод информации за пределы программы. Технология СОМ
WordDoc := WordApp,Documents.Add(Template := 'Template.doc'); Закрыть документ после окончания вывода информации в него можно с помощью метода close: WordDoc.Close; WordDoc := UnAssigned;
Непосредственный вывод информации. Объект Selection Вывод информации в Microsoft Word аналогичен работе пользователя в редакторе и некоторым образом эмулирует ее. Так, с помощью объекта Selection, отражающего текущее выделение в документе, поддерживаются команды ввода текста и настройки его параметров. Если явного выделения не присутствует, то объект Selection отражает местонахождение текстового курсора. Заметим, что объект Selection принадлежит объекту «Word.Application», а не объекту Document. Для вывода информации в объект Selection используется его метод TypeText: WordApp.Selection.TypeText(Text:
String);
Для ввода символа перевода строки можно воспользоваться методом TypeParagraph объекта Selection: WordApp.Selection.TypeParagraph; При последовательном выводе информации изменение автоматически установленного выделения обычно не требуется, однако, если возникнет такая необходимость, можно установить параметры выделения с помощью методов Move, MoveRight (сместить выделение вправо) и MoveLeft (сместить выделение влево): M o v e ( U n i t : Integer, C o u n t : Integer); MoveRight(Unit: Integer, -Count: Integer, Extend: Boolean = False); MoveLeft(Unit: Integer, Count: Integer, Extend: Boolean = False); Методы сдвигают выделение на заданное параметром Count количество единиц. Значение параметра может быть положительным, либо отрицательным. В случае метода Move знак параметра Count определяет направление смещения выделения. Отрицательное значение параметра указывает на смещение влево, а положительное — вправо. Методы MoveRight и MoveLeft изначально настроены на смещение выделения в заданную сторону (вправо и влево соответственно), поэтому . 517
Часть V. Взаимодействие приложения с операционной системой
отрицательное значение параметра Count в их вызове просто меняет направление смещения выделения. При использовании методов Move,. MoveLeft и MoveRight, выделение, если оно существовало до их вызова, снимается. Этого можно избежать в функциях MoveLeft и MoveRight, если в качестве значения необязательного параметра Extend задать значение True (по умолчанию устанавливается False). Параметр Unit определяет единицу смещения выделения и может принимать значения, указанные в табл. 20.1 (в таблице представлены не все возможные значения). ;:< ' '. • 'Значения параметра Unit
Таблица 20.1 Единица сдвига
Значение
1
Один символ
2
Одно слово
3
Одно предложение
4
Один абзац
5
Одна строка
9
Один столбец таблицы, если выделение находится в таблице
10
Одна строка таблицы, если выделение находится в таблице
12
Одна ячейка таблицы, если выделение находится в таблице
И, наконец, чтобы просто сбросить выделение, не изменяя его начального положения, можно воспользоваться методом Collapse объекта Selection: WordApp.Selection.Collapse;
Форматирование текстовой информации Для форматирования текущего выделения через объект Selection можно получить доступ к объекту Font, определяющему характеристики шрифта данного выделения. Основные свойства объекта Font перечислены в табл. 20.2. Документ, с точки зрения текстовой информации, состоит из набора (семейства) абзацев, представленных объектами Paragraph, доступ к каждому из которых возможен через функцию i t e m объекта-семейства Paragraphs. Форматирование параграфа, редактирование которого производится в данный момент, осуществляется через свойство ParagraphFormat объекта Selection, а для объектов типа Paragraph возможности форматирования доступны напрямую.
518
Глава 20. Вывод информации за пределы программы. Технология СОМ Основные свойства объекта Font Название свойства
Таблица 20.2 Тип
.
Описание
Name
String
Название шрифта
Size
Integer
Размер шрифта
Bold
Boolean
Наличие атрибута «Полужирный»
Italic
Boolean
Наличие атрибута «Наклонный»
StrikeThrough
Boolean
Наличие атрибута «Перечеркнутый»
Subscript
Boolean
Символы в режиме «нижний индекс»
Superscript
Boolean
Символы в режиме «верхний индекс»
SmallCaps
Boolean
Все символы строчные
AllCaps
Boolean
Все символы заглавные
Доступные для изменения настройки абзаца включают выравнивание, наличие буквицы (первой буквы абзаца специального начертания), отступы первой строки от границы абзаца и отступы самой границы абзаца от краев страницы, название стиля абзаца, и множество других параметров, используемых в Word. Мы рассмотрим две часто используемых настройки абзаца — отступы абзаца и его выравнивание. Отступы абзаца задаются свойствами L e f t i n d e n t (отступ слева), Rightlndent (отступ справа) и FirstLinelndent (отступ первой строки от левой границы абзаца) объекта ParagraphFormat. Значения отступов задаются вещественными числами в условных единицах, которые можно получить из сантиметров или дюймов с помощью методов объекта «Word.Application» CentimetersToPoints и InchesToPoints. Таким образом, установка отступа текущего абзаца слева выглядит следующим образом: WordApp.Selection.ParagraphFormat.Leftindent WordApp.CentimetersToPoints(2);
:= :
Установка отступов для всех абзацев в документе может быть выполнена так: WordDoc.Paragraphs.Leftindent
10;
Выравнивание редактируемого (текущего) абзаца выполняется с помощью свойства Alignment объекта ParagraphFormat. Выравнивание всех абзацев документа можно выполнить через одноименное свойство объектатсемейства Paragraphs. В качестве значений, определяющих выравнивание, могут использоваться: 0 (выравнивание по левому краю), 1 (выравнивание по центру), 2 (выравнивание по правому краю) и 3 (выравнивание по ширине). Например, для выравнивания текущего абзаца по центру следует установить свойству Alignment объекта ParagraphFormat значение 1: WordApp.Selection.ParagraphFormat.Alignment
:= 1; 519
Часть V. Взаимодействие приложения с операционной системой
Для установки одинакового выравнивания для всех абзацев можно воспользоваться объектом-семейством Paragraphs: WordDoc.Paragraphs.Alignment := 2 ;
Использование закладок Microsoft Word поддерживает возможность работы с закладками — неотображаемыми атрибутами документа, управление которыми (добавление, удаление и переход на закладку) осуществляется с помощью диалога пункта главного меню Всфавка->3акладка. Приложение, которое является OLE-клиентом, может обратиться к семейству Bookmarks закладок для доступа к каждой из них, или к объекту Selection для перехода (перемещения выделения) на закладку, заданную именем. Для перехода на закладку следует использовать метод Goto объекта Selection: Selection. GoTo (What: Integer; Name-: String);
Параметр what указывает тип элемента, на который следует переместиться. В случае использования закладок значением данного параметра должно быть — 1. Параметр Name задает название закладки, указанное при ее добавлении в документ. Таким образом, для перемещения на закладку с названием «Имя пользователя», следует выполнить следующую команду: WordApp.Selection.GoTo(What := -1, Name := 'Имя пользователя');
Использование закладок существенно упрощает подготовку унифицированных документов, например, анкет, в которых большая часть информации является вспомогательной, а на ее основе следует заполнить какие-либо поля данных. Места, куда должна быть введена информация, можно пометить закладками, по которым Delphi-программа будет перемещать выделение с целью вывода информации методом TypeText объекта Selection. Например: Var
WordDoc:
Variant;
// Создание документа на основе шаблона «Анкета.doc», // содержащего закладку с названием «ФИО» WordDoc := WordApp.Documents.Add(Template := л Анкета.doc'); WordApp.Selection.GoTo(What := -1, Name := ' Ф И О ' ) ; WordApp.Selection.TypeText('Ольга Тихонова'); 520
Глава 20. Вывод информации за пределы программы. Технология СОМ
Управление приложением Microsoft Word Приложение Microsoft Word, которое является сервером автоматизации, может присутствовать на экране в момент обращения к нему клиента, а может быть, скрыто. Видимость приложения определяется логическим свойством Visible. Управление видимостью приложения может быть необходимо, чтобы пользователь не смог помешать процессу экспорта информации. Приложения o f f i c e устроены таким образом, что им все рано, кто вводит информацию — стороннее приложение через сервер автоматизации или пользователь с помощью интерфейса. Таким образом, если Delphi-приложение осуществляет длительный экспорт информации, используя метод TypeText объекта Selection, пользователь имеет возможность переключиться в окно Word и, например, изменить положение текстового курсора. В результате таких действий изменится состояние объекта Selection, и информация будет выведена не по порядку, а из того места, которое указал пользователь. Для того, чтобы запретить пользователю изменять выделение во время процесса экспорта информации, можно скрыть окно приложения с экрана на это время: WordApp.Visible
:=
False;
Еще одна интересная особенность использования сервера автоматизации Microsoft Word вытекает из принадлежности объекта Selection к объекту «Word.Application», а не к объекту Document, с которым работает программа. Если ссылка на сервер автоматизации не создана в программе функцией CreateOleObject, а получена из функции GetActiveOleObject, то переключение пользователем в другое окно приложения Word, используемого программой, также приведет к изменению смысла свойства Selection. После переключения в другой документ данное свойство будет определять выделение в этом документе, что является недопустимым, так как в него будет осуществляться вывод информации. Для избежания таких проблем не следует использовать сервера автоматизации, полученные функцией GetActiveOleObject, если вывод информации может занять длительное время. Заметим, что даже вывод нескольких строк может дать пользователю возможность переключения между приложениями, поэтому более правильно создавать новый сервер автоматизации при использовании методов объекта «Word.Application» вообще. Вышесказанное не относится к объектам Document, так как пользователь не может изменить их состояние78.
78
На самом деле может с помощью закрытия документа. Поэтому вывод в Microsoft Word следует производить, скрыв его с экрана. 18 Зак. 867
521
Часть V. Взаимодействие приложения с операционной системой
Экспорт текстовой информации из компонента RichEdit В качестве примера приведем процедуру переноса текстового документа из компонента RichEdit в Microsoft Word. Для этого модифицируем текстовый редактор, разработанный в главе, посвященной использованию визуальных компонентов (рис. 20.4). Добавим лишь кнопку SpeedButton с заголовком «Экспорт в Word» в панель управления файлом. Кнопке присвоим имя sbExportToWord.
I) Размер шрифта: )18
_*j
В
/
Ц
S
Цвет )• Custom... jj
< j .
>
\ 1 Отступ слева J O ~ 7 j Справа: J O ^ М Первая с т р о к а : J O " - J ! ! 1,2,3... И Undo
RTF Demo!!!
RTF Demo!!!. RTF Demo!!!
RT
FDemo!!!
Сохранение и печать...-~' "' — * : Формат: <•" Rich Text Format (*.rtf) C~ Только текст (* 4>айя:
E:\TMP\File.rtf
Загрузить
Передать документ в Microsoft Word [ Сохранить
Печатать
nWbfd
Рис. 20.4. Интерфейс приложения подготовлен для экспорта информации в Microsoft Word
Для простоты не будем реализовывать все возможности форматирования, доступных редактору. Текст, экспортируемый в Microsoft Word, будет иметь атрибуты полужирности и размера шрифта, там, где это необходимо, а абзацы сохранят выравнивание и настройки отступов. Все остальные атрибуты, такие, как цвет символов, переносятся аналогично, поэтому здесь рассмотрены не будут. Для модификации программы следует внести ссылку на модуль ComObj, поддерживающий работу с СОМ-технологиями в Delphi-программах, в раздел подключения модулей описательной части модуля главной формы: implementation Uses ComObj;
Также необходимо реализовать обработчик события Onclick для новой кнопки sbExportToWord. Код обработчика приведен в листинге 20.3.
522
Глава 20. Вывод информации за пределы программы. Технология СОМ Листинг 20.3. Обработчик события Onclick для кнопки sbExportToWord procedure TForml.sbExportToWordClick(Sender: TObject); Var WordApp, WordDoc: Variant; CurrPos: Integer; CurrParPos: Integer; CurrParNumber: Integer; NeedUpdatePar: Boolean; begin try // Создаем сервер автоматизации // «Word.Application» 'WordApp := CreateOleObject('Word.Application'); // Скрываем окно приложения Word WordApp.Visible := False; // Добавляем документ на основе шаблона // по умолчанию WordDoc := WordApp.Documents.Add; // Инициализация вспомогательных переменных CurrPos := 0; // Положение выделения // относительно документа CurrParPos := 0; // Положение выделения // относительно текущего абзаца CurrParNumber := 0; // Номер текущего абзаца NeedUpdatePar := True; // Необходимость переноса // атрибутов абзаца // Отключаем автоматическое разбиение // абзацев, чтобы каждый абзац находился // в одной строке свойства Lines.Strings reEditor.Wordwrap := False; While CurrParPos < Length(reEditor.Text) — 1 do begin // Устанавливаем выделение относительно // документа reEditor.SelStart := CurrPos; reEditor.SelLength := 1 ; // Если взведен флаг необходимости переноса // настроек абзаца (выполняется при входе // для первого абзаца) if NeedUpdatePar then begin // Переносим атрибуты абзаца в Word // — отступы 523
Часть V. Взаимодействие приложения с операционной системой WordApp.Selection.ParagraphFormat.Leftlndent := reEditor.Paragraph.Firstlndent + reEditor.Paragraph.Leftlndent; WordApp.Selection.ParagraphFormat.FirstLinelndent := -reEditor.Paragraph.Leftlndent; WordApp.Selection.ParagraphFormat.Rightlndent : = reEditor.Paragraph.Rightlndent; // — выравнивание if reEditor.Paragraph.Alignment = taLeftJustify Then WordApp.Selection.ParagraphFormat.Alignment := 0; if reEditor.Paragraph.Alignment = taCenter Then WordApp.Selection.ParagraphFormat.Alignment := 1; if reEditor.Paragraph.Alignment = taRightJustify Then WordApp.Selection.ParagraphFormat.Alignment := 2; // Атрибуты перенесены, скидываем флаг NeedUpdatePar := False; end ; // Печатаем в Word текущий символ (выделение) // — устанавливаем атрибут полужирности. if fsBold in reEditor.SelAttributes.Style then WordApp.Selection.Font.Bold := True else WordApp.Selection.Font.Bold := False; // — устанавливаем размер шрифта WordApp.Selection.Font.Size := reEditor.SelAttributes.Size; // — печатаем символ WordApp.Selection.TypeText(reEditor.SelText); // Если выделение достигло окончания абзаца if CurrParPos = Length(reEditor.Lines.Strings[CurrParNumber]) — 1 then begin // Начинаем новый абзац WordApp.Selection.TypeParagraph; // Сбрасываем положение выделение // относительно абзаца CurrParPos := •-!; // Учтываем в счетчике выделения // относительно документа символы // перевода строки CurrPos := CurrPos + 2; // Изменяем номер абзаца CurrParNumber := CurrParNumber + 1; end;
524
Глава 20. Вывод информации за пределы программы. Технология СОМ .
—
—~
// Изменяем счетчик положения выделения // относительно документа CurrPos := CurrPos + 1; . // Изменяем счетчик положения выделения // относительно абзаца CurrParPos := CurrParPos + 1; // Взводим флаг необходимости переноса // настроек абзаца NeedUpdatePar := True; end; finally // Если после выполнения всех действий // имеется ссылка на сервер автоматизации, // то есть не произошло ошибок во время // его инициализации, то выводим // на экран окно приложения word if not VarlsEmpty(WordApp) Then WordApp.Visible := True; // Разрушаем структуры программы, связанные // с объектом « W o r d . A p p l i c a t i o n » WordApp := Unassigned; // Разрушаем структуры программы, связанные // с создаваемым документом WordDoc := Unassigned; // Восстанавливаем автоматическое разбиение // абзацев по словам reEdi tor.Wordwrap : = True ; end; end;
В качестве решения поставленной задачи выбран не самый быстрый, но, по нашему мнению, самый понятный способ. Обработчик события нажатия на кнопку sbExportToWord отключает автоматическое разбиение абзаца по словам на границе компонента, заставляя, таким образом, хранить каждый абзац в одной строке свойства Lines. Далее, с помощью вспомогательных переменных происходит последовательное перемещение выделения текста по одному символу от первого до последнего. При этом отслеживается положение выделения, как относительно всего документа, так и относительно текущего абзаца. На основе номера выделенного символа внутри абзаца CurrParPos и длины абзаца, которую можно получить из соответствующего элемента свойства Lines компонента reEditor, можно определить факт окончания абзаца. В этот момент процедура выводит в Word символ перевода строки с помощью метода Type Paragraph и устанавливает внутренний флаг необходимости переноса атрибутов абзаца в Word. Непосредственно пе525
Часть V. Взаимодействие приложения с операционной системой
ренос атрибутов абзаца происходит на следующем проходе цикла, отслеживающем перемещение выделения относительно документа. Окно редактора Microsoft W o r d после экспорта в него информации из Delphi-программы показано на рис. 20.5. ; файл Qpaeica Вид Ветерка Фориат Сервис 1а6пмца £>кио '^правка
| D С* Н cl & Gi "У ••••, - . И, <7 : *1 - ^ • SI ffl П • If :, Обычный
- Times New Roman
« 12
' * j Ж /С
90%
• t5} ^j v> ;
g ;ijf 9 ЯК , <£ ! Q
Рис. 20.5. Документ в Microsoft Word создан программно
С увеличением количества поддерживаемых атрибутов существенно увеличится и объем процедуры экспорта данных. Однако преимущества, которые дает такой подход к выводу информации за пределы программы, однозначно оправдывает трудоемкость ее разработки. Сама же процедура может быть значительно упрощена за счет использования вспомогательных подпрограмм, причем такие подпрограммы могут использоваться в нескольких программных продуктах без изменения.
20.2.5. Экспорт информации в Microsoft Excel Для экспорта информации в Microsoft Excel с использованием сервера автоматизации следует: 1. Создать экземпляр сервера автоматизации «Excel.Application» (запустить Excel). 2. Создать новый документ (рабочую книгу). 3. Произвести вывод информации. 4. Разрушить структуры, связанные с экземпляром сервера автоматизации в программе. Excel обладает не менее развитой объектной иерархией, чем Word, однако использование его в качестве сервера автоматизации немного проще, в связи с логичной системой позиционирования внутри документа, представляющего собой таблицу с индексным доступом к столбцам, строкам и отдельным ячейкам.
526
Глава 20. Вывод информации за пределы программы. Технология СОМ Управление сервером автоматизации Excel Управление приложением Excel аналогично управлению приложением Word. Создание сервера автоматизации Excel производится вызовом функции CreateOleObject, в качестве параметра которой указывается название класса «Excel.Application»:
var ExcelApp:
Variant;
try // Пробуем запустить Excel ExcelApp := CreateOleObject('Excel.Application' ) ; ExcelApp.Visible := False;
Finally if not VarlsEmpty(ExcelApp) ExcelApp := UnAssigned;
then ExcelApp.Visible
:= True;
end;
_ Управление документами Excel
Документы, редактируемые в некотором экземпляре приложения Excel, доступны через семейство Workbooks объекта «Excel.Application». Ссылку на каждый элемент (документ) семейства можно получить с помощью метода Item, который возвращает объект типа Workbook. Добавление документа осуществляется методом Add, в качестве параметра которого может быть указано название шаблона: function Add(Template: S t r i n g ) : WorkBook; Если шаблон Template не задан, то документ создается на основе шаблона по умолчанию. Документ в Excel состоит из набора рабочих страниц (листов), каждой из которых сопоставляется объект типа Worksheet, причем объекты объединены в семейство Worksheets. Соответственно для определения количества листов в документе можно использовать свойство Count данного семейства, а для добавления нового листа — метод Add без параметров. Адресация элементов документа на рабочем листе Вывод информации в ячейки рабочей страницы производится с помощью свойства Range объекта Worksheet возвращающего ссылку на прямоугольное выделение ячеек на данной странице: property R a n g e [ C e l l l : S t r i n g , C e l l 2 : S t r i n g ] : Cells; 527
Часть V. Взаимодействие приложения с операционной системой
Прямоугольное выделение задается левой верхней ячейкой выделения celll и правой нижней ячейкой выделения Се112. Для установки выделения на одну ячейку, параметры Celll и Се112 должны совпадать: Worksheet.Range['D6',
Л
0б']
Далее, со ссылкой на прямоугольную область ячеек допустимы следующие действия: 1. Выделение области методом Select без параметров. 2. Занесение (вывод) информации. Вывод информации в ячейки рабочего листа Для вывода информации в ячейки или одну ячейку документа следует получить ссылку на набор ячеек с помощью свойства Range и присвоить данному свойству необходимое значение: Worksheet.RangePA1',
Х
А5']
:=
Л
Текст';
Если значение ячейки должно быть автоматически вычисляемым по заданной формуле, то первым символом значение должен быть знак = «равно». Простейшее форматирование ячеек может быть произведено с помощью подсвойства (объекта) Font свойства Range: Worksheet.Range[ЛА1' ,
Л
А 5 ' ] . F o n t . B o l d := True;
Пример вывода информации в приложение Excel В качестве примера разработаем приложение для контроля знаний пользователем таблицы умножения. Для этого откроем в Delphi новый проект и разместим на его главной форме компоненты stringGrid и кнопку Button с заголовком 'Отчет' (см. рис. 20.6).
и
• •
•
Отчет
т
|
Рис. 20.6. Форма в процессе разработки 528
Глава 20. Вывод информации за пределы программы. Технология СОМ С помощью свойств компонента StringGridl установим количество столбцов и строк, равными десяти. В обработчике события OnCreate формы занесем подписи в ячейки верхней строки и левого столбца, а также во всё остальные ячейки, причем при выводе произведений будем автоматически добавлять погрешность с помощью функции Random: (листинг 20.4). Листинг 20.4. Заполнение таблицы procedure TForml.FormCreate(Sender:
TObject);
Var
C, R: Integer; begin // Заполняем верхний заголовок For С := 1 To StringGridljiColCount-1 Do StringGridl.Cells [C, 0] := IntToStr(C+l); // Заполняем левый заголовок For R := 1 To StringGridl .RowCount-1 Do StringGridl.Cells[0, R] := IntToStr(R+l); // Заполняем ячейки с небольшой погрешностью For С := 1 То StringGridl .ColCount-1 Do For R := 1 To StringGridl.RowCount-1 Do StringGridl.Cells[C, R] := IntToStr((C+l) * (R+l) + 10 - Random(20)); end;
Далее в обработчике Onclick нажатия на кнопку Buttonl реализуем экспорт информации в Excel, во время которого состояние таблицы stringGrid на момент вызова обработчика будет переноситься в новый рабочий лист нового документа, созданного программно в приложении Excel. Код обработчика приведен в листинге 20.5 Таким образом, пользователь сможет запустить программу, исправить ошибки в таблице умножения, и вывести полученный результат в Excel (см. рис. 20.7). Листинг 20.5. Экспорт информации в Excel procedure TForml.BitBtnlClick(Sender: TObject); Var ExcelApp: Variant; ExcelDoc: Variant; ExcelSht: Variant; C, R: Integer; begin try // Создаем сервер автоматизации Excel ExcelApp := CreateOleObject('Excel.Application'); // Временно скрываем приложение Excel
529
Часть V. Взаимодействие приложения с операционной системой
шшшши 11 :
•;*j Qjxi м И I9 |10| 1S 13 24 7 г ЩйЯШЙ
*J3i:34
6
8
Т"
ИИ 16 те 16 .9
4
LJBfi SiiP" icros oft Ixtcl - KHVrtl
5~
it : .if] Файл
2
__ т~
1?
;.
15
. (^правка
11 !:"Щ &
& •
20 :;r
в
25/
то" 23':
"Зз ....... А
2
Т] 4
"s" 6 ...... ......... 9 10 11
п
Правка
5 G 7 8
Вид
"
•jj£j.] .J^i
Встдвка. Формат Сервис Данные QKMO ' j
uj Л|« зз" _ ;! О !G
QJS
">.';!?
дЛаШ
z i ©"
.
?ij.i£"-?i
в,
""г'"""з"
2 14 16: 3 в 18 4 з^ 16;
'1В""9 13" 11:
24 ""IB; 7 22
9 111 27: 10 15 26
м. < ;>..!.»
V""
':
11 16 22 27 :26 ' : ,
Е ! f
~4 '"б" бГ"? "i "16' 17 11 13 24: 12: 30 ! 13 14 20 32 -рд 25 8: 28 18 40: 44; 50 25 ЭО; 38! 48 23 46 " 45"!' 61 35 36! 51 67 47 58; 52! 65
Н: 1 | J | 8 I: 10 20 25^ "23 29 39 40: 41 45 40! 41 48 64! 59 Б2 60: 69 r 73 67 81 82 77 i 81 79 юрЁ
j:
К
: .^1 '--j
-
51
.
В
'4SI 1.>|Г •pr-r*rr— - 1 g
Й1Й я?'Х Mi |;;i._^;
•
Рис. 20.7. Результат работы программы • ExcelApp.Visible := False; // Добавляем документ на основе стандартного шаблона ExcelDoc ::= ExcelApp. Workbooks .Add; // Добавляем рабочую страницу в созданный документ ExcelSht := ExcelDoc.Worksheets.Add; // Переносим информацию из компонента stringGridl в Excel For С := 0 То StringGridl.ColCount-1 Do For R := 0 To StringGridl.RowCount-1 Do ExcelSht.Range[Chr(ord(ЛА') + R) + IntToStr(C+l), Chr(ordrA') + R) + IntToStr (C+l)] : = StringGridl.Cells[C, R] ; // Устанавливаем верхней строке атрибут полужирности ExcelSht.Range[лAl', ЛА10'].Font.Bold := True; // Устанавливаем левому столбцу атрибут полужирности ExcelSht.Range[лAl', ЛК1'].Font.Bold := True; finally // Если после выполнения всех действий переменная // ExcelApp содержит какое-либо // значение, значит приложение Excel было запущено и // можно его отобразить на экране if not VarlsEropty(ExcelApp) then ExcelApp.Visible := True; // Разрушение структур в программе, связанных с сервером // автоматизации ExcelApp := UnAssigned; end; end;
530
Управление выполнением приложения
Управление выполнением приложения, а именно, определение, каким образом и в какой последовательности вызываются подпрограммы приложения, является одной из основных функций операционной системы, однако в некоторых случаях разработчик может существенно повлиять на ход выполнения своей программы для повышения оптимальности ее работы в смысле быстродействия и ресурсоемкости, либо для достижения каких-либо специализированных задач, например, оптимизации распределения вычислительной нагрузки.
21.1. Создание и использование DLL-библиотек 21.1.1. Понятие и назначение DLL-библиотек По умолчанию построение программного продукта производится таким образом, что все фрагменты кода находятся в одном исполняемом ехефайле. Однако программа может выполнять в каждый момент времени различные задачи, и некоторая часть подпрограмм, загруженных в память при старте приложения, не будет использоваться. С усложнением приложения неэффективный расход памяти в таких случаях может стать серьезной проблемой. С другой стороны, несколько приложений могут использовать одинаковые фрагменты кода. Соответственно, при построении программных продуктов с полным включением в исполняемый файл всех подпрограмм, один и тот же набор подпрограмм будет храниться в двух разных файлах, а при одновременном запуске этих приложений еще и существовать в памяти в нескольких версиях. Аналогична ситуация и с запуском одного приложения несколько раз — все его подпрограммы будут загружены отдельно для каждой копии выполняемого приложения. 531
Часть V. Взаимодействие приложения с операционной системой
Для оптимизации программ с точки зрения описанных проблем применяются динамически компонуемые библиотеки (от англ. DLL — DynamicLink Library — Динамически компонуемая библиотека). DLL-библиотеки являются программными модулями, аналогичными по своей структуре исполняемой программе, и могут содержать подпрограммы, а также различные ресурсы, например, изображения. При построении приложения, функции, входящие в состав DLL, не заносятся в ехе-файл, а доступ к ним обеспечивается на этапе выполнения программы через подключение DLL-библиотеки и получение ссылки на необходимую подпрограмму. Все эти действия выполняются с помощью специальных API-функций. Любое приложение может получить доступ к библиотеке, при условии, что известно название файла с библиотекой и название подпрограммы, которую следует вызвать, а также список ее параметров и тип возвращаемого значения. При этом библиотека является откомпилированным программным модулем и не содержит исходного программного кода. Таким образом, помимо снижения нагрузки на память, динамические библиотеки могут использоваться для сокрытия реализации подпрограмм, передаваемых сторонним разработчикам.
21.1.2. Создание DLL-библиотеки Динамические библиотеки описываются в отдельных файлах, аналогичных по своей структуре основной части программы, за исключением того, что вместо ключевого слова Program используется ключевое слово Library. Программный продукт, который подключает DLL-библиотеку, может обратиться не ко всем ее подпрограммам, а только к тем, которые указаны в разделе exports модуля DLL. Остальные подпрограммы, таким образом, могут использоваться только для внутренних целей библиотеки. Library Заголовок библиотеки Uses Раздел подключения модулей (библиотек) Label Раздел описания меток безусловного перехода Const Раздел описания констант Type Раздел описания нестандартных типов данных .Var
Раздел описания переменных 532
Глава 21. Управление выполнением приложения Procedure Function Описание подпрограмм,
входящих в библиотеку
exports Список экспортируемых подпрограмм Начало раздела описания логики программы Раздел описания логики программы Конец раздела описания логики программы
Begin
End.
DLL-библиотека создается в отдельном проекте, имеющем соответствующий тип. Для создания проекта динамической библиотеки следует воспользоваться пунктом главного меню File->New—>other и в появившемся диалоге на странице New выбрать пункт «DLL wizard» (см. рис. 21.1).
InltaWeb | WebServices | Business ] WebSnap | \ л/eb Documents j Cotba J ActiveX j Mutter j Projects ) Forms | Dialoas j Projects Data Modulei |
New
Name
-i
| Description
?Ц Control Panel Module Щ5 Data
Creates a new Control Panel Applet Module Module —
i
О Form ^Pl Frame
«j Г
1 Qafy
С
!•'£•<•:•:•<<.
С
Ц&-6
OK
Cancel
^
Help
Рис. 21.1. Диалог создания элемента проекта
Данный диалог можно вызвать и из диалога управления группой проектов, воспользовавшись кнопкой New, а также с помощью пункта главного меню Project->Add New Project. В этих случаях новые DLL-проекты будут добавлены в текущую группу проектов, тогда как при использовании для добавления DLL-проекта пункта меню File-»Add-»other будет создана новая группа проектов. Новому DLL-проекту присваивается название Projectl.dll (см. рис. 21.2), которое может быть изменено при сохранении проекта. Project Manager ] Project1.dll Files В 5f| ProjeclGroupl
q i Sfi
л
ЕР
— ' I New Remove > ч-' -•-••; . J. Path '
.
"
'.' "' "' "ZZZZZ.~ZJ
EAPrograrnFiles\Borland\DelphiAPtojecls E:.^ProgiamFiles\Borland\DelphiAProiects
Рис. 21.2. DLL-проект в диалоге управления группой проектов 533
Часть V. Взаимодействие приложения с операционной системой
После нажатия на кнопку ОК создается новый проект, содержащий один файл — шаблон описания DLL-библиотеки, содержащий следующий код: library Projectl; {Important note about DLL memory management: ShareMem must be the first unit in your library's USES clause AND your project's (select Project-View Source) USES clause if your DLL exports any procedures or functions that pass strings as parameters or function results. This applies to all strings passed to and from your DLL—even those that are nested in records and classes. ShareMem is the interface unit to the BORLNDMM.DLL shared memory manager, which must be ' deployed along with your DLL. To avoid using BORLNDMM.DLL, pass string information using PChar or ShortString parameters.} uses SysUtils, Classes; {$R *.res) begin end.
21.1.3. Особенности передачи строковых параметров в подпрограммы DLL-библиотек Обратим внимание на предупреждение, оформленное в виде комментария, в котором описываются правила использования строковых параметров в подпрограммах, включенных в динамические библиотеки. Особенности работы со строками связаны с их представлением в Delphi, нехарактерным для других языков программирования. Для передачи параметров типа String в подпрограммы библиотеки следует подключить модуль ShareMem в разделе Uses. Модуль ShareMem должен быть первым из списка Uses: uses ShareMem, S y s U t i l s , Classes;
Однако при подключении к библиотеке модуля ShareMem для работоспособности программы, подключающей данную библиотеку, понадобится наличие еще и библиотеки BORLNDMM.DLL, поставляемой с Delphi (она расположена в подкаталоге Bin каталога, в который установлена Delphi). Если использование дополнительной библиотеки нежелательно, то параметры типа string использовать не следует, а текстовую информацию можно передавать в подпрограммы библиотеки с помощью типа PChar, характерного для работы в Windows. 534
Глава 21. Управление выполнением приложения
Указанное правило касается не только передачи строковых значений в качестве параметров, но и получения строк в качестве результата работы подпрограмм библиотеки, а также использования строковых значений в составе записей. Во всех этих случаях следует подключать модуль ShareMem и, соответственно, прилагать к программному продукту библиотеку BORLNDMM.DLL, либо вместо значений типа string использовать данные типа pchar. / '
21.2.4. Наполнение библиотек подпрограммами
Подпрограммы — процедуры и функции описываются в модуле библиотеки, а затем, часть из них может быть заявлена для экспорта в вызывающие приложения в разделе exports. Список экспортируемых подпрограмм определяет, какие именно подпрограммы данной библиотеки могут быть доступны приложениям, подключившим библиотеку, и имеет следующий вид: exports <Внутреннее имя подпрограммы> name <Внешнее имя подпрограммы>, <Внутреннее имя подпрограммы> name <Внешнее имя подпрограммы>;
Внутреннее имя подпрограммы — это название подпрограммы, с которым она описана в модуле library, либо в одном из модулей, подключаемых в разделе Uses 79 . Внешнее имя подпрограммы, указываемое после ключевого слова name, не является обязательным, и используется для назначения псевдонима подпрограмме, который должен быть указан в качестве ее имени при подключении сторонним модулем. Если псевдоним подпрограммы не указан, то он считается совпадающим с названием этой подпрограммы. Если для экспортируемой подпрограммы не указывается псевдоним, то ключевое слово name также не используется. Приведем пример описания модуля динамической библиотеки, содержащей три подпрограммы prod, Proc2 и РгосЗ, две из которых (Ргос 1 и РгосЗ) заносятся в раздел exports для обеспечения доступа к ним сторонними приложениями (см. листинг 21.1). Процедуре Prod назначим псевдоним «PrintAbout».
" Речь идет о том, что в раздел export можно внести не только подпрограммы, описанные в модуле динамической библиотеки, но и подпрограммы, содержащиеся в модулях, подключаемых в разделе Uses.
• I I Часть V. Взаимодействие приложения с операционной системой
ч Листинг 21.1. Пример описания модуля динамической библиотеки
library Projectl; uses SysUtils, Classes; {$R *.res} {описание процедуры Prod} Procedure Procl; begin end; {описание процедуры Ргос2} Procedure Proc2; begin end; {описание процедуры РгосЗ} Procedure РгосЗ; begin end; {Заявка на экспорт подпрограмм} exports {Процедура Procl экспортируется с псевдонимом PrintAbout} Procl name 'PrintAbout', {Процедура РгосЗ экспортируется без псевдонима, под своим именем} РгосЗ ; begin end.
21.1.5. Использование DLL-библиотек. Виды динамической компоновки Для вызова подпрограмм динамической библиотеки, следует получить ссылки на них. Это действие называется динамической компоновкой (связыванием приложения и подпрограммы, реализованной в библиотеке) и может быть выполнено двумя способами — явно, когда название DLLбиблиотеки и подпрограммы задается на этапе разработки приложения, а связывание производится автоматически при старте приложения, и неявно, когда загрузка библиотеки и связывание с подпрограммами производится отдельными запросами по ходу выполнения приложения.
536
Глава 21. Управление выполнением приложения
Неявная компоновка Для неявной компоновки используются следующие описания подпрограмм в модулях, где производится их вызов: Оаголовок подпрограммы>; external <Название библиотеки> name <Псевдоним подпрограммы:»; •
Заголовок подпрограммы, заявленный в приложении должен соответствовать по сигнатуре заголовку этой подпрограммы, описанной в динамической библиотеке. Если название подпрограммы, по которому она будет использоваться в приложении, отлично от ее библиотечного псевдонима, то следует использовать конструкцию name <Псевдоним>. Например, подпрограмму Prod, описанную в приведенном выше DLL-модуле, можно неявно скомпоновать с приложением следующим образом: Procedure P r i n t A b o u t ;
external
'PROJECT!.DLL' ;
Так как использованное название PrintAbout соответствует псевдониму данной подпрограммы в библиотеке, то дополнительного указания псевдонима и ключевого слова name не требуется. Если бы подпрограмма описывалась с другим именем, например A b o u t P r i n t i n g , то потребовалось бы дополнительное указание псевдонима: Procedure AboutPrinting; external лPROJECT1.DLL' name 'PrintAbout'; При неявной компоновке динамические библиотеки, указанные после ключевого слова external автоматически загружаются в память, после чего происходит связывание их подпрограмм с идентификаторами, заявленными в приложении. Разрушение связей с подпрограммами и закрытие используемых DLL-библиотек производится также автоматически, при завершении приложения. Отметим, что неявное связывание применяется, обычно, в случаях, когда все подпрограммы, получаемые из библиотек, вызываются на протяжении всей работы программы, иначе существование загруженной библиотеки не является целесообразным. Например, библиотека может содержать сотни функций, которые используются приложением только для начальной инициализации. При загрузке приложения автоматически будет загружена и библиотека, которая будет находиться в памяти до окончания приложения, хотя обращений к ее подпрограммам из приложения уже не будет выполняться. Выгрузить динамическую библиотеку принудительно невозможно при использовании неявного связывания, поэтому в таких ситуациях лучше использовать другие механизмы подключения библиотеки.
19 Зап. 867
537
Часть V. Взаимодействие приложейия с операционной системой
Явное связывание Явное связывание подразумевает собой прямое указание на загрузку динамической библиотеки с помощью API-функции LoadLibrary, и последующее связывание переменной процедурного типа, описанной в приложении, с адресом подпрограммы, хранящейся в библиотеке, с помощью API-функции GetProcAddress. Перечисленные функции описаны следующим образом: function LoadLibrary(IpLibFileName: PChar): HMODULE;. stdcall; function GetProcAddress(hModule: HMODULE; IpProcName: LPCSTR): FARPROC; stdcall; Параметры IpLibFileName и IpProcName задают названия динамической библиотеки и подпрограммы, на которую приложение получает ссылку. Значение, возвращаемое функцией LoadLibrary, используется в функции GetProcAddress для идентификации загруженной библиотеки. В момент, когда работа с библиотекой завершена, то есть в подпрограммах, содержащихся в этой библиотеке, больше нет необходимости, ее можно выгрузить из памяти с помощью функции FreeLibrary, описанной следующим образом: function FreeLibrary(hLibModule: HMODULE): BOOL; stdcall; Приведем пример явного связывания:
Type {Описание процедурного типа, соответствующего по сигнатуре используемой подпрограмме (процедура без параметров)} TProc = Procedure ; Var
• {Описание ссылки на библиотеку} DLLLibrary: HModule; {Описание процедурной переменной для связывания с подпрограммой библиотеки} Proc: TProc; if begin {Загрузка библиотеки и получение ссылки на нее} LLibrary := LoadLibrary(XPROJECT1.DLL'); {Получение ссылки на метод по имени метода и ссылке на библиотеку} 538
Глава 21. Управление выполнением приложения Proc := GetProcAddress(DLLLibrary, 'PrintAbout'); (Обнуление ссылки на метод} Proc := nil; (Выгрузка библиотеки из памяти} FreeLibrary(DLLLibrary};
21.1.6. Соглашения о вызовах подпрограмм При вызове подпрограмм параметры могут передаваться им разными способами, например через стек или регистры процессора, в прямом порядке (слева направо в порядке описания) или в обратном (справа налево). Если подпрограмма вызывается в приложении, созданном в той же среде разработки, где создана подпрограмма, то проблема несовместимости способов передачи параметров при вызове не возникает, так как используется одно и то же соглашение о вызове (англ. Calling convention — Соглашение о вызове). Однако динамические библиотеки могут быть разработаны с использованием Microsoft Visual C++, Delphi, либо какого-либо другого языка программирования. При этом в разных языках используются разные соглашения о вызовах — в Delphi параметры передаются через регистры процессора в прямом порядке, а в C++ через стек в порядке справа налево. Для того чтобы правильно осуществлять вызовы процедур и функций, хранимых в DLL, а также создавать DLL, которые могут быть использованы разработчиками, применяющими не Delphi, а другие инструменты, следует описывать подпрограммы с указанием соглашения о вызове, называемым стандартным, принятым в C++. Для этого после описания процедуры указывается ключевое слово stdcall: <0писание подпрограммы>;
stdcall;
При неявном связывании указание стандартного соглашения может выглядеть следующим образом: Procedure A b o u t P r i n t i n g ; stdcall; external ' P R O J E C T l . D L L ' name 'PrintAbout'.; При явном связывании указание соглашения производится при описании процедурного типа: Туре ТProc = Procedure; stdcall;
, Остальные модификаторы соглашений о вызове приведены в табл. 21.1. 539
Часть V. Взаимодействие приложения с операционной системой Модификаторы соглашений о вызове Модификатор
Таблица 21.1
Порядок параметров
Область памяти, через которую передается параметр
Освобождение памяти, занятой параметрами
register
Прямой (слева направо)
Регистры процессора
Подпрограмма
pascal
Прямой (слева направо)
Стек
Подпрограмма
cdecl
Обратный (справа налево)
Стек
Вызывающая подпрограмма
stdcall
Обратный (справа налево)
Стек
Подпрограмма
safecall
Обратный (справа налево)
Стек
Подпрограмма
21.2. Многопоточность 21.2.1. Понятие и назначение потоков Поток — это последовательность каких-либо команд и вызовов подпрограмм. В каждой программе, таким образом, имеется как минимум один поток, называемый основным, однако могут быть созданы и дополнительные потоки, выполняемые параллельно основному80. Потоки внутри программы работают совместно, конкурируя между собой в отношении процессорного времени. Какому потоку следует передать управления за счет другого потока, определяет операционная система на основе важной характеристики потока — его приоритета. Повышенный приоритет потока указывает системе на его большую необходимость для приложения, и такой поток имеет большую вероятность получить управление. Потоки предназначены для решения двух задач. Первой является выполнение мощных фоновых процессов, аналогично распределению вычислительной нагрузки на систему с помощью обработки события Onidle. Однако использование Onidle не гарантирует вызова обработчика, так как при высокой 'нагрузке на систему это событие может быть не инициировано, тогда как поток получает управление в зависимости от его приоритета. Еще одно достоинство потока по сравнению с обработчиком Onidle состоит в том, что поток не может монополизировать вычислительные ресурсы системы — управление будет отобрано у него в соответствующий момент в соответствии с его приоритетом и передано другому потоку. Таким образом, даже если поток выполняет бесконечный цикл, он не может «подвесить» систему или снизить скорость ее работы.
0
Потоки иногда называют нитями.
540
Глава 21. Управление выполнением приложения
Вторая задача, решаемая потоками — одновременное выполнение нескольких процессов таким образом, чтобы каждый из них мог выполнить свои функции. В качестве примера можно привести приложение, которое-получает данные, например, через СОМ-порт, сохраняет их в файле на диске, и выводит информацию на экран. В приложении, таким образом, организовывается три равнозначных процесса — получение информации и два вида ее вывода. При этом получение информации является приоритетной задачей, вывод на экран — менее приоритетной, а сохранение в файл вообще может быть отложено, если данные могут храниться в памяти. Такое приложение можно организовать с помощью двух потоков — потока получения информации, потока вывода информации на экран с меньшим приоритетом, а сохранение в файл вообще может выполняться в фоновом режиме при обработке события Onidle. Сохранение в файл также можно реализовать в дополнительном потоке с самым низким приоритетом, тогда в приложении будет три потока. Еще один интересный нюанс многопоточных приложений состоит в том, что при некоторых долгосрочных операциях не используются ресурсы процессора. .Такой операцией, например, является запись информации на диск. Многопоточные приложения в таких случаях более оптимально расходуют ресурсы компьютера, так как когда одна нить (поток) ожидает завершения операции записи или чтения, другая в этот момент может производить какие-либо вычисления. Если бы эти операции выполнялись последовательно в одном потоке, то вычисления не могли бы производиться во время процесса ввода/вывода, а были бы выполнены после его завершения.
21.2.2. Описание потока Поток выполнения является системным понятием и поддерживается непосредственно средствами Windows. В Delphi свойства и методы потоков инкапсулированы в класс Tthread (англ. Thread — нить, поток), в котором заявлен абстрактней метод Execute без параметров, как раз и представляющий собой последовательность команд, выполняемых в потоке: procedure Execute; virtual; abstract; Для создания дополнительного потока в программе следует реализовать класс-наследник TThread, переопределив метод Execute. Допустим, в программе необходимо произвести вычисление некоторого параметра по рекуррентной формуле:
где i изменяется от 1 до заданного значения.
541
Часть V. Взаимодействие приложения с операционной системой
Опишем потоковый класс для вычисления такого значения, переопределив метод Execute базового потока TThread (см. листинг 2 1 . 2 ) . Листинг 21.2. Потоковый класс (Описание главной формы приложения} TForml = class(TForm) Buttonl: TButton; Editl: TEdit; Labell: TLabel; {Описание класса потока} TCalcThread = class(TThread) protected iCount: Integer; dResult: Double; procedure Execute; override; end; {Описание ссылки на var Forml: TForml;
главную форму}
procedure TCalcThread.Execute; Var i: Integer; begin {Начальное значение} dResult := 1; {Вычисление значения длительным циклом} for i := 2 to iCount do dResult := dResult + Sin(dResult) / Cos(dResult); {Занесение значения в текстовую метку L a b e l l } Forml.Labell.Caption := FloatToStr(dResult) ; end;
Описание потока не приводит к выполнению каких-либо действий. Для того чтобы потоку было передано управление, должен быть создан экземпляр потокового класса, заполнены внутренние свойства, необходимые для выполнения вычислений, после чего поток запускается.
542
\
;
., Глава 21. Управление выполнением приложения
Л.2.3. Создание потока /правление его выполнением Экземпляра потока используется конструктор Create, опиующим образом: ;ructor Create (CreateSuspended: Boolean);
эаметр GreateSuspended определяет, следует ли создать поток в неактивном состоянии (от англ. Create Suspended — Создавать приостановленным). Если конструктору передается значение False, то поток немедленно после создания запускается и работает, пока не закончится метод Execute. Если же поток создан в приостановленном состоянии, то его можно принудительно запустить, использовав метод Resume (англ. Resume — Возобновлять, продолжать): procedure Resume;
Для того чтобы приостановить поток, следует воспользоваться методом Suspend: procedure Suspend; Приведем пример использования потока TCalcThread, описанного выше: Var {Описание ссылки на объект потока} Thread: TCalcThread;
begin {Создание объекта потока. Поток создается в приостановленном состоянии} Thread := TCalcThread.Create(true); {Настройка свойств объекта потока} Thread.iCount := StrToInt(Editl.Text); '{Запуск потока} Thread.Resume; {Приостановка потока} Thread.Suspend; {Повторный запуск потока} Thread.Resume;
Обратим внимание на одну существенную деталь. Несмотря на простоту класса TThread в нем инкапсулированы довольно сложные процессы 543
Часть V. Взаимодействие приложения с операционн'о^.
по созданию и разрушению потока, а такж Execute имеет область видимости protected Ч для выполнения из любого фрагмента кода, paci. модуле, где описан класс потока. Однако при пр. Execute, он выполняется & том потоке, который ег£ в основном потоке приложения, что противоречит токового объекта. Для запуска метода Execute только метод Resume.
21.2.4. Разрушение потока Потоковый объект самостоятельно разрушает все структуры дан_.^л, связанные с использованием ресурсов операционной структуры, после завершения метода Execute. Для того чтобы разрушить данные, связанные с потоком в программе, следует установить факт завершения потока, что является непростой задачей, так как после запуска потока методом Resume управление сразу же возвращается в вызвавшую этот метод подпрограмму, и она выполняется дальше. При этом метод Execute, возможно, еще не завершен. В классе TThread предусмотрено событие OnTerminate, обработчик которого, если он назначен, вызывается при завершении метода Execute: property OnTerminate: TNotifyEvent; Заметим, что поскольку класс TThread не является компонентом и не участвует в визуальной разработке приложения, для назначения обработчика событию OnTerminate следует самостоятельно реализовать соответствующий метод в основном потоке приложения, например в классе формы, и присвоить ссылку на него свойству OnTerminate потокового объекта. Данный метод должен принимать один параметр типа TObject. Создание такого рода обработчиков подробно описано при обсуждении события Onldle класса TApplication. Однако разрушение потокового объекта в обработчике события OnTerminate некорректно, так как сам обработчик вызывается из этого объекта81, соответственно, при разрушении объекта теряется точка возврата, необходимая для передачи управления. Для решения проблемы разрушения объекта потока в классе TThread предусмотрен механизм, который делает это автоматически. Для включения данного механизма следует установить значение True свойству FreeOnTerminate, описанному в классе TThread: property FreeOnTerminate:
Boolean;
Обработчик события OnTerminate вызывается из потокового класса, однако его вызов реализован так, что обработчик выполняется в основном потоке. Для этого в описании класса TThread применен метод Synchronize, описанный ниже.
544
Глава 21. Управление выполнением приложения
Установку значения свойства FreeOnTerminate можно выполнить в любом месте программы, при условии, что поток еще не завершен. Если потоковый объект создается в приостановленном состоянии, то установить значение можно в вызывающем фрагменте сразу же после создания: {Создание объекта п о т о к а . Поток создается в приостановленном состоянии} Thread := TCalcThread.Create(true) ; {Настройка свойства FreeOnTerminate объекта потока) Thread.FreeOnTerminate := True; {Настройка других свойств объекта потока}
Если же поток начинает работу немедленно после создания, то установку значения свойству FreeOnTerminate можно выполнить в начале метода Execute самого объекта: procedure
TCalcThread.Execute;
begin {Установка свойства FreeOnTerminate} FreeOnTerminate : = True ;
Для завершения потока можно воспользоваться методом Terminate, описанным в классе TThread: procedure Terminate;
При вызове данного метода не производится каких-либо действий, реально влияющих на выполнение потока, а просто устанавливается значение свойства Terminated, которое может быть проверено методом Execute, который должен самостоятельно решить, как следует завершить свое выполнение: property Terminated:
Boolean;
Модифицируем описанный ранее вариант метода Execute таким образом, чтобы он реагировал на вызов метода Terminate: procedure TCalcThread.Execute; Var i: Integer; dResult: Double;
545
Часть V. Взаимодействие приложения с операционной системой
begin {Начальное значение) dResult := 1; {Вычисление значения длительным циклом} for i := 2 to iCount do begin {Если установлен флаг Terminated, то цикл следует прервать} if Terminated then break; dResult := dResult + Sin(dResult) / Cos(dResult); end; {Занесение значения в текстовую метку Labell} Forml.Labell.Caption : = FloatToStr(dResult); end;
21.2.5. Получение результата работы потока Если поток создается с целью получения какого-либо результата, то следует предусмотреть механизм его возврата в приложение, создавшее поток. Для этого можно использовать несколько различных подходов, каждый из которых обладает своими особенностями. Использование VCL Теоретически, поток может использовать ссылки на интерфейсные компоненты для того, чтобы отображать в них свои внутренние данные. В нашем примере в качестве последней команды метода Execute используется вывод на экран строки с помощью текстовой метки, расположенной на форме. Ссылка на форму может быть получена с помощью переменной модуля Forml, а ссылка на метку доступна как свойство этой формы: Forml.Labell.Caption
:= F l o a t T o S t r ( d R e s u l t ) ;
Однако библиотека визуальных компонентов (VCL) реализована таким образом, что доступ к интерфейсным компонентам из разных потоков может вызвать конфликт между ними. Таким образом, корректное изменение свойств визуальных компонентов возможно только из того потока, который ими владеет, то есть из основного. Для того чтобы выполнить какой-либо метод потокового объекта из основного потока для получения доступа к визуальным компонентам, в классе TThread предусмотрен метод Synchronize: procedure S y n c h r o n i z e (Method: TThr,eadMethod) ;
546
Глава 21. Управление выполн\ением приложения На время выполнения метода, указанного в качестве параметра, работа потока приостанавливается. Метод, который должен быгь выполнен, передается по имени, является элементом экземпляра потокового класса, и не имеет параметров: Type TThreadMethod = procedure of object; Модифицируем метод Execute таким образом, чтобы текущее значение отображалось в текстовой метке без вероятности появления конфликтов (см. листинг 21.3). „
.....:,,„
,
.
,.
, . , , , ,
,,„.....-.,•.....•..
..<
. . . . , . , , , „ ,
Листинг 21.3. Использование VCL
.,,.
.„:„,„.
.,..:
-
, , „ , ; , . , , . ,
•
..,
{Описание метода потокового класса, изменяющего состояние визуального компонента) procedure TCalcThread.SetResultToCaption; begin Forml.Label!.Caption := FloatToStr(Self.dResult); end; {Метод, выполняемый в качестве потока} procedure TCalcThread.Execute; Var i: Integer; begin {После завершения метода Execute следует автоматически разрушить потоковый объект} FreeOnTerminate := True; dResult := 1; for i := 2 to iCount do begin i {Если установлен флаг Terminated, выполнение -цикла следует прервать} if Terminated then break; {Расчет текущего значения} dResult := dResult + Sin(dResult) / Cos(dResult); {Вызов метода SetResultToCaption таким образом, чтобы он выполнялся в основном потоке} Synchronize(SetResultToCaption); end; end;
547
Часть V. Взаимодействие приложения с операционной системой
/Использование обработчика OnTerminate Обработчик события OnTerminate является удобным местом для получения результатов работы нити, так как вызывается после завершения метода Execute, но до разрушения экземпляра потокового класса. В обработчике доступны свойства потокового объекта, в которых и должны храниться результаты работы потока. Так как обработчик события OnTerminate принадлежит основному потоку, то из него возможен доступ к визуальным компонентам. Сочетание этих двух свойств обработчика OnTerminate делает его самым привлекательным способом получения результатов работы потока. Использование дополнительных событий Если поток должен взаимодействовать с другим потоком для передачи ему текущих данных, то использование обработчика события OnTerminate невозможно, так как он вызывается после завершения потока. Однако потоковый класс полностью реализуется разработчиком программного продукта, который может добавить в него собственные события. Модифицируем наш пример таким образом, чтобы при расчете очередного значения вызывался обработчик дополнительного события OnCurrentValue (листинг 21.4). Для этого в классе TCalcThread опишем свойство fOnCurrentValue и организуем на его основе propertyсвойство OnCurrentValue. В качестве типа данного свойства используем стандартный тип для простых обработчиков TNotifyEvent.
Листинг 21.4. Использование дополнительного события OnCurrentValue {Описание класса потока} TCalcThread = class(TThread) protected iCbunt: Integer; dResult: Double; \ fOnCurrentValue: TNotifyEvent; procedure Execute; override; published property OnCurrentValue: TNotifyEvent read fOnCurrentValue write fOnCurrentValue; . end;
548
Глава 21. Управление выполнением приложения {Метод, выполняемый в качестве потока} procedure TCalcThread.Execute; Var
i: Integer; begin * {После завершения метода Execute следует автоматически разрушить потоковый объект} FreeOnTerminate := True; dResult := 1; for i := 2 to iCount do begin {Если установлен флаг Terminated, выполнение цикла следует прервать} if Terminated then break; {Расчет текущего значения) dResult := dResult + Sin(dResult) / Cos(dResult); {Если назначен обработчик событию 'OnCurrentValue, то есть ссылка в свойстве f O n C u r r e n t V a l u e не равна nil, то вызовем этот обработчик} if fOnCurrentValue <> nil then fOnCurrentValue(self) end; end;
Назначение обработчика такому событию аналогично назначению обработчика событию OnTerminate. Использование глобальных переменных Результаты работы потока могут быть сохранены и в глобальных переменных модуля, однако это противоречит концепции объектно-ориентированного программирования. Соответственно, такой метод взаимодействия потока и создавшего его приложения не рекомендуется. Еще одна причина, которая заставляет отказываться от использования глобальных переменных несколькими модулями одновременно — это возможность появления несогласованности данных. Несколько потоков выполняются параллельно, причем, в какой момент поток потеряет управление, неизвестно. Соответственно, при длительном формировании данных, которые должны быть согласованы между собой, появляется вероятность того, что сформирована только часть данных, согласованность между ними на время потеряна, а формирующий поток потерял управление, и к данным обращается поток, использующий их. Таким образом, использующий поток получает некорректные данные.
549
Часть V. Взаимодействие приложения с операционной системой
Решение описанной проблемы возможно с помощью применения методов синхронизации потоков, однако проще отказаться от передачи данных через глобальные переменные вообще.
21.2.6. Приоритеты потоков Для указания приоритетов потоков используется свойство Priority, определяющее приоритет данного потока относительно других потоков приложения: property
Priority:
• TThreadPriority;
Значения, которые можно использовать для установки приоритета потокам, перечислены в табл. 21.2. Приоритеты потоков Приоритет
Таблица 21.2 Описание
tpldle
Поток выполняется, только если система не выполняет каких-либо действий. Передача управления потоку с таким приоритетом не может прервать выполнения других потоков
tpLowest
Поток имеет приоритет существенно ниже среднего
tpLower
Поток имеет приоритет ниже среднего
tpNormal
Поток имеет нормальный приоритет
tpHigher
Поток имеет приоритет выше среднего
tpHighest
Поток имеет приоритет существенно выше среднего
tpTimeCritical
Поток является наиболее приоритетным
Свойство Priority доступно для изменения во время выполнения потока, что позволяет гибко управлять приоритетами потоков, изменяя их в зависимости от контекста выполнения программы.
Вопросы с ответами для повторения по части V Опишите основные методы, применяемые в Delphi для управления окружением программы Ответ: Для управления окружением программа Delphi может использовать следующие глобальные объекты, создаваемые автоматически при запуске приложения:! 1 .^Application типа TApplication используется для управления данным экземпляром приложения и позволяет определить его основные характеристики, управлять состоянием приложения (сворачивать, разворачивать и закрывать), распределять вычислительную нагрузку на 550
Вопросы с ответами для повторения по части V приложение (через обработку события Onidle), а также изменить стандартное поведение программы в отношении: » отображения быстрых подсказок пользователю (hints); » идентификации приложения в системе с точки зрения пользователя (через изменение значка приложения и его заголовка); » реакции на исключительные ситуации, которые не были обработаны ни одним методом программы; Screen типа TScreen используется для определения текущих характеристик рабочего стола Windows, например, разрешения экрана; Mouse типа TMouse используется для определения характеристик манипулятора мышь, используемого в системе, а также для выполнения некоторых специфических действий, таких как монополизация (захват) мыши окном.
Дайте определение динамически компонуемой библиотеки и расскажите о возможных мотивах применения DLL Ответ: Динамически компонуемая библиотека — Dynamic Link Library — представляет собой программный модуль, содержащий подпрограммы и или какие-либо данные, которые могут быть подключены во время выполнения программы и не линкуются к ней во время построения. При использовании динамических библиотек их наличие обязательно для нормального функционирования приложений, которые на них ссылаются. Динамические библиотеки помогают решать задачи разделения одних и тех же фрагментов кода между различными приложениями, снижая тем самым объем памяти, необходимый для хранения приложений и их загрузки. В качестве еще одного достоинства динамических библиотек можно назвать возможность скрытия реализации подпрограмм, включаемых в библиотеку, при передаче их сторонним разработчикам.
Расскажите про использование DLL в программных продуктах Ответ: Для вызова подпрограммы, расположенной в динамической библиотеке, следует загрузить необходимую библиотеку с помощью APIфункции LoadLibrary и получить ссылку на подпрограмму с помощью функции GetProcAddress. Такой подход к получению доступа к библиотеке называется явным связыванием приложения и подпрограммы библиотеки. Неявное связывание подразумевает указание названия динамической библиотеки при описании метода, в результате чего, вся функциональность, необходимая для получения адреса метода будет добавлена в программу автоматически при компиляции. Это не отменяет требования к наличию динамической библиотеки в области видимости программного продукта, использующего неявное связывание.
551
Часть V. Взаимодействие приложения с операционной системой
Применение неявного связывания целесообразно в случаях интенсивного использования динамической библиотеки в течение всего времени работы приложения, в остальных случаях рекомендуется применение явного связывания. , Расскажите про многопоточные приложения Ответ: В каждом приложении существует как минимум один поток выполнения — главный, однако с помощью наследников класса TThread можно создать один или несколько дополнительных потоков для оптимизации выполнения приложения в целом и более гибкой настройки взаимодействия фрагментов программы, которые выполняют разные задачи. Для создания потока следует реализовать класс-наследник TThread, поместив последовательность команд потока в метод Execute. Далее, создавшая поток программа может управлять им, приостанавливая и возобновляя его работу, а также завершая его в случае необходимости. При окончании выполнения метода Execute поток завершается автоматически. .
Расскажите об особенностях модели СОМ Ответ: СОМ — Component Object Model — Модель многокомпонентных объектов является объектно-ориентированной концепцией, описывающей правила построения классов, называемых СОМ-объектами, с целью обеспечения взаимодействия между ними вне зависимости от языка, на котором реализованы данные классы, и среды, в которой выполняются приложения содержащие их. Механизм работы СОМ построен на использовании объектных интерфейсов, и каждый СОМ объект поддерживает как минимум один интерфейс — lUnknown, задачей которого является контроль количества созданных экземпляров объекта и выдача информации о других интерфейсах, реализованных СОМ-объектом. СОМ-объекты могут применяться как в составе программного продукта (в I этом случае они называются внутренними), так и за его пределами (внешние СОМ-объекты). Связывание методов, которые предоставляет СОМобъект, с вызывающей программой может быть реализовано двумя путями — через таблицу виртуальных методов на этапе компиляции программного продукта (раннее связывание) и через вызов метода Invoke интерфейса IDispatch, если такой интерфейс поддерживается СОМобъектом (позднее связывание). Опишите возможности Delphi в области вывода информации за пределы приложения Ответ: Delphi поддерживает вывод информации на печать с помощью объекта класса TPrinter, использование которого сводится к отображению графических данных на его свойстве Canvas. Для вывода информации используется принтер, установленный в системе по умолчанию, одна-
552
Вопросы с ответами для повторения по части V ко настройки печатающего устройства могут быть изменены с помощью соответствующих диалоговых компонентов. Некоторые стандартные компоненты Delphi поддерживают также печать своего контекста, однако такие возможности могут быть использованы только в целях разработки и отладки программного продукта, в силу отсутствия каких-либо настроек для повышения качества получаемых документов. Второй подход к выводу информации за пределы программы подразумевает использование OLE-технологии — взаимодействии приложения с СОМобъектами, реализующими интерфейс IDispatch, и зарегистрированными в операционной системе. Такие объекты называются OLE-серверами (серверами автоматизации) и используются для передачи в них информации. Примерами OLE-серверов являются Microsoft Word или Excel. Данные сервера автоматизации имеют иерархическую объектную структуру, в которой объект верхнего уровня владеет несколькими объектами более низкого уровня. Объекты реализованы таким образом, что отображают некоторый атрибут документа или приложения, и имеют методы для изменения состояния данного атрибута.
553
-•
ЧАСТЬ VI
СОЗДАНИЕ КОМПОНЕНТОВ
22
ОСНОВЫ ИСПОЛЬЗОВАНИЯ КОМПОНЕНТОВ
23
ЖИЗНЕННЫЙ ЦИКЛ КОМПОНЕНТА В СРЕДЕ РАЗРАБОТКИ
24
РАЗРАБОТКА КОМПОНЕНТОВ
octНОВЫ DELPHI. Профессиональный подход
Основы использования компонентов
22.1. Понятие компонента Компонентом в Delphi называется любой наследник класса TComponent, который может быть интегрирован в среду разработки для использования его в визуальном построителе. В этом случае, в результирующий программный продукт автоматически добавляется функциональность, необходимая для создания компонента во время выполнения программы, обращения к нему, и разрушения. Наряду с этим, экземпляр компонента может быть создан непосредственно, в любом месте программы, причем, в зависимости от параметра Owner, переданного в его конструктор, разрушение компонента после работы программы, также может произойти автоматически. Термин «компонент» используется не только в отношении классов-компонентов, но и для указания экземпляра компонента.
22.2. Назначение компонентов 22.2.1. Повторная используемость Компоненты используются для локализации некоторой программной логики внутри одного класса, экземпляры которого можно использовать в различных программных продуктах. Такой подход существенно упрощает разработку, используя ранее реализованные и отлаженные компоненты. Итак, одним из предназначений компонентов является их повторная используемость. 555
Часть VI. Создание компонентов
Заметим, что повторная используемость может быть достигнута и с помощью обычных классов, хранимых в модулях, однако компонент имеет существенное преимущество перед классами — возможность интеграции со средой разработки, что позволяет использовать его в визуальном проектировании. Еще один аспект повторной используемости состоит в том, что компонент может быть унаследован не только от класса TComponent, но и от любого его наследника, в результате чего довольно сложный компонент может быть создан небольшими усилиями.
22.2.2.
Функциональность
Данное назначение компонентов вытекает из их сущности — компоненты являются классами Delphi, поэтому несут в себе функциональность. Однако заметим, что использование компонентов только ради локализации в них функциональности не имеет смысла, если разрабатываемые компоненты используются только один раз и не принимают участия в визуальной разработке.
22.2.3. Унификация программных продуктов Компоненты, расположенные на формах различных приложений являются всего лишь экземплярами класса компонента, хранящегося в среде разработки. В связи с этим, изменения, производимые в классе компонента, незамедлительно отражаются на экземплярах компонентов. Фиксация состояния компонента происходит во время компиляции, то есть изменения в компоненте не могут повлиять на ранее построенное приложение. Для согласования различных программных продуктов, после изменения кода компонента следует заново откомпилировать проекты, в которых компонент используется. Этим и достигается унификация программных продуктов — сохранение общих черт, например, интерфейса, в различных программах за счет использования одного и того же компонента.
22.3. Описание компонента 22.3.1. Реализация модуля компонента Описание компонента производится в отдельном модуле, построенном по обычным правилам Delphi, и представляет собой описание одного или нескольких классов компонентов, а также процедуру регистрации компонента в среде разработчика (листинг 22.1). 556
Глава 22. Основы использования компонентов Листинг 22.1. Модуль компонента unit MyCompUnit; interface uses SysUtils, Classes; type TntbComponent = class(TComponent) , end; - procedure Register;
>
implementation . procedure Register; begin RegisterComponents('My Components', end; • end.
[TntbComponent]);
Описание компонента произведено в интерфейсной части модуля и не добавляет функциональности в родительский класс TComponent: TntbComponent = class(TComponent) end;
Обратим внимание на процедуру регистрации Register, которая является «узнаваемой» для среды разработки и устанавливает компонент или набор компонентов в Палитру. Для указания, какие именно компоненты должны быть инсталлированы, и на какой закладке Палитры компонентов они должны располагаться, в процедуре Register вызывается процедура RegisterComponents, описанная в модуле Classes: procedure RegisterComponents(const Page: s t r i n g ; const C o m p o n e n t C l a s s e s : array of T C o m p o n e n t C l a s s ) ;
В качестве значения параметра Page передается строка, содержащая название закладки Палитры компонентов, на которой следует расположить компонент или группу компонентов. Добавление компонентов возможно на любую существующую закладку. Если же указано название, которое не соответствует ни одной закладке Палитры, то автоматически будет создана новая закладка. В примере при вызове процедуры RegisterComponents указано название закладки «My Components». Так как названия классов устанавливаемых компонентов обычно заранее известно, то для передачи массива ссылок на них можно воспользоваться конструктором массива, как и сделано в рассмотренном примере:
Часть VI. Создание компонентов
R e g i s t e r C o m p o n e n t s ( л М у Components',
[TntbComponent]);
При необходимости установить несколько компонентов на одной странице Палитры, следует воспользоваться конструкцией следующего вида: [TntbComponent, TntbButton, TntbGrid] Если существует необходимость установить несколько компонентов на разные страницы Палитры, то процедуру RegisterComponents можно вызвать несколько раз: procedure Register; begin RegisterComponents('NTB B u t t o n s ' , [TntbButton]); RegisterComponents{'NTB Grids', [TntbGrid, TntbComponent]); end; Инсталляция компонентов возможна только в контексте среды разработчика, которая обладает средством автоматизации создания модулей компонентов, использование которого более предпочтительно по сравнению с созданием модуля вручную и описано ниже.
22.3.2. Использование пакетов компонентов Пакеты компонентов (англ. Package — пакет) используются для объединения компонентов схожей направленности. Например, для хранения набора интерфейсных компонентов можно использовать один пакет, а для объединения компонентов, предназначенных для работы с данными, — другой. Помимо хранения компонентов, пакеты могут выполнять еще одну важную функцию — разделение компонентов между приложениями. Пакеты компонентов имеют структуру динамически компонуемых библиотек DLL и могут быть использованы для разделения программного продукта на несколько частей — собственно приложение, и набор компонентов. В результате такого разделения при компиляции приложения в него не. добавляются описания компонентов, а обращения к ним происходит во время работы программы, что снижает размер исполняемых файлов и позволяет использовать один и тот же пакет компонентов несколькими программными продуктами одновременно. Для создания пакета следует воспользоваться пунктом главного меню File-»New-»Other и в появившемся диалоге выбрать страницу New. В списке объектов необходимо выбрать пункт Package (см. рис. 22.1). Созданный пакет не содержит компонентов, но они могут быть добавлены в него с помощью диалога, аналогичного диалогу управления проектом (см. рис. 22.2). С помощью данного диалога можно добавить модуль компонента в пакет (кнопка Add), удалить компонент из пакета 558
Глава 22. Основы использования компонентов xl
5" New Items Projects
|
Data Modules
Business j : WebSriap j New j ActiveX j Muftitier Name
|
IrtiaWeb ) WebServices Web Documents j Corba
!
Package 1 | Forms | Dialog s
j •.-;.; ; I Oescrption
•»!
нр Frame ЭР' eject Group
d
«•«••П11 Wbwrt
v^j*
<~ Oa:y:
Г ?i:U\! |
±f
f" 11; OK
1
Cancel
j
Help
1
Рис. 22.1. Диалог создания элемента проекта
(кнопка Remove), а также откомпилировать компоненты, входящие в пакет, без их инсталляции в Палитру компонентов (кнопка Compile), либо с последующей инсталляцией (кнопка install) 82 . После того, как пакет сформирован, его следует сохранить командой главного меню File—»Save. Описания пакетов сохраняются в файлы с расширением dpk (от англ. DPK — Delphi PacKage — Пакет Delphi). Описания пакета могут быть включены в группы проектов (пункт главного меню Project-»Add Existing Project) или открыты для индивидуальной работы с ними (пункты главного меню File-»Open или File-»0pen Project). Delphi хранит компоненты в откомпилированном виде. Это касается и компонентов, представленных отдельными модулями (соответственно, такие компоненты хранятся в файлах компилированных модулей с расширением dcu), и компонентов, включенных в пакеты. Пакеты компилируются в файлы с расширениями bpl, соответствующими формату динамических библиотек, и dcp, содержащих описание пакета для линковки пакета с приложением.
Compile j ; Add
Remove 5
Instal
Options
Рис. 22.2. Диалог управления пакетом компонентов
!J
Инсталлируются все компоненты, входящие в пакет.
559
••*
S3
Жизненный цикл компонента в среде разработки
Среда разработки тесно взаимодействует с компонентами во время визуального проектирования приложения, создавая их экземпляры на формах и фреймах с помощью вызова конструкторов, обращаясь к свойствам компонентов при отображении и изменении их в Инспекторе объектов, и, наконец, разрушая экземпляры компонентов при закрытии проектов. Эта часть взаимодействия среды и компонента касается процесса разработки и аналогична работе компонента во время выполнения программы. С другой стороны, среда разработки хранит ссылки на модули компонентов и пакеты, в которых они находятся, представляет компоненты постранично в палитре компонентов и обеспечивает доступ разработчика к ним с целью создания экземпляров. Разработчик может добавлять компоненты в Палитру, перемещать компоненты на разные страницы, а также удалять компоненты из Палитры, в случае необходимости. Взаимодействие среды разработчика и компонентов накладывает некоторые требования на реализацию компонентов. Эти требования рассмотрены немного ниже, а сейчас мы рассмотрим механизм управления компонентом в палитре компонентов среды. V
23.1. Создание компонентов Под созданием компонента в контексте его жизненного цикла в среде разработчика мы подразумеваем создание модулей, необходимых для хранения класса компонента и инсталляции компонента в Палитру компонентов среды разработчика. 560
Глава 23. Жизненный цикл компонента в среде разработки
23.1.1. Создание компонентов вручную Создание компонента вручную рассмотрено выше и основано на создании нового модуля с помощью пункта главного меню File-»New—>Unit, реализация в этом модуле класса компонента и процедуры Register, размещение модуля в пакете компонентов и инсталляция компонента с помощью диалога управления пакетом.
23.1.2. Создание компонентов средствами среды разработчика Второй вариант создания компонента состоит в вызове диалога New Component с помощью пункта главного меню Component-»New Component (см. рис. 23.1.).
New Component j
I Ancestor type: |?WinControl [Controls] ! •] Class Name: JTntbSuperGri^ P Palette Page: ]Му Components ; j Unit file name: jI:\Michael\NT_BOOK_D\Pioarams\Componen J j I Search path; |$(DELPHI)\Lib;$(DELPHI]\Bin;$(DELPHI)4mpot
Install...
1
Cancel
I j
Help
Рис. 23.1. Диалог создания компонента
Данный диалог является частью диалога, вызываемого при нажатии на кнопку Add в диалоге управления пакетом компонентов и позволяет выбрать: 1. компонент, от которого наследуется создаваемый (выпадающий список Ancestor type); 2. название класса нового компонента (строка ввода Class Name); 3. закладку Палитры компонентов, на которой будет размещен создаваемый компонент (строка ввода с выпадающим списком Palette Page); 4. название файла модуля описания компонента (строка ввода Unit f i l e name с возможностью вызова диалога открытия файла); После нажатия на кнопку install или Ok будет создан модуль компонента, аналогичный приведенному выше, причем при нажатии кнопки i n s t a l l , компонент будет установлен в Палитру. Модуль компонента содержит описание класса компонента и процедуры Register. 561
Часть VI. Создание компонентов
Если при работе с диалогом была нажата кнопка Ok, а не i n s t a l l , то компонент не будет добавлен в Палитру83. В дальнейшем, инсталляцию можно выполнить с помощью диалога Install Component (см. рис. 23.2), вызываемого пунктом главного меню Component—^Install Component. ШЩУШШШлННШ Iritb existing package ] Into new
jjnit file name:
100K_0\Ptogfams\Components\ManuaMHyCompLlnH2.pas
Search path:
|${OELPHI)\Lib;$(DELPHIl\Bin;$(DELPHI)VlmporU;$(DELPH№
i Package dlename:
jjtowse...
i
)E:V;talkefMMCE\icePackage.dpk
i: Packagejtescnptiort j
•''..••••!;•:'&'-.
Help
Рис. 23.2. Диалог инсталляции компонента
Диалог i n s t a l l Component предназначен для инсталляции одного или нескольких компонентов, задаваемых файлами модулей, которые можно указать в строке ввода Unit f i l e name. Для облегчения ввода имен файлов можно использовать диалог открытия файла, вызываемый при нажатии кнопки Browse справа от строки ввода84. Для указания, в каком пакете должен находиться компонент используется строка ввода Package f i l e name, причем, если инсталляция компонента производится на странице Into existing package, то следует указать действительно существующий пакет. Если же выбрана страница Into new package, то будет создан новый пакет.
23.2. Управление компонентами Управление компонентами, содержащимися в Палитре компонентов среды разработчика производится с помощью диалога Palette Properties (см. рис. 23.3). Данный диалог вызывается с помощью пункта главного меню Component—»Configure Palette и позволяет выполнять следующие настройки Палитры компонентов: » Добавление страницы (кнопка Add); * Удаление страницы, если она не содержит компонентов (кнопка Delete); " Инсталляция компонента имеет смысл, если он полностью разработан и отлажен, так как, в противном случае, некорректно работающий компонент может вызвать крах среды разработки. Более детально подробности отладки компонентов обсуждаются ниже. ' Возможен выбор нескольких файлов одновременно. 14 Возможен выбор нескольких файлов одновременно. 562
Глава 23. Жизненный цикл компонента в среде разработки _x| Palette i
1
-• •
Eages: Standard
-
Components:
•»
Common Controls ;V :
| Package
Name :
' Вв TTabControt
.
-
dclstd?0
^^^щяятяттнатттттящтвттт
System i Data Access •'"'. Data Controls dbExpfess
i BDE •
DataSnap
Siji TPageControl
dclsld70
(3i Tlmagelist
dclstd70
.';'
"""I
3S Щ \ TRichEdit
ADO
-,-
:.,g| TTiackBai
InterBase :.,;.; WebServices !ет * i TProgtessBar Щ InternetExpress 1 Internet WebSnap ^JTUpDown Dialogs Decision Cube : • ':'-\'\''''' '-''i ] B> : : W W . fi*i,.. Hide
OK
dclstd70 dclsld70 dclstd70 dclrtd70
TJ j'U -•.•:- Up
I
j
Cancel
„^i .-^..^...
MoveDown i
||!
Help
il
1
•-.-.,-.- -А- ^ l^""-"""--""-- -""-^:^
Рис. 23.3. Диалог настройки Палитры компонентов •
.
•
Л»:
» Переименование страницы (кнопка Rename); » Изменение порядка страниц в Палитре путем последовательного перемещения выбранной страницы вверх (кнопка Move Up) или вниз (кнопка Move Down) в списке страниц; * Временное скрытие компонента из Палитры (кнопка Hide).
23.3.Удалениекомпонентов из Палитры компонентов 23.3.1. Использование диалога управления проектом Удаление компонентов из палитры компонентов среды разработчика производится с помощью диалога управления проектом Project Options, вызываемым с помощью пункта главного меню Project->0ptions. Заметим, что этот пункт меню доступен даже при отсутствии открытого на данный момент проекта. На закладке Packages диалога управления проектом (см. рис. 23.4) представлен список (Design Packages - Пакеты для проектирования) пакетов, установленных в данный момент в Палитре компонентов. Для удаления какого-либо пакета из списка его следует выбрать и нажать кнопку Remove, расположенную под списком.
Часть VI. Создание компонентов x| Forms
j
Application
|
Compiler
Directories/Conditionals
}
Compiler Messages
Version Info
" r Design gackages
"
r
I Linker D ackages
|
'."",
] ; Delphi 1.0 Compatibility Components | j e:\program liles\borland\delphi7\Projects\Bpl\icePackage.b| : T'i i InterBase Data Access Components : Internet Direct (Indy) for D7 Property and Component Editors i \ j Internet Explorer Components
mm
i -J j
i i eAprograni files^borland'i^delphi7^Proiects^Bpl^Package1,bpl Add.; Г Ryntime packages
Qemove
С ornponents J |
i;...
~
l Г~ Buiid with runtime packages
:
;b-:'^-
:: Г Default
OK
Cancel
°-
1^ Help
j
Рис. 23.4. Закладка управления пакетами компонентов диалога управления проектом
После подтверждения удаления ссылка на пакет будет утеряна, а все компоненты, входящие в пакет исчезнут из Палитры компонентов. При этом разработка приложений, в которых использованы удаленные компоненты, станет невозможной, так как в файлах описания форм хранятся только названия классов компонентов, а не их код. Обратим внимание на нижнюю часть закладки Packages. Независимый переключатель «Build with runtime packages», указывает на то (во включенном состоянии), что в исполняемый файл приложения не должны включаться пакеты компонентов. Приложение во время выполнения получает описание компонентов динамически из bpl-библиотек. Если переключатель находится в состоянии «не выбрано», то к приложению будут прилинкованы все пакеты компонентов, которые необходимы для его работы, в результате чего будет получен полностью независимый программный продукт. Если же переключатель выбран, то приложение должно иметь доступ к bplбиблиотекам. Также необходим доступ и ко всем библиотекам, которые заявлены как необходимые в библиотеках, перечисленных в списке. Например, все библиотеки используют библиотеку RTL, а те компоненты, которые являются визуальными, должны иметь доступ к библиотеке VCL. Программный продукт, к которому не прилинкованы пакеты компонентов, с которыми он работает, не является полностью самостоятельным, но имеет существенно меньший размер. Таким образом, при использовании одного и того же пакета компонентов несколькими приложения564
Глава 23. Жизненный цикл компонента в среде разработки
ми на одном компьютере, можно достичь экономии дискового пространства за счет разделения пакетов компонентов.
23.3.2. Удаление компонентов с помощью диалога управления пакетами компонентов При использовании диалога управления проектом из Палитры компонентов удаляется весь пакет компонентов. Для выборочного удаления компонентов из пакета и, соответственно, Палитры, следует использовать диалог управления пакетами компонентов (см. выше ). При нажатии на кнопку Remove данного диалога, компонент удаляется из состава пакета, в результате чего при последующей компиляции он будет удален и из Палитры компонентов. Вне зависимости от метода удаления компонентов из Палитры, среда разработчика перестает хранить ссылки на их описания, то есть дальнейшее их использование в визуальном проектировании невозможно. Однако, подключив модуль описания компонента, например, к модулю формы, остается возможность создания экземпляра компонента во время 1&шолнения программы.
.
Разработка компонентов
Итак, мы довольно подробно рассмотрели основы интеграции среды разработчика и компонентов, ознакомились с возможностями управления компонентами внутри IDE, а также рассмотрели, какими способами компоненты могут подключаться к программному продукту. Настоящий раздел посвящен особенностям создания компонентов с точки зрения программирования, то есть реализации классов компонентов. Создание компонентов не совсем относится к прикладному программированию, что в некоторой степени оправдывает сложность этого процесса, обусловленную, в основном, полным отсутствием каких бы то ни было визуальных инструментов, привычных для разработчика Delphi-приложений.
24.1. Соглашение об именовании компонентов и их элементов 24.1.1. Именование компонентов Именование компонентов не отличается от назначения имен любым идентификаторам в программе. Однако предусмотрены некоторые правила, использование которых не обязательно, но рекомендуемо. Итак, компонент является классом, поэтому его название должно начинаться с заглавного символа «т». При этом название компонента, которое разработчик видит в Палитре компонентов, указывается без этого префикса. Например, класс стандартного диалогового компонента открытия файла имеет название TOpenFileDialog, а в Палитре компонентов он отображается как OpenFileDial'og. Поскольку несколько разных компонентов могут иметь одинаковую направленность, то имена компонентов могут пересекаться, что недопус566
Глава 24. Разработка компонентов
тимо. Например, при создании компонента, который представляет собой кнопку, было бы логично назвать его Button, однако в этом случае, его имя войдет в конфликт со стандартным компонентом Button и компонент невозможно будет добавить в Палитру компонентов. Поэтому при создании собственных компонентов рекомендуется снабжать их названия условно-уникальными префиксами. Например, компонент, разработанный фирмой «АМТ», может иметь префикс »amt». Использование префикса не гарантирует уникальность имен, однако в существенной степени повышает ее вероятность.
24.2.2. Именование свойств Компоненты, как и все остальные классы Delphi, могут использовать два вида свойств — обычные и property-свойства. Property-свойства могут иметь различные названия, отражающие их назначение, и, обычно, именуются без использования каких-либо префиксов. Обычные свойства компонентов предназначены для внутреннего использования, и при назначении им имен рекомендуется использовать префикс «f» (от англ. Field — Поле). Использование такого префикса удобно, также, с учетом того, что внутреннее свойство компонента часто является отражением его property-свойства, что неминуемо вызывает конфликт имен внутри класса. В остальном же, именование внутренних полей является личным делом разработчика компонента и определяется здравым смыслом. При создании сложных компонентов с большим количеством свойств и методов, особенно если создаваемые компоненты предназначены для построения на их основе других компонентов, можно применять так называемую венгерскую нотацию, являющуюся стандартом именования идентификаторов в Windows. Однако заметим, что разработчиками Delphi, например, данное правило не используется.
24.2. Жизненный цикл компонента в программе Вне зависимости от метода создания компонента во время выполнения программы, компонент является классом Delphi и проходит обычные для экземпляров класса стадии жизненного цикла: 1. Создание экземпляра с помощью вызова конструктора; 2. Использование свойств (установка значений) и методов (вызов), производимое по ссылке на экземпляр компонента; 3. Разрушение с помощью вызова метода Free и, далее, деструктора. 567
Часть VI. Создание компонентов Однако при разработке компонента, который будет в дальнейшем помещен на форму или фрейм в визуальном построителе, в жизненном цикле его экземпляра могут присутствовать некоторые особенности. С другой стороны, если компонент создается в программном коде, то к создавшему его модулю также могут предъявляться специфические требования.
24.2.1. Компоненты, созданные в визуальном построителе При добавлении компонента на форму в визуальном построителе описание компонента заносится, затем, в файл формы. Для каждого компонента указывается принадлежность к классу, а также запоминаются значения ргореЛу-сзойств и, возможно, простых свойств, в зависимости от внутренней реализации компонента. При инициализации формы каждый компонент, находящийся в ее описании, создается, и его свойства автоматически настраиваются. Этим и достигается удобство визуальной разработки, позволяющей не просто создать компонент, но привести его к виду, который он имел в процессе визуального построения формы. Более того, так как property-свойство может иметь любой тип данных, в частности являться ссылкой на другой компонент, то возможно визуальное построение не только интерфейса, но частичной логики приложения. При автоматическом создании компонентов на основе файла описания формы, в конструкторы создаваемых компонентов передается ссылка на форму, которой они принадлежат. Соответственно, разрушение компонентов будет также произведено автоматически, при разрушении формы-владельца.
24.2.2. Компоненты, созданные программно Компоненты, которые создаются внутри программного кода во время выполнения программы, ничем не отличаются от компонентов, созданных автоматически, однако, в зависимости от параметра, переданного в конструктор (ссылки на владельца), может быть необходимо прямое разрушение компонента методом Free. Этот вопрос подробно освещен ранее, поэтому в настоящем разделе не рассматривается.
24.3. Структура компонента Компонент имеет точно такую же структуру, как и любой класс в Delphi, то есть имеет конструктор, деструктор, свойства, методы и property-свойства. В дополнение к этому, компонент обладает всеми возможностями, реализованными в классе TComponent, так как является его наследником. Рассмотрим особенности структуры компонентов. 568
Глава 24. Разработка компонентов
23.3.1. Конструктор и деструктор Как и экземпляры других классов, компоненты разрушаются методом Free (за исключением форм, которые разрушаются методом Release). Перед реальным разрушением компонента разрушаются все его дочерние компоненты. Особенности разрушения компонентов были рассмотрены при обсуждении архитектуры компонентов Delphi в соответствующем разделе. Компонент предназначен для использования в среде разработки, которая создает во время проектирования его экземпляр и может вызывать только один, известный ей, конструктор: constructor Create(AOwner: TComponent); virtual; Заметим, что конструктор является виртуальным, что определяется особенностями регистрации компонентов в Палитре компонентов. При инсталляции компонента процедуре RegisterComponents передается массив метаклассов — ссылок на объекты, которые являются описаниями классов: procedure
RegisterComponents(const Page: s t r i n g ; const ComponentClasses: array of TComponentClass) ;
Метакласс предоставляет возможность вызова конструктора класса, который он описывает, чем и пользуется среда разработки Delphi при создании экземпляров компонентов во время визуального построения приложения. Поясним механизм использования метаклассов на примере. Откроем новый проект и добавим на главную форму две кнопки, например, типа Button. В описательной части модуля заявим метакласс с помощью следующего описания: TButtonMetaClass = class of TButton;
Переменные-ссылки такого типа будут иметь методы, описанные в классе TObject и конструктор Create, свойственный классу, на основе.которого создается метакласс. Вызов этого конструктора приведет к созданию компонента TButton или его наследника, в зависимости от того, какой именно класс присвоен данной ссылке. Реализуем функцию, получающую в качестве параметра метакласс, создающую экземпляр компонента и возвращающую его в качестве значения: Function
TForml.CreateButton(ButtToCreate: TButtonMetaClass) :
TButton;
begin Result := ButtToCreate.Create(self) ; Result.Parent := Self; i 20 Зак 867
569
--Г
Часть VI. Создание компонентов end;
Итак, из ссылки типа TButtonMetaClass вызывается конструктор того класса, который был передан в качестве параметра. Например, возможен вызов данной функции с параметром TButton: var Button: Button
TButton;
:= CreateButton(TButton);
Но также возможна и передача наследника класса TButton, например, TBitBtn: var Button: TButton; Button := CreateButton(TBitBtn)
В этом случае функцией CreateButton будет создан экземпляр класса TBitBtn, ссылка на который будет также возвращена в качестве значения. Реализуем обработчики нажатия на кнопки Button 1 и Button2 разрабатываемой формы таким образом, чтобы при нажатии кнопки Button 1 в произвольном месте формы создавался компонент типа Button, а при нажатии на кнопку Button2 - компонент TBitBtn. Модуль главной формы приложения будет иметь следующий вид, приведенный в листинге 24.1. Листинг 24.1. Модуль главной формы unit Unitl; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Buttons; type TButtonMetaClass = class of TButton; TForml = class(TForm) Buttonl: TButton; Button2: TButton; 570
Глава 24. Разработка компонентов procedure ButtonlClick(Sender: procedure Button2Click(Sender: private
TObject); TObject);
Function CreateButton(ButtToCreate: TButtonMetaClass): TButton; end; var Forml: TForml; implementation {$R *.dfm} Function TForml.CreateButton(ButtToCreate: TButtonMetaClass): TButton; Var tmpButt: TButton; begin tmpButt := ButtToCreate.Create(self); tmpButt.Parent := Self; tmpButt.Width := 30 + Random(lOO); tmpButt.Height := 10 + Random(30); tmpButt.Left := Random(ClientWidth - tmpButt.Width); tmpButt.Top := Random(ClientHeight — tmpButt.Height); Result := tmpButt; end; procedure TForml.ButtonlClick(Sender: TObject); Va* tmpButt: TButton; begin tmpButt := CreateButton(TButton); tmpButt.Caption := ''TButton' ; end; procedure TForml.Button2Click(Sender: TObject); Var tmpButt: TButton; begin tmpButt := CreateButton(TBitBtn); tmpButt.Caption := "TBitBtn'; end; end.
В заключение отметим, что раз конструктор является виртуальным, то при его переопределении следует указывать ключевое слово override, а первой командой в переопределенном конструкторе должен быть вызов его родительской версии с помощью ключевого слова Inherited. 571
Часть VI. Создание компонентов
Описание конструктора должно находиться в секции public компонента, так как его вызов производится из-за пределов модуля с его описанием: Туре TntbButton = class(TBitBtn)
public constructor Create(AOwner: TComponent); overrride; end;
constructor TntbButton.Create(AOwner: TComponent); begin Inherited; end;
24.3.2. Простые свойства Простые свойства компонентов обладают всего одной существенной особенностью — они могут быть записаны в файл описания формы или фрейма с помощью метода DefineProperties, описанным в классе TPersistent: 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;
Переопределив данный метод, можно также записать в файл формы какуюлибо другую информацию, не имеющую свойства автоматического сохранения, например property-свойства, являющиеся массивами. Рассмотрение таких возможностей выходит за рамки нашего издания, поэтому для более глубокого их изучения следует обратиться к справочной системе Delphi.
• •
24.3.3.
Property-свойства
Property-свойства классов Delphi довольно разнообразны и имеют следующий формат описания: property <Название>[Index: <Тип данных>]: <Тип> index <Целочисленная константа> read <Метод чтения> write <Метод записи> stored <Логическая константа> default Оначение по умолчанию> или nodefault; default; 572
Глава 24. Разработка компонентов
Модификаторы Read и Write Модификаторы Read и Write уже были описаны выше. Напомним, что они определяют, соответственно, методы чтения или сохранения значения при обращении к property-свойству, причем вместо указания метода может быть использовано название внутреннего поля, подходящего property-свойству по типу. Если для получения или установки значения property-свойству используются методы, то они должны являться процедурами (для установки значения), принимающими параметр соответствующего типа, или функциями (для получения значения), возвращающими значение соответствующего типа: Туре TntbButton = class(TBitBtn) protected fBorderColor: TColor; Procedure SetBorderColor(NewBorderColor: TColor) published property BorderColor: SetBorderColor;
TColor
read
fBorderColor write
end;
В приведенном примере значение, возвращаемое property-свойством BorderColor, получается напрямую из простого свойства fBorderColor, тогда как его установка производится с помощью protected-процедуры SetBorderColor.
Заметим, что для подобных процедур установки существует некоторый шаблон, основанный на предварительной проверке нового значения на его равенство старому. Необходимость таких действий связана с частым наличием ресурсоемких операций, выпЪлняемых при изменении свойств. Например, при установке свойства BorderColor, как видно из его названия (цвет окантовки), необходимо перерисовать компонент. Однако этого не следует делать, если новый цвет не отличается от старого: Туре TntbButton = class(TBitBtn) protected fBorderColor:
TColor;
573
Часть VI. Создание компонентов Procedure SetBorderColor(NewBorderColor: TColor)
published property BorderColor: TColor read fBorderColor write SetBorderColor ; end; Procedure TntbButton.SetBorderColor(NewBorderColor: TColor); begin (Если новый цвет отличается от т е к у щ е г о . . . } If NewBorderColor <> fBorderColor Then begin { . . . тогда запомним его во внутреннем с в о й с т в е . . . } fBorderColor := NewBorderColor; { . . . и перерисуем компонент) Invalidate; end; end;
Методы, используемые для получения и изменения значений propertyсвойств, не могут быть виртуальными и динамическими. Модификатор Index Модификатор property-свойства index предназначен для объединения методов доступа (чтения и установки) к значениям нескольких однотипных property-свойств. После модификатора указывается целочисленное значение, которое является, фактически номером свойства. Методы доступа модифицируются добавлением параметра типа integer, в котором передается номер этого свойства. При обращении к свойству по имени автоматически вызывается метод чтения или записи, которому передается индекс свойства. Этот индекс анализируется методом доступа для выполнения соответствующих действий. Представим себе компонент TntbSDPoint, предназначенный для хранения координат точки в трехмерном пространстве. Такой компонент должен иметь три property-свойства — х, Y и z ддя установки и получения координат. Для каждого из них можно создать внутреннее свойство, например, fx, f y и f z , и реализовать отдельные методы чтения и записи. Однако с помощью индексации свойств можно хранить информацию о положении точки в массиве из трех элементов, а методы чтения и записи объединить (см. листинг 24.2): 574
Глава 24. Разработка компонентов Листинг 24.2. Использование индексации TntbSDPoint = class(TComponent) protected fPointCoordinates : A r r a y [ 1 . . 3 ] of Double; Procedure SetPointCoordinate(Index: Integer; Value: Double) Function GetPointCoordinate(Index: Integer): Double; published property X: Double index 1 read GetPointCoordinate write SetPointCoordinate; property Y: Double index 2 read GetPointCoordinate write SetPointCoordinate; property Z: Double index 3 read GetPointCoordinate write SetPointCoordinate;
end; Procedure TntbSDPoint.SetPointCoordinate(Index: Integer; Value: Double); begin {Установка координаты точки по индексу} fPointCoordinates[Index] := Value; end; Function TntbSDPoint.GetPointCoordinate(Index: Integer): Double; begin {Выдача координаты точки по индексу} Result := fPointCoordinates[Index]; end;
В приведенном примере property-свойства x, Y и z проиндексированы значениями, соответственно, 1, 2 и 3. Для хранения координат используется внутреннее свойство-массив fPointCoordinates, в котором последовательно хранятся координаты по осям x, Y и Z. Таким образом, индексы property-свойств x, Y и z соответствуют индексам в массиве fPointCoordinates, что существенно упрощает реализацию методов доступа. Property-массивы
Для доступа к массивам информации используются property-свойства, тип данных которых аналогичен описаниям массивов. Методы доступа в этом 575
Часть VI. Создание компонентов
случае модифицируются дополнительным параметром, определяющим номер элемента в массиве, к которому производится обращение. Изменим предыдущий пример так, чтобы координаты можно было получать не только по отдельности через property-свойства х, Y и Z, но и через дополнительное property-свойство Coords, которое будет являться отражением внутреннего массива fPointCoordinates (см. листинг 2 4 . 3 ) . Листинг 24.3. Property-массивы Tntb3DPoint = class (TComponent) protected Procedure SetCoordinates (Index: Integer; Value: Double); Function GetCoordinates (Index: Integer): Double; public {Описание property-массива} property Coords[Index: Integer]: Double read GetCoordinates write SetCoordinates; published
end; implementation Procedure TntbSDPoint.SetCoordinates(Index: Integer; Value: Double); begin fPoint[Index] := Value; end; Function TntbSDPoint.GetCoordinates(Index: Integer): Double; begin Result : = fPoint[Index]; end;
Property-свойство Coords описано в секции public, а не published, так как доступ к нему из Инспектора объектов невозможен в силу нестандартности типа данных, использованного при описании этого свойства. Заметим, что пары методов SetPointCoordinate, GetPointCoordinate, использованные для работы с вещественными свойствами х, Y и z, и SetCoordinates, GetCoordinates не просто имеют одинаковую сигна576
Глава 24. Разработка компонентов туру, но и выполняют абсолютно одинаковые действия. Этот факт позволяет не вводить новые методы для доступа к свойствам-массивам, а использовать уже описанные SetPointCoordinate и GetPointCoordinate: TntbSDPoint = class(TComponent) protected fPointCoordinates: A r r a y [ 1 . . 3 ] of Double; Procedure SetPointCoordinate(Index: Integer; Value: Double); Function GetPointCoordinate(Index: Integer): Double; public property Coords[Index: Integer]: Double read GetPointCoordinate write SetPointCoordinate; published property X: Double index 1 read GetPointCoordinate write SetPointCoordinate; property Y: Double index 2 read GetPointCoordinate write SetPointCoordinate; property Z: Double index 3 read GetPointCoordinate write SetPointCoordinate; end;
Procedure TntbSDPoint.SetPointCoordinate(Index: Integer; Value: Double); begin {Установка координаты точки по индексу} fPointCoordinates [Index] := Value; end;
Function TntbSDPoint.GetPointCoordinate(Index: Integer): Double; begin {Выдача координаты точки по индексу} Result := fPointCoordinates [Index]; end;
Таким образом, совместное использование property-массивов и индексированных property-свойств позволяет создавать компоненты с гибким программным интерфейсом к внутренним свойствам при минимальных затратах труда.
577
Часть VI. Создание компонентов
Модификаторы stored и default Модификаторы stored, default и nodefault настраивают среду разработчика в отношении сохранения property-свойств и указывают, следует ли сохранять значение property-свойства, и в каких случаях. Если после ключевого слова stored указано логическое значение False, то такое свойство не сохраняется в файле формы, то есть теряется при закрытии проекта. Применение свойств такого рода имеет смысл, если значение свойства не устанавливается вызывающей программой, а рассчитывается на основе других свойств, либо может быть рассчитано. Добавим, например, в компонент Tntfc>3DPoint property-свойства Xview, Y v i e w и Zview, определяющие положение точки взгляда в трехмерном пространстве для проецирования трехмерной точки, хранимой в компоненте на некоторую плоскость. Для этих property-свойств следует указать методы доступа и обеспечить хранение заносимых значений, например во внутренних свойствах f x v i e w , f Y v i e w и f Z v i e w . Сохранение этих данных в модуле формы не имеет значения, так как они существенно зависят от хода выполнения программы, и могут быть установлены в начале ее работы. Для отключения сохранения, к описанию свойств следует применить модификатор stored с указанием значения False: property Xview: Double read fXview write fXview stored False; property Yview: Double read fYview write fYview stored False; property Zview: Double read fZview write fZview stored False; Модификатор default рассматривается только, если в описании propertyсвойства используется модификатор stored с указанием значения True, либо модификатор stored не используется вообще, что эквивалентно указанию «stored True». После ключевого слова default указывается значение «по умолчанию», которое применяется к описываемому свойству. Если в момент записи компонента в файл описания формы, его значение совпадает со значением по умолчанию, то значение свойства не записывается. Можно изменить описания свойств х, Y и z таким образом, чтобы в файл формы не записывались нулевые значения, и так устанавливаемые при создании экземпляра компонента. Такой подход позволит ускорить инициализацию компонента и уменьшить файл формы: property X: Double index 1 read GetPointCoordinate write SetPointCoordinate default 0; Однако приведенное описание некорректно, так как значение по умолчанию может использоваться только для свойств так называемых порядковых (англ. Ordinal — порядковый) типов данных, например, integer. Отметим интересную особенность модификатора default. Дело в том, что данный модификатор управляет только сохранением значения свой-
578
Глава 24. Разработка компонентов
ства, но не влияет на чтение этого значения. Таким образом, если при сохранении компонента в файл описания формы, значение какого-либо свойства не сохраняется, так как это значение эквивалентно значению по умолчанию, то при чтении компонента, значение данного свойства не устанавливается автоматически вообще. Соответственно, все свойства, помеченные модификатором d e f a u l t , должны получать значения в конструкторе компонента, либо следует использовать в качестве значений по умолчанию нулевые значения, присваиваемые всем свойствам при создании экземпляра класса. TntbSDPoint = class(TComponent) protected fColor: Tcolor; public Constructor Create(aOwner: TComponent); override; {Propety-свойство описывается со значением по умолчанию, которое не будет записываться в файл формы (clRed)} property Color: TColor read fColor write fColor default clRed; Constructor Create(aOwner: TComponent); begin Inherited; {Так как значение по умолчанию свойства Color отлично от clBlack (нулевое значение для цвета), то в конструкторе следует присвоить ему начальное значение clRed, заявленное как значение по умолчанию в описании property-свойства Color} fColor := clRed; end; Если свойство Color во время сохранения будет иметь значение clRed, то оно не попадет в файл формы. При последующем чтении компонента из файла формы, значение свойства Color устанавливаться автоматически не будет, и значением данного свойства будет являться значение, присвоенное ему в конструкторе. Если же при сохранении значение свойства Color будет отличаться от clRed, то такое значение будет записано в файл формы. При чтении компонента, значение данного свойства будет сначала установлено в конструкторе в clRed, а потом ему будет присвоено значение, прочитанное из файла формы. 579
Часть VI. Создание компонентов
Для property-массивов модификатор, default имеет совсем другой смысл, и позволяет обратиться к свойству-массиву без указания его имени. Например, пометим этим модификатором свойство Coords компонента Tntb3DPoint следующим образом: property C o o r d s [ I n d e x : I n t e g e r ] : Double read G e t P o i n t C o o r d i n a t e wri,te S e t P o i n t C o o r d i n a t e ; default;
Тогда обращение к свойству Coords может быть произведено так: Var PointSd: Point3d[l]
TntbSDPoint; := 2 8 . 1 3 ;
Отметим, что в данном случае перед модификатором default ставится разделитель — точка с запятой. Переопределение property-свойств. Модификатор nodefault В процессе построения иерархии компонентов в пакете могут изменяться требования к функциональности свойств. Так, подавляющее большинство стандартных визуальных компонентов наследуется не прямо от классов TWinControl или TGraphicControl, а через вспомогательный класс, имеющий, обычно в своем названии префикс Custom. Например, класс TGroupBox строится на основе класса TCustomGroupBox, в котором и реализована вся функциональность этого компонента. Однако в классе TCustomGroupBox нет property-свойств, описанных в секции published. Более того, данный класс вообще не содержит property-свойств. Все свойства, доступные компоненту TGroupBox описаны еще в классе T W i n C o n t r o l , но находятся, в основном, в секции protected. Для того чтобы расширить область видимости свойств в Delphi предусмотрен механизм переопределения property-свойств. Каждый класс может перенести property-свойство в область published или public, указав его название после ключевого слова p r o p e r t y без каких-либо дополнительных описаний. Например, класс TGroupBox реализован в модуле stdctrls следующим образом: TGroupBox = class(TCustomGroupBox) published property Align; property Anchors; property BiDiMode;
580
Глава 24. Разработка компонентов property property property property property property
Caption; Color; Constraints; Ctl3D; DockSite; DragCursor;
end ;
Изменение типов свойств, методов доступа и других атрибутов при переопределении невозможно, однако, если в описании родительского класса property-свойство было описано с использованием значения по умолчанию, то такое поведение можно отменить, указав после описания свойства модификатор nodefault: property
Color
nodefault;
24.3.4. Сообщения и события в компонентах Модель сообщений в компонентах При описании класса TObject было указано, что объекты Delphi поддерживают обработку сообщений. Модель сообщений в компонентах является отражением системы сообщений в Windows, соответственно, используется аналогичный формат сообщения, включающий в себя код сообщения, идентифицирующий его, и вспомогательную информацию. Появление сообщения в контексте какого-либо объекта вызывает поиск его обработчика -- метода, построенного по определенным правилам. Если такой метод не найден, то сообщение считается необработанным и вызывается обработчик, принятый по умолчанию. Большинство сообщений инициируется операционной системой для компонентов, которые являются оконными. Сообщения передаются в оконные функции компонентов для обработки, после чего вызывается метод Dispatch, описанный в классе TObject, и предназначенный для диспетчеризации сообщения — поиска для него метода-обработчика. Понятием «событие» описывается факт появления сообщения в компоненте, поэтому, термины «событие» и «сообщение» отчасти взаимозаменяемы. Однако в Delphi событием (англ. Event — событие) описывается обычно ссылка на некоторый метод объекта, реагирующий на сообщение. Например, событие Onclick кнопки Button - это свойство процедурного типа TNotifyEvent, в которое может быть занесена ссылка на метод, что и делается с помощью Инспектора объектов во время визуальной разработки приложения. 581
, Часть VI, Создание компонентов
Сообщения Windows Для обработки события в компонентах организуются методы, помеченные специальной директивой message, после которой указывается идентификатор сообщения — целочисленное, значение в интервале от 1 до 49151: Оаголовок метода>; message <Идентификатор сообщения>;
Для удобства в Delphi описаны константы, -соответствующие некоторым сообщениям Windows, например константа WM_PAINT используется для идентификации сообщения о необходимости перерисовки окна и имеет шестнадцатеричное значение $OOOF. Список констант можно найти в стандартном модуле Messages. Метод, описываемый с использованием модификатора message, должен являться процедурой и иметь один параметр-переменную типа TMessage. Также, методы-обработчики событий не должны указывать модификатор override при переопределении родительского метода, однако могут использовать вызов родительской версии метода с помощью ключевого слова Inherited (см. листинг 24.4). Листинг 24.4. Вызов родительской версии метода TntbPanel = class(TCustomPanel) protected Procedure ntbPaint(var Message: TMessage) ; message WM PAINT;
end; Procedure TntbPanel.ntbPaint(var Message: TMessage); begin {Установка ц в е т а } Color := clWhite; {Вызов родительской версии метода} Inherited; end;
События компонентов Сообщения, которые имеют префикс WM, относятся к сообщениям Windows (от англ. WM — Windows Message — Сообщение Windows), и появляются в методе Dispatch компонента по инициативе операцией582
Глава 24. Разработка компонентов
ной системы через оконную функцию компонента. Однако в Delphi существует целый пласт легковесных визуальных компонентов, которые не имеют окна, но должны вести себя так же, как и обычные компоненты, например, реагировать на перемещение мыши в их пределах. Для унификации модели сообщений для разных видов компонентов и реализован метод Dispatch, который не зависит от системного механизма рассылки сообщений и может быть использован для внутрипрограммного обмена сообщениями между компонентами. Более того, сообщения, передаваемые компонентами друг другу, не накладывают ограничений на сами компоненты, поэтому возможна передача сообщений не только между визуальными компонентами. Напомним, что метод Dispatch реализован в классе TObject, следовательно, доступен всем объектам. Еще одно удобство в модели обмена сообщениями Delphi состоит в отсутствии необходимости обработки всех сообщений, передаваемых в метод Dispach, что просто невозможно. Таким образом, компоненты обрабатывают только те сообщения, которые необходимы им для реализации собственной функциональности. Описанный механизм используется для рассылки сообщений оконными компонентами-контейнерами легковесным компонентам, которые вставлены в них. Так, например, рассылаются события входа и выхода указателя мыши в пределы компонента cm_MouseEnter и cm_MouseLeave. Далее, эти события могут быть обработаны компонентом для выполнения каких-либо специфических действий при входе и выходе мыши. В качестве примера приведем компонент TntbButton, который является наследником компонента TSpeedButton, и меняет цвет подписи при входе мыши в пределы компонента (см. листинг 24.5). Для этого определим методы-обработчики данных сообщений, в которых будем изменять подсвойство Color свойства Font, что автоматически вызывает перерисовку компонента. Листинг 24.5. Компонет TntbButton
TntbButton = class(TSpeedButton) protected {Заголовок метода-обработчика сообщения cm_MouseEnter} Procedure MouseComeln(var Message: TMessage); message cm_MouseEnter; {Заголовок метода-обработчика сообщения cm_MouseLeave} Procedure MouseComeOut(var Message: TMessage); message cm_MouseLeave;
583
Часть VI. Создание компонентов
end; {Реализация метода-обработчика сообщения cm_MouseEnter} Procedure TntbButton.MouseComeIn(var Message: TMessage); begin {Устанавливаем шрифту подписи красный цвет} Font.Color := clRed; end; {Реализация метода-обработчика сообщения cm_MouseLeave} Procedure TntbButton.MouseComeOut(var Message: T M e s s a g e ) ; begin {Устанавливаем шрифту подписи черный цвет} Font.Color := clBlack; end;
Отметим, что константы, идентифицирующие сообщения компонентов, имеют префикс см (от англ. СМ — Component message — Сробщение компонента) и описаны в модуле Controls. :
'
:
-
i
Делегирование событий При создании компонентов, часто бывает заранее известно, каким именно образом должно быть обработано то или иное сообщение. Например, при получении сообщения WM_PAINT многие компоненты автоматически перерисовывают свое изображение, а некоторые, как, например, формы могут делегировать выполнение необходимых действий с помощью организации события. Некоторые сообщения вообще не могут быть обработаны автоматически, например, обработчик сообщения нажатия на кнопку полностью зависит от контекста программы, использующей компонент, и, фактически, не имеет к компоненту никакого отношения, за исключением того, что компонент должен делегировать событие. Так как обычно обработчики событий являются методами формы, то при описании свойства-события используется конструкция of Object, например: Туре TEventProcedure = Procedure
(Sender:
property OnEvent: TEventProcedure;
584
TObject)
of Object;
Глава 24. Разработка компонентов
Если описание такого свойства OnEvent поместить в секцию published, то во время визуального построения оно будет отображено на странице Events Инспектора объектов, и при двойном щелчке мышью напротив него в текущей форме или фрейме будет создаваться метод-обработчик. Использование событийных свойств основывается на проверке, установлен ли обработчик событию, и вызове метода-обработчика по ссылке, хранимой в свойстве: If
OnEvent
О
nil
Then
OnEvent(Self);
24.4. Основы отладки компонентов Отладка компонентов происходит в интегрированной среде разработчика по обычным правилам, но является намного более сложной. При создании компонента во время визуального проектирования, Delphi создает экземпляр компонента и располагает его на форме или фрейме. Далее, с помощью обращения к методам, возвращающим RTTI-информацию, визуальный построитель считывает список свойств и событий, описанных.в секции published и заносит их в Инспектор компонентов. Инспектор компонентов обращается к экземпляру компонента и получает значения всех необходимых свойств, так же, как это происходило бы при создании компонента из программного кода во время выполнения программы. Если метод, возвращающий значение property-свойства реализован некорректно, и вызывает исключительную ситуацию, то во время выполнения программы, это исключение обрабатывается программой, так как именно программа работает с экземпляром компонента. Однако при работе с визуальным построителем, обращение к propertyсвойству производится Инспектором объектов, который является частью среды разработки, следовательно исключительная ситуация обрабатывается средой. Представим себе метод, возвращающий значение property-свойства ScaleFactor на основе деления других свойств друг на друга: Function T n t b B u t t o n . G e t S c a l e F a c t o r : begin Result := R o u n d ( f A / fB) ; end;
Integer;
585
Часть VI. Создание компонентов
Приведенный метод может работать корректно, пока свойство f в не примет нулевое значение. Тогда при попытке обращения к property-свойству ScaleFactor будет вызван метод GetScaleFactor, в котором возникнет исключение — ошибка деления на ноль. Сообщение об исключении будет выдано разработчику, после чего Инспектор объектов обратится к property-свойству ScaleFactor еще раз. При возникновении повторной исключительной ситуации опять будет выдано сообщение, и так далее, пока свойство fB не примет корректное значение. Поскольку в компонентах редко используются свойства с самопроизвольно изменяющимися значениями, то сообщение об ошибке будет выводиться непрерывно. Данное сообщение отображается в модальном диалоге, который запрещает использование интерфейсных элементов, пока сам присутствует на экране. Таким образом, разработчик теряет возможность сохранения проекта и выхода из среды разработки каким-либо штатным способом. Более того, даже при повторном открытии Delphi после принудительного закрытия, например, через Диспетчер задач Windows, в зависимости от настроек среды, редактируемый проект может быть автоматически открыт. Если при этом некорректный компонент попадет в фокус, будет произведено чтение свойства через метод, вызывающий исключение, и работа с Delphi станет невозможной вообще. Таким образом, визуальный построитель Delphi не может являться средством отладки компонентов. Каждый компонент должен разрабатываться отдельно, отлаживаться с помощью создания его во время работы какого-либо вспомогательного приложения, и уже после этого инсталлироваться в Палитру компонентов интегрированной среды. И даже использование этого правила не снимает с разработчика обязанности предусматривать возможные исключительные ситуации и заключать критичные в смысле возможности появления ошибок фрагменты кода в защищенные блоки типа Try. .Except. .End. Вообще, все методы, которые вьщают значения property-свойств или устанавливают их можно заключать в защищенные блоки, тогда как методы, реализуемые в классах могут возбуждать исключительные ситуации, в том числе-и целенаправленно, для извещения вызывающих фрагментов о некорректном использовании экземпляра класса.
586
Глава 24. Разработка компонентов
24.5. Пример разработки компонента Постановка задачи Требуется модифицировать компонент SpeedButton таким образом, чтобы при наведении на него мыши изменялся цвет подписи кнопки. Цвета, используемые для активного (когда мышь находится над компонентом) и неактивного (мышь находится за пределами компонента) состояния кнопки следует задавать property-свойствами, доступными при визуальной разработке через Инспектор объектов. Создание группы проекта Для разработки необходимого компонента создадим группу проектов, состоящую из пакета компонентов, в котором находится разрабатываемый компонент, и тестового приложения, во время выполнения которого создается экземпляр компонента. Для создания группы проекта откроем новое приложение с помощью пункта главного меню File-»New->Application. Вне зависимости от состояния среды разработчика на момент запуска будет создана группа проектов, в которой содержится один проект с названием по умолчанию: «Projectl.exe» (см. рис. 24.1). Мы будем использовать его в качестве тестового приложения.
Piojecl1.exe
EAProgramFiles\Borland\Delphi7\Projects EAProgiam Files\Borland\Delphi7\Projects
Рис. 24.1. Создана группа проектов и тестовое приложение
Далее, добавим в группу проектов пакет компонентов с помощью пункта Project—>Add New Project главного меню или кнопки New диалога управления группой проектов. Вне зависимости от способа добавления нового проекта на экране появится диалог New Items, на закладке New которого следует выбрать пункт «Package» (рис. 21.2). В группу проектов будет добавлен пакет компонентов (см. рис. 24.3). Если он не является активным, то выделим его двойным щелчком мыши. По умолчанию пакету компонентов присвоено название Packagel.bpl. Сохраним группу проектов со всеми добавленными в нее проектами с помощью пункта главного меню File-»Save All. При этом на экран будет выведена последовательность диалогов сохранения файлов для каж1
587
1
Часть VI. Создание компонентов % New Item* Data Modules New
IntraWeb | Cotba | WebSeivices j WebSnaj
[ActiveX | Multitier (Package! | Forms | Dialogs
Name:
f
Business Projects >.)
| Description
Control Panel Applicati... Creates a new Control Panel Applet Application DLL Wizard
Э Project Group ?j§ Resource DLL Wizard Г
f
Г IniWiii
Resource DLL Wizard
J
Г OK
~j
Help : "••;
Cancel
Рис. 24.2. Добавление в группу проектов пакета компонентов
,j • i
j Packagel. Ьр| Files
^J
/^
gi
— ' ,J : New ':••.-••• V:^.;:v
"
i^Path.;..^
BiH^l Proiectl.exe 1-9 Packagel. bpl
L
..;
;
;„. j
EAProgramFiles\Borland\Delphi7\Projects EAPtogramFiles\Borland\Delphi7\Proiecls E:\ProgiamFiles\Borland\Delphi7\Projects
Рис. 24.3. Б группу проектов добавлен пакет компонентов
дого элемента проекта. Не будем изменять названия файлов, предлагаемые по умолчанию, и сохраним все файлы в один и тот же каталог. После выполнения процедуры сохранения в диалоге управления группой проектов будут отображены названия каталогов, в которых хранятся файлы элементов проекта (см. рис. 24.4). Project Manager С New
I Package!, bpl Ffes
X Remove
Path | ProjectGroupl •i jp Praject1.exe ii: Щ Unit! 1---Й) Unit! .pas Д Form! 3 & ШИЯМЯ
EAMichael\NT_BOOK_D\Programs(iComponents\Example EЛMichae^NT_BOOK_D\Plograms\Components\Example E:\Michael\NT_BODK_D\Programs\Components\Example E:\Michael\NT_BOOK_D\ProgramsComponents\Example E:\Michael\NT_BOOK_D\Programs\Componenls\Example E:\Michael\NT_BOOK_D\Programs\Components\Example
Рис. 24.4. Группа проектов после сохранения
С помощью пункта главного меню Component->New Component выведем на экран диалог создания компонентов New Component. В поле ввода Ancestor Туре введем название базового класса для нашего компонента: «TSpeedButton». В качестве названия класса в поле ввода Class Name наберем: «TntbButton». В качестве названия закладки Палитры компонентов в поле ввода Palette Page введем строку «NTB Components». С 588
Глава 24. Разработка компонентов
помощью кнопки, расположенной справа от поля ввода Unit f i l e name, вызовем диалог сохранения файла и укажем имя файла модуля компонента. При этом лучше указывать имя файла, который еще не существует. Каталог, куда будет помещен файл, лучше задать тот, который был использован при сохранении группы проектов (рис. 24.5). New Component New Component
Ancestor type: JTSpeedButton [Buttons] Class Name:
JTnlbBullon
Palette Page: JNTB Components
.
Unit file name^ KProgtamsSComponentsVExampleVitbButlon.pas
,..J
Search path: |$(DELPHI)\Lib;$(DELPHI)\Bin;$(DELPHI)4mpoi
...|
Install...
Рис. 24.5. Создание нового компонента
Далее, нажатием на кнопку Ok создадим компонент. Заметим, что мы не используем кнопку install, так как это приведет к инсталляции компонента, что нежелательно. Проверим, в какой из проектов группы помещен новый компонент. Название файла с его описанием должно отображаться в дереве проекта Packagel .bpl. Если это не так, то компонент следует удалить с помощью кнопки Remove диалога, сделать активным проект Packagel .bpl, и создать компонент заново. В диалоге управления группой проектов сделаем активным проект P r o j e c t l , дважды щелкнув по нему мышью. Перейдем в модуль главной формы этого проекта (Unitl) и добавим ссылку на модуль компонента в список подключаемых модулей интерфейсной секции. Если при указании названия модуля компонента имя, предложенное по умолчанию, не изменялось, то модуль компонента называется ntbButton: unit U n i t l ; interface uses W i n d o w s , Messages, S y s U t i l s , V a r i a n t s , C l a s s e s , Graphics, C o n t r o l s , Forms, Dialogs, ntbButton; j. .
Сохраним внесенные изменения командой Save All. Окно диалога управления группой проектов представлено на рис. 24.6. 589
Часть VI. Создание компонентов
d
Packagel. bpl
New -Remove
Part ProjectGioupl Proiect1.exe 3 Unitl ; S) Unittpas Я Fotml
ЕАМЬКаеЛМ_ВООК_0\Ргад|атг\Сотропеп18\Ехатр1е EAMichael\NT_BOOK_DSPtogtams\Components\Example EAMichael\NT_BOOK_D\Progtams\Components\Example EAMichaelSNT.BOOK.DVPtogtamsVComponenls^Example EAMichael\NT_BOOK_D\PragtamsSComponents\Example EAMichaelSNT_BOOK_D\Ptograms\Components\Example
B-Q Cpntains Щ ntbBulton.. EAMichael\NT_BOOK_D\Ptogtams\Components\Example BuJ Requites : p rtl.dcp :
^ vcLdcp
Рис. 24.6. Группа проектов после создания тестового приложения, пакета компонентов и компонента ntbButton
Реализация компонента Дополнительная логика, вносимая в компонент SpeedButton, основывается на обработке сообщений cm_MouseEnter и cm_MouseLeave входа и выхода указателя мыши за пределы компонента. Аналогичные обработчики уже были приведены выше. Внутри данных обработчиков определяется состояние внутреннего логического protected-свойства fMouseOnMe, определяющего, находится ли указатель мыши в пределах компонента. Также реализуются два property-свойства ActiveColor и InActiveColor, предназначенных для изменения цветов подписи кнопки в активном и неактивном состояниях. Эти свойства будут доступны через Инспектор объектов, либо программно. При использовании компонента в визуальной разработке, значения этих свойств будут автоматически сохраняться в файле формы при сохранении проекта. Для изменения цвета подписи после установки новых значений свойств ActiveColor и InActiveColor, а также при вызове обработчиков сообщений cm_MouseEnter и cm_MouseLeave, реализуем вспомогательную процедуру UpdateColour: Procedure TntbButton.UpdateColour; begin {Установка значения подсвойству color свойства Font для отображения подписи необходимого цвета, в зависимости от активности компонента} if fMouseOnMe then Font.Color := fActiveColor else Font.Color := fInActiveColor; end; 590
Глава 24. Разработка компонентов
Модуль описания компонента будет иметь вид, приведенный в листинге 24.6. Листинг 24.6. Модуль описания компонента unit ntbButton; interface uses {Дополнительно подключаются модули Buttons (содержит описание класса TSpeedButton) , Messages (содержит описание констант-идентификаторов сообщений), и Graphics (содержит описание типа данных TColor)} SysUtils, Classes, Controls, ExtCtrls, Messages, Buttons, Graphics ; type TntbButton = class (TSpeedButton) protected fActiveColor: TColor; flnActiveColor: TColor; , fMouseOnMe: Boolean; {Метод обновления цвета подписи компонента} Procedure UpdateColour; {Метод установки значения свойства ActiveColor} Procedure SetActiveColor(NewColor: TColor); {Метод установки значения свойства InActiveColor} Procedure SetlnActiveColor(NewColor: TColor); {Метод обработки сообщения cm_MouseEnter} Procedure MouseComeln(var Message: TMessage); message cm_MouseEnter; {Метод обработки сообщения cm_MouseLeave} Procedure MouseComeOut(var Message: TMessage); message cm_MouseLeave; public {Конструктор переопределяется для установки начальных значений свойствам fActiveColor и flnActiveColor} Constructor Create(aOwner: TComponent); override; published {Описание property-свойства ActiveColor, несохраняемое значение — clRed} property ActiveColor: TColor read fActiveColor write SetActiveColor default clRed; {Описание property-свойства InActiveColor, несохраняемое значение — clBlack} property InActiveColor: TColor read flnActiveColor write SetlnActiveColor default clBlack; end;
Часть VI. Создание компонентов {Процедура регистрации компонента} procedure Register; implementation {Процедура регистрации компонента} procedure Register; begin RegisterComponents('NTB Components', [TntbButton]); end; Constructor TntbButton.Create(aOwner: TComponent) ; begin {Вызов родительской версии конструктора} Inherited; {Установка начального значения свойству fActiveColor} fActiveColor := clRed; fInActiveColor := clBlack; UpdateColour; end ; Procedure TntbButton.UpdateColour; begin {Установка значения подсвойству color свойства Font для отображения подписи необходимого цвета, в зависимости от активности компонента} if fMouseOnMe then Font.Color := fActiveColor else Font.Color := fInActiveColor; end; Procedure TntbButton.SetActiveColor(NewColor: TColor); begin {Если новое значение цвета отличается от ••'текущего, то оно принимается и выполняется обновление цвета подписи через метод UpdateColour} if NewColor <> fActiveColor then begin fActiveColor := NewColor; UpdateColour ; end; end; Procedure TntbButton.SetlnActiveColor(NewColor : TColor); begin {Если новое значение цвета отличается от текущего, то оно принимается и выполняется обновление цвета подписи через метод UpdateColour} if NewColor <> flnActiveColor then
592
Глава 24. Разработка компонентов
begin fInActiveColor := NewColor; UpdateColour; end; end; Procedure TntbButton.MouseComeln(var Message: TMessage) ; begin {Установка флага активности компонента} fMouseOnMe := True; {Обновление цвета подписи через вызов метода UpdateColour} UpdateColour; end; Procedure TntbButton.MouseComeOut(var Message: TMessage); begin {Установка флага активности компонента) fMouseOnMe := False; {Обновление цвета подписи через вызов метода UpdateColour} UpdateColour ; end; end.
Отладка компонента
Для отладки компонента с помощью тестового приложения добавим на главную форму этого приложения стандартную кнопку Button, в обработчике нажатия на которую создадим компонент ntbButton и настроим его свойства: procedure TForml.ButtonlClick(Sender: Var Button: TntbButton; begin Button := TntbButton.Create(Self) ; Button.Parent := Self; Button.Left := 50; Button.Top := 50; Button.Width := 80; Button.Height := 30; Button.Caption := 'Текст подписи'; end;
TObject);
Часть VI. Создание компонентов
Такой подход позволяет увидеть как создается кнопка и настраиваются ее свойства. При этом визуальный построитель не участвует в процессе тестирования, поэтому в нем не могут возникнуть исключительные ситуации, критичные для работы среды. В случае, если после запуска приложения и нажатия на кнопку Buttonl, компонент ntbButton появился на форме, можно немного модифицировать тестовую процедуру и изменить цвета подписи в активном и неактивном состояниях с помощью свойств ActiveColor и InActiveColor: 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 B u t t o n : TntbButton; begin Button := T n t b B u t t o n . C r e a t e ( S e l f ) ; B u t t o n . P a r e n t := S e l f ; B u t t o n . L e f t := 5 0 ; B u t t o n . T o p := 50; B u t t o n . W i d t h := 80; B u t t o n . H e i g h t := 30; B u t t o n . C a p t i o n := 'Текст подписи'; B u t t o n . A c t i v e C o l o r := clRed; B u t t o n . I n A c t i v e C o l o r := clMaroon; end;
TObject);
Теперь в неактивном состоянии подпись кнопки должна быть темно красная, а в активном — ярко красная. Если компонент ведет себя некорректно, то есть не обладает необходимой функциональностью, либо генерирует исключительные ситуации, можно начать его отладку с помощью установок в его модуле точек останова, использования пошагового выполнения приложения с помощью клавиш F7 и F8 и других методов отладки, которые имеются в Delphi. Инсталляция компонента После того, как компонент полностью отлажен, его можно установить в Палитру компонентов. Для этого в модуле описания компонента уже создана автоматически процедура Register^ содержащая код, необходимый для инсталляции. С помощью правой клавиши мыши вызовем контекстное меню проекта Packagel в диалоге управления группой проектов. В контекстном меню выберем пункт Build, в результате чего компонент будет откомпилирован и установлен в Палитру. При необходимости можно переместить его на другую страницу Палитры, или изменить положение страницы в Палитре с помощью диалога управления компонентами, описанного выше.
594
Вопросы с ответами для повторения по части VI
Использование компонента в визуальной разработке После того, как компонент появился в Палитре компонентов, его можно использовать в визуальной разработке обычными способами. Отобразим главную форму тестового приложения и добавим на нее компонент ntbButton со страницы NTB Components. Обратим внимание на Инспектор объектов, который содержит все методы компонента SpeedButton, от которого унаследован ntbButton, а также два дополнительных — ActiveColor И InActiveColor. Значения этих свойств изменяются с помощью выпадающего списка, и могут быть установлены с помощью диалога выбора цвета, как и все свойства типа TColor в Delphi. Это связано с тем, что Delphi имеет набор стандартных редакторов для свойств некоторых типов. Такое поведение среды разработки очень удобно и существенно упрощает разработку компонентов,
Вопросы с ответами для повторения по части VI Поясните понятие компонента Ответ: Компонентами называются наследники класса TCompqnent, которые предназначены для интеграции в среду разработчика Delphi для использования их в процессе визуальной разработки. Приведите основные особенности компонентов, как классов Delphi Ответ: Компонент имеет точно такую же структуру, как и любой класс в Delphi, то есть содержит конструктор, деструктор, свойства, методы и property-свойства. В дополнение к этому, компонент обладает всеми возможностями, реализованными в классе TComponent, так как является его наследником. Компоненты имеют виртуальные конструкторы, которые должны переопределяться в классах-наследниках с обязательным указанием ключевого слова override после описания. Также необходим вызов родительского варианта конструктора, для того, чтобы не нарушить жизненный цикл компонента. . Расскажите про возможности property-свойств Ответ: Property-свойства являются интерфейсом к внутренним данным экземпляра класса и могут быть доступны Инспектору объектов для изменения их значений во время визуальной разработки приложения. Данные свойства используют сложные описания, основанные на применении набора следующих модификаторов: 595
Часть VI. Создание компонентов 1. Модификаторы Read и Write определяют, соответственно, методы чтения или сохранения значения при обращении к property-свойству, причем вместо указания метода может быть использовано название внутреннего поля, подходящего property-свойству по типу. Если для получения или установки значения property-свойству используются методы, то они должны являться процедурами (для установки значения), принимающими параметр соответствующего типа, или функциями (для получения значения), возвращающими значение соответствующего типа. 2. Модификатор property-свойства index предназначен для объединения методов доступа (чтения и установки) к значениям нескольких однотипных property-свойств. После модификатора указывается целочисленное значение, которое является, фактически номером свойства. Методы доступа модифицируются добавлением параметра типа integer, в котором передается номер этого свойства. 3. Модификаторы stored, default и nodefault настраивают среду разработчика в отношении сохранения property-свойств и указывают, следует ли сохранять значение property-свойства, и в каких случаях. 4. Модификатор nodefault отменяет действие модификатора default при переопределении property-свойства. Для доступа к массивам информации могут использоваться property-свойства, тип данных которых аналогичен массивам. Методы доступа в этом случае модифицируются дополнительным параметром, определяющим номер элемента в массиве, к которому производится обращение. Расскажите про модель сообщений компонентов Ответ: Модель сообщений компонентов позаимствована из операционной системы Windows и позволяет компонентам обмениваться между собой сообщениями, упакованными в структуры типа TMessage. События, используемые в Delphi, делятся на две группы: » сообщения для оконных компонентов, полученные из системной очереди сообщений Windows; » и сообщения, созданные внутри программного продукта для передачи информации между компонентами смешанного типа.
596
Подарок оптовикам Украины! Почему все оптовые покупатели технической книги Украины теперь обращаются только к нам?
Ю Л И V\\ \А
Как издатели мы предоставили: тысячи книг по радиоэлектронике и ПК ведущих издательств наборы и альбомы схем бытовой радиоэлектронной техники подписку на прайс-листы с рассылкой по E-mail и по почте привлекательную систему скидок, не уступающую московской возможность приема заказа по прайсам любых издательств удобство подбора товара в нашем офисе при личном визите оперативное комплектование и оформление Вашего заказа срочную доставку в регионы Украины за счет издательства
Украинский лидер оптовой торговли издательство НАУКА и ТЕХНИКА, город Киев Книги по радиоэлектронике и компьютерным технологиям Интернет-магазин: www.nit.com.ru E-mail: [email protected] 02166 Киев, ул. Курчатова, 9/21. Тел.(044)-516-38-66, 559-27-40 Наш партнер в России: 000 "Наука и Техника" (г. Санкт-Петербург) E-mail: mKa}mailwplus.net Тел.(812)-567-70-25, 567-70-26
Книги и Наборы схем ПОЧТОЙ Издательство «Наука и Техника» принимает заказы на продажу печатной продукции по почте наложенным платежом. Оплата производится на почте при получении книг, для организаций возможна оплата по безналичному расчету после выставления счета. Для этого Вам необходимо оформить бланк заказа и отправить его нам. Для жителей России: 193029 Санкт-Петербург, а/я 44, ООО «Наука и Техника» тел/факс(812)-567-70-26, 567-70-25 E-mail: [email protected]
Для жителей Украины: 02166 Киев-166, ул. Курчатова, 9/21, «Наука и Техника» тел/факс (044)-516-38-66, 559-27-40 E-mail: [email protected]
Перечень рассылаемых книг размещен на сайте издательства: www.nit.com.ru Вы также можете приобрести книги других издательств по тематикам: Радиоэлектроника Компьютерные технологии : Техника безопасности на предприятиях : ,| | Транспорт | | Примечание. Заказанные бесплатные каталоги высылаются по электронной почте или вкладываются в посылку с книгами (отдельно письмом каталоги не рассылаются). Заполняйте поля аккуратно большими отдельными буквами.
Информация для приобретения книг почтой частными лицами 1. Фамилия, имя, отчество 2. Почтовый адрес: индекс страна область город, поселок улица __ дом корпус _ телефон ( ) адрес электронной почты (если он у Вас есть): E-mail: Информация для выставления счета организациям Название ИНН Телефон/факс ( ) Контактное лицо Адрес для отправки заказа: индекс страна. область город, поселок улица адрес электронной почты (если он у Вас есть): E-mail:
дом_
БЛАНК
ЗАКАЗА
А
(принимаются ксерокопии) Заказ (экз.)
г
Россия (руб.)
Украина (грн.)
Профи 127 00 .. 1800 .
Гургенидзе Кучерявый Назаров
2003
С 176
Мультисервисные сети и услуги широкопол, доступа... 109,00.. 19,00 ... . 2003 .. С 400 Пакетная сеть связи общего пользования 132,00.. 27,00 ... .2004.. С 272. Нейросетевые алгоритмы прогноз, и оптимизации 160,00.. 32,00 ... . 2003 .. С.384. 76,00.... 12,00 ... .2002.. М 256 С 384 154 00 .. 26,00 .. 2004
Секреты мастерства Колисниченко .. Linux-сервер своими руками, изд. 2-е, перераб. и доп. 193,00.. 39,00 ... .2004.. С_704. 8800.... 18,00 ... .2002.. С 768.
Просто о сложном Герасимов Дмитриев
AutoCAD 2002. Популярный самоучитель Настройки BIOS
Кузнецова Подольский Пестрикоа Серогодский ....
'.
27,00 15,00 32,00 30,00 34,00 8,00 8,00 21,00 22,00 22,00 33,00
135,00 .. 74,00 .... 159 00.. 152 00.. 169 00.. Установка и переустановка Windows, изд. 2-е .41,00.... Печать на ПК слепым 10-папьц. методом, 2-е + макет клав.. 39,00.... Turbo Pascal 7.0. Изучаем на примерах 106,00.. 110 00.. Excel 2002. Популярный самоучитель 110,00.. 165,00..
... ... ... ... ...
... ... ... ...
.2004.. .2004.. .2004.. .2004. .2004 .2004.. .2003.. .2004.. .2003 . 2003 . . 2004
С 496 М 288 С 560 С 640 С 576 М 128 М 80.. С_368 . С 336 С 352. С 640
Конспект программиста
Костельцев Куправа Николенко Николенко Николенко
С 352 28 00 .. . 5,00 . . 2001 .2001 С 256 33 00 ... . 6,00 . 2001 . С 352 .33,00... . 6,00 С 192 400 2001 1800 . 2001 . . М 224 Построение интерпретаторов и компиляторов + CD .... .44,00... . 9,00 . 2001 . .С 144. Самоучитель Access 97/2000 + дек .43,00... . 9,00 .2000 . С_144 . .39,00... . 8,00 MIDI - язык богов + CD : .2000 . С 1 28 . .18,00... . 4,00 Практические занятия no JavaScript + дек . 105,00. . 11,00 ... . 2001 . . С 368 . Самоучитель по Visual C++6 . 2001 . .С 190 22,00... . 4,00
Библиотека пользователя Будилов
Работаем с Finale 2001 + CD
Цеховой Цеховой
Энциклопедия дизайнера CorelDRAWIO + CD Macromedia Flash 5 + дек
.50,00... 33 00 .33,00... .44,00... .33,00...
. 5,00 600 . 6,00 . 9,00 . 6,00
. 2001 . . С 240 . С 144 2001 . 2001 . .М 224 . 2001 . .С 512 . 2001 . . С_192
Популярный учебник . 102,00. . 20,00 ... . 2003 . . М 400 . 102,00. . 20,00 ... . 2001 . .М 368
Домашний мастер Балясников
Обувь. Выбор, уход, ремонт
Богданов Умный дом Корякин-Черняк . Справ, домашнего электрика, изд. 2-е, перераб. и доп. . Корякин-Черняк . Холодильники от А до Я, изд. 2-е, перераб. и доп Пестриков
Азбука сотового телефона, изд. 2-е, перераб. и доп. ...
.49,00... . .61,00... . . 44,00 ... . .130,00. . .123,00. . . 68,00 ... . .123,00. .
10,00 12,00 9,00 27,00 25,00 14,00 25,00
... . 2003 . .М 240 ... . 2003 . . М 176 . 2003 . . М 112 ... . 2004 . .С 480 ... . 2003 . .С 416 .. . 2003 . .С 144 .. . 2004 . . С_352
.77,00... . . 55,00 ... . 105 00 . .87,00... . . 87,00 ... . . 60,00 .. . .60,00 .. .
15,00 11,00 16,00 17,00 17,00 12,00 12,00
.. ..2003. .. ..2003. .. ..2004. .. ..2004. .. ..2004. .. ..2004. .. .. 2004
Радиолюбитель Виноградов Саулов Семьян \л Шмырев
*
Микропроцессорное управление телевизорами .. Металл оискател и для любителей и профессионалов .. 500 схем для радиолюбителей. Приемники Радиостанция своими руками + вклейки
.М 223 . М 144 . М 400 . М 224 . М 288 . М 192 . М 144
(принимаются ксерокопии) [ Автор
Название
. Цена ... . Цена ....• Год .. .. Объем Заказ Украина Россия (эй.)
(руб.)
(грн.)
Радиомастер . 102,00. .49,00.... .55,00.... 5500 5500 Виноградов Сервисные режимы телевизоров-4 .55,00.... Корякин-Черняк . Сервисные режимы телевизоров-5 55,00.... 55,00 . . 5500 Виноградов Сервисные режимы телевизоров-8 .55,00.... Корякин-Черняк . Сервисные режимы телевизоров-9 .55,00.... Виноградов Сервисные режимы телевизоров-10 .55,00.... 55 00 Корякин-Черняк . Сервисные режимы телевизоров-12 .55,00.... 83,00 17500 Корякин-Черняк . Абонентские телефонные аппараты .121,00.. Корякин-Черняк . АОН в телефонных аппаратах .109,00.. Корякин-Черняк . Автосигнализации от А до Z, изд. 2-е, перераб. и доп. 127,00.. 106 00 66 00 Мухин Энциклопедия мобильной связи 63,00.... 5600 7000 Пашкевич Модернизация телевизоров 3..5УСЦТ, изд. 2-е нет Пестриков Домашний электрик и не только... Книга 1 76,00.... Пестриков Домашний электрик и не только... Книга 2 .76,00.... Пестриков Радиоэлектроника в конструкциях и увлечениях .87.00.... .87.00.... Янковский Видеомагнитофоны серии ВМ + (вклейки: 18 схем) .... .66,00 ...
Виноградов
Источники питания видеомагнитофонов
20 00 10,00 10,00 10 00 10 00 10,00 10,00 10 00 10 00 10,00 10,00 10,00 10 00 10,00 13 00 26 00 20,00 18,00 25,00 1600 1000 10,00 10 00 10 00 13,00 15,00 15,00 18,00 1800 13,00
.. ... ... .. . ... ... .. .. ... ... ... .. ... .. . ... ... ...
...
... ... ... ... .. ...
. 2003 . .С 192. . 2001 . . Б 256 . . 2002 . .С 208 . . 2001 С 208 .2001 С 208 . 2002 . . С 208 . . 2002 . .С 208 . . 2002 . . С 208 .2002 С 208 . 2002 . . С 208 . . 2002 . . С 208 . . 2002 . . С 208 . . 2002 С 208 . 2002 . . С 208 . . 2001 . . С 512. . 2003 . С 352 . 2003 . . С 368 . . 2003 . . С 288 . . 2003 . . С 336 . 2003 С 272 С 240 2001 . 2001 . . С 240 . 2000 С 192 2001 С 224 . 2001 . . С 320 . 2003. . С 208 . . 2003 . . С 208 . . 2004 . .С 240. . 2004 . . С 272 . . 2001 . . Б_272 .
Телемастер и Энциклопедия телемастера Безверхний Виноградов
Телевизоры DAEWOO и SAMSUNG + схемы . 120 00 2400 .. . 2003 . . С 144. Импульсные источники питания видеомагнитофонов .. .55,00.... 9,00 ... . 2003 . .С 160 . 142,00 . . 142 00 8300 10900 7000 12700
29,00 .. . 2004 . . С 320 . 29 00 .. . 2004 . . С 304 1300 .. . 2003 . С 160 2003 С 144 1800 2002 С 496 11 00 2003 С 384 25 00
Электронные компоненты Герасимов Никамин
87 00
18 00
2004
83 00 99 00 1 09 00 202 00 16400
15 00 20 00 2200 40 00 33 00
2001 2002 2004 2003 2004
С 256
Интегральные УНЧ, изд. 2-е, перераб. и доп . 154,00.. 13,00 ... . 2003 . . М~528 Микросхемы для CD-проигрывателей. Сервосистемы . .120,00.. 24,00 .,. . 2003 . . С 272 . С 400 М 320 С~288 М 768 М~590
Зарубежная электроника Авраменко Брускин
Ремонт и регулировка CD-проигр. + (вклейки: 26 схем) .58,00.... 12,00 ... . 1999. .Б 160. Зарубежные резидентные радиотелефоны, изд. доп. .. .48,00.... 10,00 ... .2000 .Б 176.
Кульский Куприянов Лаврус
КВ-приемник мирового уровня (для радиолюбителей) .40,00.... 8,00 .2000 . С 352 . Тех. обеспечение цифровой обработки сигналов .66,00 ... 13,00 ... .2000 . С 752 . Практика измерений в телевизионной технике .2000 .18,00.... 4,00 .Б 192.
. 18,00 ..
18 00
4,00 .
4 00
. . 1999. . Б 176
1998
Б 136
Справочник рабочего Шевченко
Чтение машиностроительных чертежей. Справ, пособие .54,00.... 11,00 ... . 2003 . . MJ92
Примечания. }. В колонке "Объем» буквой указан размер книги в см: М — 14x21, С — 16x23, Б — 20x28. 2. -t-CO — означает, что к книге прилагается компакт-диск. +дск — означает, что к книге прилагается дискета. 3. Цены розничные, указаны без учета почтовых расходов. Цены приведены по состоянию на сентябрь 2004 года.