WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!

Pages:     || 2 | 3 | 4 | 5 |   ...   | 10 |
-- [ Страница 1 ] --

Петр Дарахвелидзе Евгений Марков Санкт-Петербург «БХВ-Петербург» 2003 УДК 681.3.06 ББК 32.973.26-018.2 Д20 П. Г., Марков Е. П.

Д20 Программирование в Delphi 7. — СПб.: БХВ-Петербург, 2003. — 784 с: ил.

ISBN 5-94157-116-Х В книге обсуждаются вопросы профессиональной разработки приложе ний в среде Borland Delphi 7. Приводится детальное описание объектной концепции, стандартных и программных технологий, используемых при ра боте программистов. Значительная часть материала посвящена разработке приложений, базирующихся на широко используемых и перспективных технологиях доступа к данным: ADO, dbExpress, InterBase Express. Достой ное место отведено распределенным многозвенным приложениям и техно логии DataSnap. Все рассматриваемые в этой книге темы сопровождаются подробными примерами.

Для программистов УДК 681.3.06 ББК 32.973.26-018.2 Группа подготовки издания:

Главный редактор Екатерина Кондукова Зав. редакцией Анна Кузьмина Редактор Эльвира Максунова Компьютерная верстка Ольги Сергиенко Корректор Зинаида Дмитриева Оформление серии Via Design Дизайн обложки Игоря Цырульникова Зав. производством Николай Тверских Лицензия ИД № 02429 от 24.07.00. Подписано в печать 31.10.02.

Формат Печать офсетная. Усл. печ. л. 63,21.

Тираж 6000 экз. Заказ № "БХВ-Петербург", 198005, Санкт-Петербург, Измайловский пр., 29.

Гигиеническое заключение на продукцию, товар Na от 13.03.2002 г. выдано Департаментом ГСЭН Минздрава России.

Отпечатано с готовых диапозитивов в Академической типографии "Наука" РАН 199034, Санкт-Петербург, 9 линия, 12.

ISBN П. Г., Марков Е. П., © Оформление, издательство "БХВ-Петербург", Содержание ЧАСТЬ I. ОБЪЕКТНАЯ КОНЦЕПЦИЯ DELPHI 7 Глава 1. Объектно-ориентированное программирование Объект и класс Поля, свойства и методы События Инкапсуляция Наследование Полиморфизм Методы Перегрузка методов Области видимости Объект изнутри Резюме Глава 2. Библиотека визуальных компонентов VCL и ее базовые классы Иерархия базовых классов Класс Класс Класс Базовые классы элементов управления Класс TControl Группа свойств Visual. Местоположение и размер элемента управления Выравнивание элемента управления Внешний вид элемента управления Связь с родительским элементом управления Класс Класс Класс Резюме Содержание Глава 3. Обработка исключительных ситуаций Исключительная ситуация как класс Защитные конструкции языка Object Pascal Блок Блок Использование исключительных ситуаций Протоколирование исключительных ситуаций Коды ошибок в исключительных ситуациях Исключительная ситуация EAbort Функция Assert Резюме Глава 4. Кроссплатформенное программирование для Linux Проект CLX Объектная концепция кроссплатформенного программирования Библиотека компонентов CLX Сходства и различия визуальных компонентов CLX и VCL Особенности программирования для Linux Приложения баз данных для Linux Internet-приложения для Linux Резюме ЧАСТЬ II. ИНТЕРФЕЙС И ЛОГИКА ПРИЛОЖЕНИЯ Глава 5. Элементы управления Win32 Что такое библиотека ComCtl32 Многостраничный блокнот — компоненты и Компонент TToolBar Компонент Компоненты и '.

Календарь Компонент TMonthCalendar Компонент TDate Time Picker Панель состояния Расширенный комбинированный список Создание нового компонента на базе элементов управления из библиотеки Резюме Глава 6. Элементы управления Windows Пользовательский интерфейс Windows Манифест Windows Компонент TXPManifest Включение манифеста Windows в ресурсы приложения Визуальные стили и темы оформления Визуальные стили в Delphi Theme API Содержание Компоненты настройки цветовой палитры Резюме Глава 7. Списки и коллекции Список строк Класс Класс TStringList Список указателей Класс TList Пример использования списка указателей Коллекции Класс TCollection Класс Резюме Глава 8. Действия (Actions) и связанные с ними компоненты Действия. Компонент TActionList События, связанные с действиями Свойства, распространяемые на клиентов действия Прочие свойства Стандартные действия Категория Категория Search Категория Help Категория Категория Dialog Категория Window Категория Tab Категория List Категория Internet Категория Format Категория Dataset Категория Tools Компонент Изменение и настройка внешнего вида панелей Ручное редактирование коллекций панелей и действий Резюме Глава 9. Файлы и устройства ввода/вывода Использование файловых переменных. Типы файлов Операции ввода/вывода Ввод/вывод с использованием функций Windows API Отложенный (асинхронный) ввод/вывод Контроль ошибок ввода/вывода Атрибуты файла. Поиск файла Потоки Базовые классы и Класс 6 Содержание Класс Класс Оповещение об изменениях в файловой системе Использование отображаемых файлов Резюме Глава 10. Использование графики Графические инструменты Delphi Класс Класс Класс Класс TCanvas Класс Класс TPicture Класс Класс Класс Графический формат JPEG. Класс Компонент Использование диалогов для загрузки и сохранения графических файлов Класс Класс TScreen Вывод графики с использованием отображаемых файлов Класс TAnimate Резюме ЧАСТЬ III. ПРИЛОЖЕНИЯ БАЗ ДАННЫХ Глава 11. Архитектура приложений баз данных Как работает приложение баз данных Модуль данных Подключение набора данных Настройка компонента TDataSource Отображение данных Резюме Глава 12. Набор данных Абстрактный набор данных Стандартные компоненты Компонент таблицы Компонент запроса Компонент хранимой процедуры Индексы в наборе данных Механизм подключения индексов Список описаний индексов Описание индекса Использование описаний индексов Параметры запросов и хранимых процедур Содержание Класс TParams Класс Состояния набора данных Резюме Глава 13. Поля и типы данных Объекты полей Статические: и динамические поля Класс TField. Виды полей Поля синхронного просмотра Вычисляемые поля Внутренние вычисляемые поля Агрегатные поля Объектные поля Типы данных Ограничения Резюме Глава 14. Механизмы управления данными Связанные таблицы Отношение "один-ко-многим" Отношение "многие-ко-многим" Поиск данных Поиск по индексам Поиск в диапазоне Поиск по произвольным полям Фильтры Быстрый переход к помеченным записям Диапазоны Резюме Глава 15. Компоненты отображения данных Классификация компонентов отображения данных Табличное представление данных Компонент Компонент Навигация по набору данных Представление отдельных полей Компонент TDBText Компонент TDBEdit. Компонент Компонент Компонент Компонент Компонент TDBMemo Компонент Компонент TDBRichEdit 8 Содержание Синхронный просмотр данных Механизм синхронного просмотра Компонент Box Компонент Графическое представление данных Резюме ЧАСТЬ IV. ТЕХНОЛОГИИ ДОСТУПА К ДАННЫМ Глава 16. Процессор баз данных Borland Database Engine Архитектура и функции BDE Псевдонимы баз данных и настройка BDE Интерфейс прикладного программирования BDE Соединение с источником данных Компоненты доступа к данным Класс TBDEDataSet Класс Компонент TTable Компонент Компонент TStoredProc Резюме Глава 17. Технология dbExpress Драйверы доступа к данным Соединение с сервером баз данных Управление наборами данных Транзакции Использование компонентов наборов данных Класс Компонент TSQLDataSet Компонент TSQLTable Компонент Компонент TSQLStoredProc Компонент Способы редактирования данных Интерфейсы dbExpress Интерфейс Интерфейс lSQLConnection Интерфейс Интерфейс ISQLCursor. Отладка приложений с технологией dbExpress Распространение приложений с технологией dbExpress Резюме Глава 18. Сервер баз данных InterBase и компоненты InterBase Express Механизм доступа к данным InterBase Express Компонент TIBDatabase Компонент TIBTransaction Содержание Компоненты доступа к данным Область дескрипторов Структура XSQLVAR Компонент TIBTable Компонент Компонент TIBStoredProc Компонент TIBDataSet Компонент Обработка событий Информация о состоянии базы данных Компонент Компонент TIBSQLMonitor. Резюме Глава 19. Использование ADO средствами Delphi Основы ADO Перечислители Объекты соединения с источниками данных Сессия Транзакции Наборы рядов Команды Провайдеры ADO Реализация ADO в Delphi Компоненты ADO Механизм соединения с хранилищем данных ADO Компонент DO Connect ion Настройка соединения Управление соединением Доступ к связанным наборам данных и командам ADO Объект ошибок ADO Транзакции Наборы данных ADO Класс Набор данных Курсор набора данных Локальный буфер Состояние записи Фильтрация Поиск Сортировка Команда ADO Групповые операции Параметры Класс TParameters Класс TParameter Компонент TADODataSet Компонент TADOTable Содержание Компонент TADOQuery Компонент TADOStoredProc Команды ADO Объект ошибок ADO Пример приложения ADO Соединение с источником данных Групповые операции.„ Фильтрация Сортировка Резюме ЧАСТЬ V. РАСПРЕДЕЛЕННЫЕ ПРИЛОЖЕНИЯ БАЗ ДАННЫХ Глава 20. Технология DataSnap. Механизмы удаленного доступа Структура многозвенного приложения в Delphi Трехзвенное приложение в Delphi Сервер приложений Клиентское приложение Механизм удаленного доступа к данным DataSnap Компонент TDCOMConnection Компонент Компонент Провайдеры данных Вспомогательные компоненты — брокеры соединений Компонент TSimpleObjectBroker. Компонент TLocalConnection Компонент TSharedConnection Компонент TConnectionBroker. Резюме Глава 21. Сервер приложения Структура сервера приложения Интерфейс IAppServer Интерфейс Удаленные модули данных Удаленный модуль данных для сервера Автоматизации Дочерние удаленные модули данных Регистрация сервера приложения Пример простого сервера приложения Главный удаленный модуль данных Дочерний удаленный модуль данных Регистрация сервера приложения Резюме Глава 22. Клиент многозвенного распределенного приложения Структура клиентского приложения Клиентские наборы данных Содержание Компонент Получение данных от компонента-провайдера Кэширование и редактирование данных Управление запросом на сервере Использование индексов Сохранение набора данных в файлах Работа с данными типа BLOB Представление данных в формате XML Агрегаты Объекты-агрегаты Агрегатные поля Группировка и использование индексов Вложенные наборы данных Дополнительные свойства полей клиентского набора данных Обработка ошибок Пример "тонкого" клиента Соединение клиента с сервером приложения Наборы данных клиентского приложения Резюме ЧАСТЬ VI. ГЕНЕРАТОР ОТЧЕТОВ RAVE REPORTS 5.0 Глава 23. Компоненты Rave Reports и отчеты в приложении Delphi Генератор отчетов Rave Reports 5.0 Компоненты Rave Reports и их назначение Отчет в приложении Delphi Компонент отчета TRvProject Компонент управления отчетом TRvSystem Резюме Глава 24. Визуальная среда создания отчетов Инструментарий визуальной среды создания отчетов Проект отчета Библиотека отчетов Каталог глобальных страниц Словарь просмотров данных Стандартные элементы оформления и их свойства Элементы для представления текста и изображений Графические элементы управления Штрихкоды Обработка событий Внешние источники данных в отчете Соединение с источником данных и просмотры Безопасность доступа к данным Отображение данных в отчетах Структурные элементы отчета Элементы отображения данных Резюме 12 Содержание Глава 25. Разработка, просмотр и печать отчетов Этапы создания отчета и включение его в приложение Простой отчет в визуальной среде Rave Reports Нумерация страниц отчета Использование элемента FontMaster Добавление страниц к отчету Отчет в приложении Просмотр и печать отчета Сохранение отчета во внешнем файле Компонент Преобразование форматов данных Резюме Глава 26. Отчеты для приложений баз данных Соединения с источниками данных в Rave Reports Соединения с источниками данных в визуальной среде Rave Reports Соединение через драйвер Rave Reports Соединение через компонент приложения Delphi Соединения с источниками данных в приложении Компонент Компоненты, использующие BDE Компонент Аутентификация пользователя в отчете Типы отчетов Простой табличный отчет Отчет Группирующий отчет Использование вычисляемых значений Вычисляемые значения по одному источнику Вычисляемые значения по нескольким источникам Управляющие вычислительные элементы Резюме ЧАСТЬ VII. ТЕХНОЛОГИИ ПРОГРАММИРОВАНИЯ Глава 27. Стандартные технологии программирования Интерфейс переноса Drag-and-Drop Интерфейс присоединения Drag-and-Dock Усовершенствованное масштабирование Управление фокусом Управление мышью Ярлыки Резюме Глава 28. Динамические библиотеки Проект DLL Экспорт из DLL Содержание Соглашения о вызовах Директива register Директива pascal Директива stdcall Директива Директива safecall Инициализация и завершение работы DLL Вызов DLL Неявный вызов Явный вызов Ресурсы в DLL Использование модуля Резюме Глава 29. Потоки и процессы Обзор потоков Потоки и процессы Фоновые процедуры, или способ обойтись без потоков Приоритеты потоков Класс TThread Пример создания многопоточного приложения в Delphi Проблемы при синхронизации потоков Тупики Гонки Средства синхронизации потоков Событие Взаимные исключения Семафор Критическая секция Процесс. Порождение дочернего процесса Поток Консольный ввод Оповещение об изменении в файловой системе Локальные данные потока Как избежать одновременного запуска двух копий одного приложения Резюме Глава 30. Многомерное представление данных Понятие Взаимосвязь компонентов многомерного представления данных Подготовка набора данных Компонент Query Компонент TDecisionCube Компонент TDecisionSource Отображение данных Компонент TDecisionGrid Компонент TDecisionGraph Содержание Управление данными Компонент TDecisionPivot Пример многомерного представления данных Резюме Глава 31. Использование возможностей Shell API Понятие пространства Размещение значка приложения на System Интерфейс Интерфейс IShellFolder Добавление пунктов в системное контекстное меню Резюме Приложение. Описание дискеты Предметный указатель Объектная концепция Delphi Глава 1. Объектно-ориентированное программирование Глава 2. Библиотека визуальных компонентов и ее базовые классы Глава 3. Обработка исключительных ситуаций Глава 4. Кроссплатформенное программирование для Linux ГЛАВА Объектно-ориентированное программирование Несколько лет назад книгу Delphi 2 или 3 надо было начинать с азов объектно-ориентированного программирования (ООП). Многие только пе реходили к Delphi из DOS, многие использовали Borland Pascal for Windows и работали с Windows API напрямую. Объекты еще были в диковинку, и полное разъяснение новых принципов было просто обязательно.

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

Поэтому в данной главе мы постараемся акцентировать внимание читателя на применение ООП в среде Delphi 7.

По сравнению с традиционными способами программирования ООП обла дает рядом преимуществ. Главное из них заключается в том, что эта кон цепция в наибольшей степени соответствует внутренней логике функцио нирования операционной системы (ОС) Windows. Программа, состоящая из отдельных объектов, отлично приспособлена к реагированию на события, происходящие в ОС. К другим преимуществам ООП можно отнести боль шую надежность кода и возможность повторного использования отработан ных объектов.

В этой главе рассматриваются способы реализации основных механизмов ООП в Object Pascal и Delphi:

понятия объекта, класса и компонента;

основные механизмы ООП: инкапсуляция, наследование и полимор физм;

особенности реализации объектов;

взаимодействие и методов.

Глава 1. Объектно-ориентированное программирование Материал главы рассчитан на читателя, имеющего представление о самом языке Object Pascal, его операторах и основных возможностях.

Объект и класс Перед началом работы необходимо ввести основные понятия и опреде ления.

Классом в Object Pascal называется структура языка, которая может иметь в своем составе переменные, функции и процедуры. Переменные в зависимо сти от предназначения именуются или свойствами (см. ниже). Про цедуры и функции класса — методами. Соответствующий классу тип будем называть объектным типом:

type = Integer;

function MyMethod: Integer;

end;

В этом примере описан класс TMyObject, содержащий поле и метод MyMethod.

Поля объекта аналогичны полям записи (record). Это данные, уникальные для каждого созданного в программе экземпляра класса. Описанный здесь класс TMyObject имеет одно поле — MyFieid.

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

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

Разрешено опережающее объявление классов, как в следующем примере:

type TFirstObject = class;

TSecondObject = 18 Часть I. Объектная концепция Delphi : TFirstObject;

end;

TFirstObject = end;

Чтобы использовать класс в программе, нужно, как минимум, объявить пе ременную этого типа. Переменная объектного типа называется экземпляром класса или объектом:

var AMyObject: TMyObject;

До введения термина "класс" в языке Pascal существовала двусмысленность определения "объект", который мог обозначать и тип, и переменную этого типа. Теперь же существует четкая граница: класс — это описание, объ ект — то, что создано в соответствии с этим описанием.

Как создаются и уничтожаются объекты?

Те, кто раньше использовал ООП в работе на C++ и особенно в Turbo Pascal, будьте внимательны: в Object Pascal экземпляры объектов могут быть только динамическими. Это означает, что в приведенном выше фрагменте переменная AMyObject на самом деле является указателем, содержащим адрес объекта.

Объект "появляется на свет" в результате вызова специального метода, кото рый инициализирует объект — конструктора. Созданный экземпляр унич тожается другим методом — деструктором:

AMyObject := { действия с созданным объектом } AMyObject.Destroy;

Но, скажет внимательный читатель, ведь объекта еще нет, как мы можем вызывать его методы? Справедливое замечание. Однако обратите внимание, ЧТО а не Есть такие методы (в том числе конструктор), которые успешно работают до (или даже без) создания объекта. О подобных методах, называемых методами класса, пойдет речь чуть ниже.

В Object Pascal конструкторов у класса может быть несколько. Общепринято называть конструктор (в отличие от Turbo Pascal, где конструктор обычно назывался и от C++, его имя совпадает с именем класса).

Типичное название деструктора — Destroy.

type TMyObject = MyField: Integer;

Глава 1. Объектно-ориентированное программирование Constructor Create;

Destructor Destroy;

Function Integer;

end;

Для уничтожения экземпляра объекта рекомендуется использовать метод Free, который первоначально проверяет указатель (не равен ли он Nil) и только затем вызывает Destroy:

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

( Примечание Конструктор создает новый объект только в том случае, если перед его именем указано имя класса. Если указать имя уже существующего объекта, он поведет себя по-другому: не создаст новый объект, а только выполнит код, содержащий ся в теле конструктора.

Чтобы правильно инициализировать в создаваемом объекте поля, относя щиеся к классу-предку, нужно сразу же при входе в конструктор вызвать конструктор предка при помощи зарезервированного слова inherited:

constructor begin inherited Create;

end;

Взяв любой из примеров, прилагаемых к этой книге или поставляемых вместе в Delphi, вы почти не увидите там вызовов конструкторов и деструк торов. в том, что любой компонент, попавший при визуальном проек тировании в ваше приложение из Палитры компонентов, включается в оп иерархию. Иерархия эта замыкается на форме (класс для всех ее составных частей конструкторы и деструкторы вызываются авто матически, незримо для программиста. Кто создает и уничтожает формы?

Это делает приложение (глобальный объект с именем Application). В фай ле (с расширением вы можете увидеть вызовы метода предназначенного для этой цели.

Что же касается объектов, создаваемых динамически (во время выполнения приложения), то здесь нужен явный вызов конструктора и метода Free.

20 Часть I. Объектная концепция Delphi Поля, свойства и методы Поля класса являются переменными, объявленными внутри класса. Они предназначены для хранения данных во время работы экземпляра класса (объекта). Ограничений на тип полей в классе не предусмотрено. В описа нии класса поля должны предшествовать методам и свойствам. Обычно по ля используются для обеспечения выполнения операций внутри класса.

( Примечание При объявлении имен полей принято к названию добавлять заглавную букву F.

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

Для получения и передачи данных в классе применяются свойства. Для объ явления свойств в классе используется зарезервированное слово property.

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

Для экземпляра класса "кнопка" значения этих атрибутов задаются при по мощи свойств — специальных переменных, определяемых ключевым сло вом property. Цвет может задаваться свойством color, размеры — свойства ми Width И Height И Т. Д.

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

type TAnObject function GetColor:

procedure TSomeType);

property AColor: TSomeType read GetColor write SetColor;

end;

В данном примере доступ к значению свойства AColor осуществляется через вызовы методов GetColor и SetColor. Однако в обращении к этим методам в явном виде нет необходимости: достаточно написать:

AValue;

AVariable := Глава 1. Объектно-ориентированное программирование и компилятор самостоятельно оттранслирует обращение к свойству AColor в вызовы методов или есть внешне свойство выглядит в точности как обычное поле, но за всяким обращением к нему могут сто ять нужные вам действия. Например, если у вас есть объект, представляю щий собой квадрат на экране, и его свойству "цвет" вы присваиваете значе ние "белый", то произойдет немедленная перерисовка, приводящая реаль ный цвет на экране в соответствие со значением свойства. Выполнение этой операции осуществляется методом, который связан с установкой значения свойства "цвет".

В методах, входящих в состав свойств, может осуществляться проверка ус танавливаемой величины на попадание в допустимый диапазон значений и вызов других процедур, зависящих от вносимых изменений. Если же по требности в специальных процедурах чтения и/или записи нет, можно вме сто имен методов применять имена полей. Рассмотрим следующую конст рукцию:

TPropObject FValue: TSomeType;

procedure DoSomething;

function procedure Integer);

property AValue: Integer read FValue write SetValue;

end;

procedure Integer);

begin if (NewValueOFValue) and Correct (NewValue) then FValue := NewValue;

DoSomething;

end;

В этом примере чтение значения свойства AValue означает просто чтение поля Зато при присвоении значения внутри SetValue вызывается сразу два метода.

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

type = property AProperty: TSomeType read GetValue;

end;

В этом примере вне объекта значение свойства можно лишь прочитать;

попытка присвоить свойству AProperty значение вызовет ошибку компи ляции.

Часть I. Объектная концепция Delphi Для присвоения свойству значения по умолчанию используется ключевое СЛОВО default:

property Visible: boolean read FVisible write SetVisible default True;

Это означает, что при запуске программы свойство будет установлено ком пилятором В True.

Свойство может быть и векторным;

в этом случае оно внешне выглядит как массив:

property : Integer]:TPoint read GetPoint write SetPoint;

На самом деле в классе может и не быть соответствующего поля — массива.

Напомним, что вся обработка обращений к внутренним структурам класса может быть замаскирована.

Для векторного свойства необходимо описать не только тип элементов мас сива, но также имя и тип индекса. После ключевых слов read и write в этом случае должны стоять имена методов — использование здесь полей масси вов недопустимо. Метод, читающий значение векторного свойства, должен быть описан как функция, возвращающая значение того же типа, что и элементы свойства, и имеющая единственный параметр того же типа и с тем же именем, что и индекс свойства:

function Аналогично, метод, помещающий значения в такое свойство, должен пер вым параметром иметь индекс, а вторым — переменную нужного типа (ко торая может быть передана как по ссылке, так и по значению):

procedure NewPoint:TPoint);

У векторных свойств есть еще одна важная особенность. Некоторые классы в Delphi (списки TList, наборы строк "построены" вокруг основ ного векторного свойства (см. гл. 7). Основной метод такого класса дает доступ к некоторому массиву, а все остальные методы являются как бы вспомогательными. Специально для облегчения работы в этом случае век торное свойство может быть описано с ключевым словом type = class;

property Integer]: string read Get write Put;

default;

end;

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

var TMyObject;

begin Глава 1. Объектно-ориентированное программирование := ' Fi rst ;

{первый способ} := end.

Будьте внимательны, применяя зарезервированное слово default, — как мы увидели, для обычных и векторных свойств оно употребляется в разных случаях и с различным синтаксисом.

О роли свойств в Delphi красноречиво говорит следующий факт: у всех имеющихся в распоряжении программиста стандартных классов 100% полей недоступны и заменены базирующимися на них свойствами. Рекомендуем при разработке собственных классов придерживаться этого же правила.

Внимательный читатель обратил внимание, что при объяснении терминов "поле" и "свойство" мы использовали понятие метода, и наверняка понял его общий смысл. Итак, методом называется объявленная в классе функция или процедура, которая используется для работы с полями и свойствами класса. Согласно принципам ООП (см. разд. "Инкапсуляция" далее в главе), обращаться к свойствам класса можно только через его методы. От обычных процедур и функций методы отличаются тем, что им при вызове передается указатель на тот объект, который их вызвал. Поэтому обрабаты ваться будут данные именно того объекта, который вызвал метод. На неко торых особенностях использования методов мы остановимся ниже.

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

как реализованы события на уровне языка Object Pascal. Собы тие — это свойство процедурного типа, предназначенное для создания пользовательской реакции на то или иное входное воздействие:

property OnMyEvent: read write FOnMyEvent;

24 Часть I. Объектная концепция Delphi Здесь — поле процедурного типа, содержащее адрес некоторого метода. Присвоить такому свойству значение — значит указать объекту ад рес метода, который будет вызываться в момент наступления события. Та кие методы называют обработчиками событий. Например, запись:

:= означает, что при активизации объекта Application (так называется объект, соответствующий работающему приложению) будет вызван метод обработчик MyActivatingMethod.

Внутри библиотеки времени выполнения Delphi вызовы обработчиков со бытий находятся в методах, обрабатывающих сообщения Windows. Выпол нив необходимые действия, этот метод проверяет, известен ли адрес обра ботчика, и, если это так, вызывает if then События имеют разное количество и тип параметров в зависимости от про исхождения и предназначения. Общим для всех является параметр sender — он указывает на объект-источник события. Самый простой тип — TNotifyEvent — не имеет других параметров:

TNotifyEvent = procedure (Sender: TObject) of object;

Тип метода, предназначенный для извещения о нажатии клавиши, преду сматривает передачу программисту кода этой клавиши о передвижении мыши — ее текущих координат и т. п. Все события в Delphi принято пред варять префиксом onCreate, OnMouseMove, onPaint и т. д. Дважды щелкнув в Инспекторе объектов на странице Events в поле любого события, вы по лучите в программе заготовку метода нужного типа. При этом его имя будет состоять из имени текущего компонента и имени события (без префикса а относиться он будет к текущей форме. Пусть, например, на форме есть текст Тогда для обработки щелчка мышью (событие Onclick) будет создана заготовка метода procedure begin end;

Поскольку события — это свойства объекта, их значения можно изменять в любой момент во время выполнения программы. Эта замечательная воз можность называется делегированием. Можно в любой момент взять способы реакции на события у одного объекта и присвоить (делегировать) их другому:

:= Глава 1. Объектно-ориентированное программирование Принцип делегирования позволяет избежать трудоемкого процесса порож дения новых дочерних классов для каждого специфического случая, заме няя его простой подстановкой процедур. При необходимости можно выби рать один из нескольких возможных вариантов обработчиков событий.

Но какой механизм позволяет подменять обработчики, ведь это не просто процедуры, а методы? Здесь как нельзя кстати приходится существующее в Object Pascal понятие указателя на метод. Отличие метода от процедуры состоит в том, что помимо явно описанных параметров методу всегда неявно передается еще и указатель на вызвавший его экземпляр класса (переменная Вы можете описать процедурный тип, который будет совместим по присваиванию с методом (т. е. предусматривать получение Для этого в описание процедуры нужно добавить зарезервированные слова of object. Указатель на метод — это указатель на такую процедуру.

type = TObject;

var AValue: Integer) of object;

TlstObject = class;

property TMyEvent read FOnMyEvent write FOnMyEvent;

end;

T2nd0bject = class;

procedure TObject;

var AValue: Integer);

procedure TObject;

var AValue: Integer);

end;

var TlstObject;

begin Objl := := := := end.

Этот пример показывает, что при делегировании можно присваивать мето ды других классов. Здесь обработчиком события OnMyEvent объекта objl по очереди выступают методы SetValuel и объекта Обработчики событий нельзя сделать просто процедурами — они обязатель но должны быть чьими-то методами. Но их можно "отдать" какому-либо другому объекту. Более того, для этих целей можно описать и создать спе 26 Часть I. Объектная концепция Delphi циальный объект. Его единственное предназначение — быть носителем ме тодов, которые затем делегируются другим объектам. Разумеется, такой объ ект надо не забыть создать до использования его методов, а в конце — уничтожить. и не делать этого, объявив методы методами класса, о которых речь пойдет в одном из последующих разделов.

Мы сейчас решили задачу использования нескольких разных обработчиков того или иного события для одного объекта. Но не менее часто требуется решить обратную задачу — а как использовать для различных событий раз ных объектов один и тот же обработчик?

Если никакой "персонификации" объекта, вызвавшего метод, не нужно, все делается тривиально и проблемы не возникает. Самый простой пример:

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

Более сложный случай, когда внутри такого метода нужно разобраться, кто собственно вызвал. Если потенциальные кандидаты имеют разный объ ектный тип (как в предыдущем абзаце — кнопка и пункт меню), то именно объектный тип можно применить в качестве критерия:

If Sender is then пункт меню');

Если же все объекты, разделяющие между собой один обработчик события, относятся к одному классу, то приходится прибегать к дополнительным ухищрениям. Типовой прием — использовать свойство Tag, которое имеется у всех компонентов, и, вполне вероятно, именно для этого и задумывалось:

const colors : of TColor = procedure TObject);

begin with do if Checked then Color := else Color := clBtnFace;

end;

Пусть в форме имеется несколько переключателей. Для того чтобы при на жатии каждый из них окрашивался в свой цвет, нужно в Инспекторе объек тов присвоить свойству Tag значения от 0 до 7 и для каждого связать собы тие Onclick с методом Этот единственный метод справится с задачей для всех переключателей.

Глава 1. Объектно-ориентированное программирование Инкапсуляция В предыдущих разделах мы ввели ряд новых понятий, которыми будем пользоваться в дальнейшем. Теперь поговорим о принципах, составляющих суть объектно-ориентированного программирования. Таких принципов три — инкапсуляция, наследование и полиморфизм.

Как правило, объект — это сложная конструкция, свойства и поведение со ставных частей которой находятся во взаимодействии. К примеру, если мы моделируем взлетающий самолет, то после набора им определенной скоро сти и отрыва от земли принципы управления им полностью изменяются.

Поэтому в объекте "самолет" явно недостаточно просто увеличить значение поля "скорость" на несколько километров в час — такое изменение должно автоматически повлечь за собой целый ряд других.

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

Классическое правило объектно-ориентированного программирования ут верждает, что для обеспечения надежности нежелателен прямой доступ к полям объекта: чтение и обновление их содержимого должно производиться посредством вызова соответствующих методов. Это правило и называется инкапсуляцией. В старых реализациях ООП (например, в Turbo Pascal) эта мысль внедрялась только посредством призывов и примеров в документа ции;

в языке же Object Pascal есть соответствующая конструкция. В Delphi пользователь вашего объекта может быть полностью отгорожен от полей при помощи свойств (см. выше).

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

Наследование Вторым "столпом" ООП является наследование. Этот простой принцип озна чает, что если вы хотите создать новый класс, лишь немного отличающийся от старого, то совершенно нет необходимости в переписывании заново уже существующих полей и методов. Вы объявляете, что новый класс == является потомком или дочерним классом старого класса TOldObject, назы ваемого предком или родительским классом, и добавляете к нему новые поля, методы и свойства — иными словами, то, что нужно при переходе от обще го к частному.

28 Часть I. Объектная концепция Delphi Примечание Прекрасный пример, иллюстрирующий наследование, представляет собой иерархия классов VCL В Object Pascal все классы являются потомками класса TObject. Поэтому, ес ли вы создаете дочерний класс прямо от TObject, то в определении его можно не упоминать. Следующие два выражения одинаково верны:

= TMyObject = class;

Первый вариант, хотя он и более длинный, предпочтительнее — для устра нения возможных неоднозначностей. Класс TObject несет очень серьезную нагрузку и будет рассмотрен отдельно.

Унаследованные от класса-предка поля и методы доступны в дочернем классе;

если имеет место совпадение имен методов, то говорят, что они пе рекрываются.

Поведение методов при наследовании, без преувеличения, является крае угольным камнем объектно-ориентированного программирования. В зави симости от того, какие действия происходят при вызове, методы делятся на три группы. В первую группу отнесем статические методы, во вторую — виртуальные (virtual) и динамические (dynamic) и, наконец, в третью — появившиеся только в Delphi 4 — перегружаемые (overload) методы.

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

Язык C++ допускает так называемое множественное наследование. В этом случае новый класс может наследовать часть своих элементов от одного ро дительского класса, а часть — от другого. Это, наряду с удобствами, зачас тую приводит к проблемам. В Object Pascal понятие множественного насле дования отсутствует. Если вы хотите, чтобы новый класс объединял свойст ва нескольких, породите классы-предки один от другого или включите в один класс несколько полей, соответствующих другим желаемым классам.

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

Глава 1. Объектно-ориентированное программирование type = class function virtual;

abstract;

end;

TStringField = FData : string;

function GetData: string;

override;

end;

= FData : Integer;

function GetData:

end;

TExtendedField = FData : Extended;

function GetData:

end;

function begin Result := FData;

end;

function begin Result end;

function begin ffFixed, 7, 2);

end;

procedure : TField);

begin := end;

В этом примере классы содержат разнотипные поля данных FData и только то и "умеют", что сообщить о значении этих данных текстовой строкой (при ПОМОЩИ GetData). Внешняя ПО ОТНОШеНИЮ К НИМ Процедура ShowData получает объект в виде параметра и показывает эту строку.

Правила контроля соответствия типов (typecasting) языка Object Pascal гла сят, что объекту как указателю на экземпляр объектного типа может быть 30 Часть I. Объектная концепция Delphi присвоен адрес любого экземпляра любого из дочерних типов. В процедуре параметр описан как — это значит, что в нее можно пере давать И TStringField, И И TExtendedField, И любого другого потомка класса TFieid.

Но какой (точнее, чей) метод GetData при этом будет вызван? Тот, который соответствует классу фактически переданного объекта. Этот принцип назы вается полиморфизмом, и он, пожалуй, представляет собой наиболее важный козырь ООП.

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

Основные, "родовые" черты (методы) нужно описать в классе-предке и за тем перекрывать их в классах-потомках. В нашем примере программисту, пишущему процедуру вроде важно лишь, что любой объект, пере данный в нее, является потомком TFieid и он умеет сообщить о значении своих данных (выполнив метод GetData). Если, к примеру, такую процедуру скомпилировать и поместить в динамическую библиотеку, то эту библиоте ку можно будет раз и навсегда использовать без изменений, хотя будут по являться и новые, неизвестные в момент ее создания TFieid.

Наглядный пример использования полиморфизма дает среда Delphi. В ней имеется класс TComponent, на уровне которого сосредоточены определенные "правила" взаимодействия компонентов со средой разработки и с другими компонентами. Следуя этим правилам, можно порождать от класса TComponent свои компоненты, настраивая Delphi на решение специальных задач.

Теперь, надеемся, стало более или менее ясно, какие преимущества ООП позволили ему стать основным способом разработки серьезного программ ного обеспечения. Те, кто начинал программировать еще для Windows 3.0, наверняка помнят, сколько усилий требовалось при написании совершенно тривиального кода. Сейчас для того же самого в Delphi достаточно букваль но пары щелчков мышью. На самом деле именно сложность программиро вания для Windows стала катализатором внедрения ООП.

Методы Из предыдущего материала читатели узнали, что функционирование объек тов обеспечивается различными типами методов, которые различаются осо бенностями реализации механизма наследования. Теперь настало время рассмотреть эти методы более подробно.

Глава 1. Объектно-ориентированное программирование Абстрактными называются методы, которые определены в классе, но не содержат никаких действий, никогда не вызываются и обязательно должны быть переопределены в потомках класса. Абстрактными могут быть только виртуальные и динамические методы. В Object Pascal такие методы объяв ляются с помощью одноименной директивы. Она указывается при описа нии метода:

procedure virtual;

abstract;

При этом никакого кода для этого метода писать не нужно. Вызов метода приведет К созданию ситуации EAbstractError ситуациям посвящена гл. 4).

Пример с классом из разд. "Полиморфизм" этой главы поясняет, для чего нужно использование абстрактных методов. В данном случае класс TFieid не используется сам по себе;

его основное предназначение — быть родоначальником иерархии конкретных классов-"полей" и дать возможность абстрагироваться от частностей. Хотя параметр процедуры и опи сан как TFieid, но, если передать в нее объект этого класса, произойдет ис ключительная ситуация вызова абстрактного метода.

Статические методы, а также любые поля в объектах-потомках ведут себя одинаково: вы можете без ограничений перекрывать старые имена и при этом изменять тип методов. Код нового статического метода полностью пе рекрывает (заменяет собой) код старого метода:

type TlstObj = class i : Extended;

procedure Extended);

end;

= i : Integer;

procedure Integer);

end;

procedure begin i := 1.0;

end;

procedure begin i := 1;

inherited end;

32 Часть I. Объектная концепция Delphi В этом примере разные методы с именем SetData присваивают значения раз ным полям с именем i. Перекрытое (одноименное) поле предка недоступно в потомке;

поэтому, конечно, два одноименных поля с именем i — это нонсенс;

так сделано только для примера.

Примечание В практике программирования принято присваивать всем идентификаторам в программе (в том числе полям объектов) осмысленные названия. Это сущест венно облегчит работу с исходным кодом не только другим разработчикам, но и вам.

В отличие от поля, внутри других методов перекрытый метод доступен при указании зарезервированного слова inherited. По умолчанию все методы объектов являются статическими — их адрес определяется еще на стадии компиляции проекта, поэтому они вызываются быстрее всего. Может быть, читателю еще не ясно, для упоминается этот факт. Просто запомните его, он понадобится при сравнении статических и виртуальных методов.

Принципиально отличаются от статических виртуальные и динамические ме тоды. Они должны быть объявлены путем добавления соответствующей ди рективы virtual или Обе эти категории существовали и в прежних версиях языка Pascal. С точки зрения наследования методы этих двух видов одинаковы: они могут быть перекрыты в дочернем классе только одноимен ными методами, имеющими тот же тип.

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

Естественно, что этот механизм должен быть каким-то образом связан с передаваемым объектом. Для этого используются таблица виртуальных методов (Virtual Method Table, VMT) и таблица динамических методов (Dynamic Method Table, DMT).

Разница между виртуальными и динамическими методами заключается в особенности поиска адреса. Когда компилятор встречает обращение к вир туальному методу, он подставляет вместо прямого вызова по конкретному адресу код, который обращается к VMT и извлекает оттуда нужный адрес.

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

Глава 1. Объектно-ориентированное программирование Динамические методы вызываются медленнее, но позволяют более эконом но расходовать память. Каждому динамическому методу системой присваи вается уникальный индекс. В таблице динамических методов класса хранят ся индексы и адреса только тех динамических методов, которые описаны в данном классе. При вызове динамического метода происходит поиск в этой таблице;

в случае неудачи просматриваются таблицы DMT всех клас сов-предков в порядке иерархии и, наконец, класс где имеется стандартный обработчик вызова динамических методов. Экономия памяти налицо.

Для перекрытия и виртуальных, и динамических методов служит директива override, с помощью которой (и только с ней!) можно переопределять оба этих типа методов. Приведем пример:

type TFirstClass = class FMyFieldl: Integer;

FMyField2: Longint;

procedure StatMethod;

procedure virtual;

procedure VirtMethod2;

virtual;

procedure dynamic procedure dynamic end;

TSecondClass = procedure StatMethod;

procedure VirtMethodl;

override;

procedure DynaMethodl;

override;

end;

var TFirstClass;

TSecondClass;

Первый из методов в примере создается заново, остальные два — перекры ваются. Попытка применить директиву override к статическому методу вы зовет ошибку ( Примечание Будьте внимательны: попытка перекрытия с директивой не overri de, a vi r t ual или dynamic приведет на самом деле к созданию нового одноимен ного метода.

! Зак. 34 Часть I. Объектная концепция Delphi Перегрузка методов Есть еще одна, совершенно особенная разновидность методов — перегру жаемые.

Эту категорию методов нельзя назвать антагонистом двух предыдущих:

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

Рассмотрим немного измененный пример, иллюстрирующий статические методы:

type TlstObj = class FExtData : Extended;

procedure Extended);

end;

= : Integer;

procedure Integer);

end;

var TlstObj;

T2 :

В этом случае попытка вызова из объекта методов вызовет ошибку компиляции на первой из двух строк. Для компилятора внутри тг статический метод с параметром типа extended перекрыт, и он его "не признает". Где же выход из сложившегося положения? Переимено вать ОДИН ИЗ методов, создать И SetExtendedData?

Можно, но если методов не два, а, скажем, сто, моментально возникнет путаница. Сделать методы виртуальными? Нельзя, поскольку тип и коли чество параметров в одноименных виртуальных методах должны в точности совпадать. Теперь для этого существуют перегружаемые методы, объявляе мые при помощи директивы overload:

type TlstObj class FExtData : Extended;

procedure end;

Глава 1. Объектно-ориентированное программирование = : Integer;

procedure Integer);

overload;

end;

Объявив метод SetData перегружаемым, в программе можно использовать обе его реализации одновременно. Это возможно потому, что компилятор определяет тип передаваемого параметра (целый или с плавающей точкой) и в зависимости от этого подставит вызов соответствующего метода: для целочисленных данных — метод объекта T2ndobj, для данных с плавающей точкой — метод объекта Можно перегрузить и виртуальный (динамический) метод. Надо только в этом случае добавить директиву reintroduce:

type TlstObj = class : Extended;

procedure Extended);

overload;

virtual;

end;

= FIntData : Integer;

procedure Integer);

reintroduce;

overload;

end;

На перегрузку методов накладывается ограничение — нельзя перегружать методы, находящиеся в области видимости published, m. e. me, которые бу дут использоваться в Инспекторе объектов.

Области видимости При описании нового класса важен разумный компромисс. С одной сторо ны, требуется скрыть от других методы и поля, представляющие собой внутреннее устройство класса (для этого и придуманы свойства). Маловаж ные детали на уровне пользователя объекта будут бесполезны и только по мешают целостности восприятия.

С другой стороны, если слишком ограничить того, кто будет порождать классы-потомки, и не обеспечить ему достаточный набор инструментальных средств и свободу маневра, то он и не станет использовать ваш класс.

В модели объектов языка Object Pascal существует механизм доступа к со ставным частям объекта, определяющий области, где ими можно пользо ваться (области видимости). Поля и методы могут относиться к четырем группам (секциям), отличающимся областями видимости. Методы и свойст ва могут быть общими (секция public), личными (секция private), защищен 36 Часть I. Объектная концепция Delphi (секция protected) и опубликованными (секция published). Есть еще и пятая группа, automated, она ранее использовалась для создания объектов теперь она присутствует в языке только для обратной совместимости с программами на Delphi версий 3—5.

Области видимости, определяемые первыми тремя директивами, таковы.

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

Поля, свойства и методы, находящиеся в секции private, доступны толь ко в методах класса и в функциях, содержащихся в том же модуле, что и описываемый класс. Такая директива позволяет полностью скрыть дета ли внутренней реализации класса. Свойства и методы из секции private можно изменять, и это не будет сказываться на программах, работающих с объектами этого класса. Единственный способ для кого-то другого об ратиться к ним — переписать заново созданный вами модуль (если, ко нечно, доступны исходные тексты).

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

Рассмотрим пример, иллюстрирующий три варианта областей видимости.

\ Листинг 1.1. Пример задания областей видимости методов unit First;

unit Second;

interface interface uses First;

type type TFirstObj = class TSecondObj = procedure private end;

procedure protected procedure public procedure end;

procedure TestProcl;

procedure TestProc2;

Глава 1. Объектно-ориентированное программирование implementation implementation uses dialogs;

var AFirstObj : TFirstObj;

var procedure TestProcl;

procedure begin begin AFirstObj := Methodl;

произойдет ошибка Method2;

{допустимо} {допустимо} Method3;

{допустимо} end;

end;

procedure TestProc2;

begin procedure AFirstObj := begin {недопустимо} {недопустимо} end;

{допустимо} procedure begin ASecondObj := {недопустимо} end;

procedure {допустимо} begin ShowMessage end;

end;

end.

end.

Если к этому примеру добавить модуль Third и попробовать вызвать методы классов TFirstObj и TSecondObj оттуда, то к числу недоступных будет отне сен и Method2 — он доступен только в том модуле, в котором описан.

Наконец, область видимости, определяемая четвертой директивой — published, имеет особое значение для интерфейса визуального проектирова ния Delphi. В этой секции должны быть собраны те свойства объекта, кото рые будут видны не только во время исполнения приложения, но и из сре ды разработки. Публиковать можно свойства большинства типов, за исклю чением старого типа real (теперь он называется свойств типа "массив" и некоторых других. Все свойства компонентов, доступные через Часть I. Объектная концепция Delphi Инспектор объектов, являются их опубликованными свойствами. Во время выполнения такие свойства общедоступны, как и Три области видимости — private, protected, public — как бы упорядоче ны по возрастанию видимости методов. В классах-потомках можно повы сить видимость методов и свойств, но не понизить ее. При описании до чернего класса можно переносить методы и свойства из одной сферы види мости в другую, не переписывая их заново и даже не описывая — достаточно упомянуть о нем в другом месте:

type TFirstObj = class private Integer;

protected property Number;

Integer read: FNumber;

end;

TSecondObj = published property Number;

end;

Если какое-либо свойство объекта из состава VCL принадлежит к области public, вернуть его в private невозможно. Напротив, обратная процедура широко практикуется в Delphi. У многих компонентов (например, TEdit) есть предок (в данном случае TCustomEdit), который отличается только от сутствием опубликованных свойств. Так что, если вы хотите создать новый редактирующий компонент, порождайте его на базе TCustomEdit и публи куйте только те свойства, которые считаете нужными. Разумеется, если вы поместили свойство в область private, "достать" его оттуда в потомках воз можности уже нет.

Объект изнутри Теперь, когда мы разобрались с основными определениями и механизмами ООП, настало время более подробно изучить, что представляет собой объ ект и как он работает. Ясно, что каждый экземпляр класса содержит от дельную копию всех его полей. Ясно, что где-то в его недрах есть указатели на таблицу виртуальных методов и таблицу динамических методов. А что еще там имеется? И как происходит вызов методов? Вернемся к примеру из разд. данной главы:

type TFirstClass = class Глава 1. Объектно-ориентированное программирование Integer;

FMyField2:

procedure StatMethod;

procedure VirtMethodl;

virtual;

procedure VirtMethod2;

virtual;

procedure dynamic procedure DynaMethod2;

dynamic end;

TSecondClass = procedure StatMethod;

procedure VirtMethodl;

override;

procedure DynaMethodl;

override;

end;

TFirstClass;

TSecondClass;

На 1.1 показано, как будет выглядеть внутренняя структура рассмот ренных в нем объектов.

Первое поле каждого экземпляра того или иного объекта содержит указа тель на его класс. Класс как структура состоит из двух частей. Начиная с адреса, на который ссылается указатель на класс, располагается таблица виртуальных методов. Напомним, что она содержит адреса всех виртуаль ных методов класса, включая унаследованные от предков. Длина таблиц VMT объектов и одинакова— по два элемента (8 байт). Перед таблицей виртуальных методов расположена специальная структура, содер жащая дополнительную служебную информацию. В ней содержатся данные, полностью характеризующие класс: его имя, размер экземпляра, указатели на класс-предок, имя класса и т. д. На рис. 1.1 она показана одним блоком, а ее содержимое расшифровано ниже.

Одно из полей структуры содержит адрес таблицы динамических методов класса (DMT). Таблица имеет следующий формат — в начале слово, содер жащее количество элементов таблицы;

затем — слова, соответствующие ин дексам методов. Нумерация индексов начинается с -1 и идет по убывающей.

После индексов идут собственно адреса динамических методов. Обратите внимание, что DMT объекта состоит из двух элементов, — из од ного, соответствующего перекрытому методу DynaMethodl. В случае вызова DynaMethod2 индекс не будет найден в таблице DMT и произой дет обращение к DMT Именно так экономится память при использо вании динамических методов.

В языке Object Pascal определены два оператора — is и as, неявно обра щающиеся к таблице динамических методов. Оператор is предназначен для проверки совместимости по присваиванию экземпляра объекта с заданным классом.

40 Часть I. Объектная концепция Delphi Внутренняя структура объекта Указатель на объект Информация класса Число динамических TFirstClass методов: Индекс метода Адрес метода TFirstClass.VirtMethod (-1) Индекс метода Указатель на класс Адрес метода TFirstClass (-2) Адрес метода Поле Адрес метода Поле DynaMethod Внутренняя структура объекта Указатель на объект Информация класса Число динамических TSecondClass методов: Индекс метода Адрес метода I Указатель на класс Адрес метода Адрес метода TSecondClass Поле FMyFieldi Поле FMyField Рис. 1.1. Внутренняя структура объектов и Выражение вида:

AnObject is TObjectType принимает значение True, только если объект AnObject совместим по при сваиванию с классом TObjectType, т. е. является объектом этого класса или одного из классов, порожденных от него. Кстати, определенная проверка происходит еще при компиляции: если формально объект и класс несовмес тимы, то компилятор выдаст ошибку в этом операторе.

Глава 1. Объектно-ориентированное программирование Оператор as введен в язык специально для приведения объектных типов.

С его помощью можно рассматривать экземпляр объекта как принадлежа щий к другому совместимому типу:

with as TAnotherType do...

От стандартного способа приведения типов с помощью конструкции TAnotherType использование оператора отличается наличием проверки на совместимость типов во время выполнения (как в операторе is): попытка приведения к несовместимому типу приводит к возникнове нию исключительной ситуации (см. гл. 4). После применения оператора as сам объект остается неизменным, но вызываются те его мето ды, которые соответствуют присваиваемому классу.

Очень полезным может быть оператор в методах-обработчиках событий.

Для обеспечения совместимости в 99% случаев источник события имеет тип TObject, хотя в тех же 99% случаев им является форма или другие компоненты. Поэтому, чтобы иметь возможность пользоваться их свойства ми, применяют оператор as:

(Sender as := "Thanks!";

Вся информация, описывающая класс, создается и размещается в памяти на этапе компиляции. Возникает резонный вопрос: а нельзя ли получить дос туп к ней, не создавая экземпляр объекта? Да, можно. Доступ к информа ции класса вне методов этого класса можно получить, описав соответст вующий указатель, который называется указателем на класс, или указателем на объектный тип (class reference). Он описывается при помощи зарезерви рованных слов class of. Например, указатель на класс TObject описан в мо дуле и называется type TObject = class;

= class of TObject;

Аналогичные указатели уже описаны и для других важных классов. Вы мо жете ИСПОЛЬЗОВаТЬ В СВОеЙ Программе TComponentClass, TControlClass и т. п.

Указатели на классы тоже подчиняются правилам приведения объектных типов. Указатель на класс-предок может ссылаться и на любые дочерние классы;

обратное невозможно:

type TFirst = class end;

Часть /, Объектная концепция Delphi = end;

TFirstClass = class of TFirst;

TSecondClass = class of TSecond;

var AFirst : TFirstClass;

ASecond : TSecondClass;

begin AFirst := TSecond;

{допустимо} ASecond := TFirst;

end.

С указателем на класс тесно связано понятие методов класса. Такие методы можно вызывать без создания экземпляра объекта — с указанием имени класса, в котором они описаны. Перед описанием метода класса нужно по ставить зарезервированное слово class:

type ect= class function GetSize: string end;

var AString: string;

begin AString := ect:= AString := end.

Разумеется, методы класса не могут использовать значения, содержащиеся в полях класса: ведь экземпляра-то не существует. Возникает вопрос: для чего нужны такие методы?

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

Резюме В этой главе рассмотрены основы объектно-ориентированного программи рования в Delphi. Объект обладает свойствами и методами, которые позво Глава 1. Объектно-ориентированное программирование ляют изменять значения свойств. Знание основ ООП необходимо для изу чения всех глав не только этой части, но и всех последующих. Ведь компо ненты Delphi — это объекты, размещенные в специальной библиотеке VCL.

А ни одна глава этой книги не обходится без описания возможностей тех или иных компонентов.

Рассмотренные в данной главе возможности объектов используются при создании исходного кода приложений Delphi.

ГЛАВА Библиотека визуальных компонентов VCL и ее базовые классы Все классы библиотеки визуальных компонентов произошли от группы ба зовых классов, которые лежат в основе иерархии VCL. Самый общий пре док компонентов — это класс TObject, инкапсулирующий простейший объ ект. Как известно (см. гл. 1), каждый объект наследует свойства и методы родительского класса. К объекту можно добавить новые свойства и методы, но нельзя удалить унаследованные. Объект-наследник в свою очередь может стать родительским для нового класса, который унаследует возможности всех своих предков.

Поэтому иерархия базовых классов VCL продумана чрезвычайно тщатель но — ведь на их основе создано все множество компонентов, используемых в Delphi. Особое место среди базовых классов, помимо TObject, занимают TComponent (от него происходят все компоненты) и (от него проис ходят все элементы управления).

В этой главе рассматривается иерархия базовых классов и их возможности.

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

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

Глава 2. Библиотека визуальных компонентов VCL и ее базовые классы TObject TPersistent TComponent TControl TGraphicControl TWinControl 2.1. Иерархия базовых классов VCL Благодаря механизму наследования свойств и методов, потомки базовых классов умеют "общаться" друг с другом;

работают в среде разработки, взаимодействуя с Палитрой компонентов и Инспектором объектов;

распо знаются операционной системой как элементы управления и окна.

В основе иерархии классов лежит класс TObject. Он обеспечивает выполне ние важнейших функций "жизнедеятельности" любого объекта. Благодаря ему, каждый класс получает в наследство механизмы создания экземпляра объекта и его уничтожения.

Обычно разработчик даже не задумывается о том, как объект будет создан и что необходимо сделать для его корректного уничтожения. Компоненты VCL создаются и освобождают занимаемые ресурсы автоматически. Иногда разработчику приходится создавать и удалять объекты самостоятельно. Но даже в этом случае ему достаточно вызвать соответствующие конструктор и деструктор:

var TStrings;

:= За кажущейся простотой этих операций скрывается довольно сложная реа лизация указанных процессов. Практически весь исходный код класса 46 Часть I. Объектная концепция Delphi TObject написан на ассемблере для обеспечения наибольшей эффективно сти операций, которые будут выполняться в каждом его потомке.

Кроме этого, класс TObject обеспечивает создание и хранение информации об экземпляре объекта и обслуживание очереди сообщений.

Класс TPersistent происходит непосредственно от класса TObject. Он обес печивает своих потомков возможностью взаимодействовать с другими объ ектами и процессами на уровне данных. Его методы позволяют передавать данные в потоки, а также обеспечивают взаимодействие объекта с Инспек тором объектов.

Класс TComponent является важнейшим для всех компонентов. Непосредст венно от него можно создавать любые невизуальные компоненты. Меха низмы, реализованные в классе TComponent, обеспечивают взаимодействие компонента со средой разработки, главным образом с Палитрой компонен тов и Инспектором объектов. Благодаря возможностям этого класса, ком поненты начинают работать на форме проекта уже на этапе разработки.

Класс происходит от класса TComponent. Его основное назначе ние — обеспечить функционирование визуальных компонентов. Каждый визуальный компонент, произошедший от TControi, наделяется основными признаками элемента управления. Благодаря этому, каждый визуальный компонент умеет работать с GUI (Graphic User Interface — графический ин терфейс пользователя ОС) и отображать себя на экране.

Класс TWinControi расширяет возможности разработчиков по созданию эле ментов управления. Он наследуется от класса TControi и обеспечивает соз дание оконных элементов управления.

На основе класса TWinControi создан еще один дополнительный класс — Он обеспечивает создаваемые на его основе компоненты возможностями по использованию канвы — специального объекта, предна значенного для отображения графики (подробнее о канве см. гл.

Класс TCustomControl является общим предком для целой группы классов, обеспечивающих создание различных нестандартных типов оконных (полу чающих фокус) элементов управления Windows: редакторов, списков и т. д.

Для создания неоконных (не получающих фокус) элементов управления ис пользуется TGraphicControl, ЯВЛЯЮЩИЙСЯ ПОТОМКОМ TControi.

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

Ниже мы остановимся на основных свойствах и методах базовых классов, выделяя только те, которые могут пригодиться в реальной работе. Часть из них доступна в Инспекторе объектов, часть может быть использована в про граммном коде.

Глава 2. Библиотека визуальных компонентов VCL и ее базовые классы Класс TObject Класс TObject является родоначальником всей иерархии использующихся в Delphi классов VCL. Он реализует функции, которые обязательно будет вы полнять любой объект, который может быть создан в среде разработки.

Учитывая гигантское разнообразие возможных областей применения объек тов в процессе создания приложений, можно сказать, что круг общих для всех классов операций весьма невелик.

В первую очередь — это создание экземпляра объекта и его уничтожение.

Любой объект выполняет эти две операции в обязательном порядке.

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

Указатель на экземпляр объекта передается в переменную объектного типа, которая в дальнейшем будет идентифицировать объект в программном коде приложения. В приведенном выше фрагменте кода переменная объектного типа объявлена как экземпляр типа TStrings. При создании экзем пляра этого типа конструктор create возвращает в переменную указатель на выделенную для нового объекта область памяти. Для этого применяется метод который вызывается в конструкторе авто матически:

class function Newinstance: TObject;

virtual;

Объект класса TObject обеспечивает выполнение этого процесса для любого порожденного от него объекта. А уже внутри конструктора, который унас ледован от класса TObject, можно предусмотреть инициализацию перемен ных и выполнение дополнительных операций.

Объявление конструктора выглядит следующим образом:

constructor Create;

В конструкторах потомков это объявление может перекрываться, но при необходимости вызвать конструктор предка используется оператор inherited:

constructor begin inherited Create;

end;

Для уничтожения экземпляра объекта в классе TObject предназначены мето ды Destroy И Free:

48 Часть I. Объектная концепция Delphi destructor Destroy;

virtual;

procedure Как видно из объявления, настоящим деструктором является метод Destroy.

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

( Примечание ) Обратите внимание, что обычно унаследованный конструктор вызывается в первую очередь. Это необходимо для того, чтобы выполнить все нужные опе рации при создании объекта до инициализации собственных свойств. При уничтожении объекта обычно сначала выполняются завершающие операции и только в самом конце вызывается унаследованный деструктор, чтобы выпол нить собственно уничтожение объекта.

При уничтожении объектов рекомендуется вместо деструктора использовать метод Free, который просто вызывает деструктор, но перед этим проверяет, чтобы указатель на экземпляр объекта был не пустым (не был равен Nil).

Это позволяет избежать серьезных ошибок.

Если объект является владельцем других объектов (например, форма владеет всеми размещенными на ней компонентами), то его метод Free автоматиче ски вызовет эти же методы для всех объектов. Поэтому при закрытии фор мы разработчик избавлен от необходимости заботиться об уничтожении всех компонентов.

Для освобождения занимаемой объектом памяти деструктор автоматически метод procedure Freelnstance;

virtual;

Каждый объект должен содержать некоторую информацию о себе, которая используется приложением и средой разработки. Поэтому класс TObject со держит ряд методов, обеспечивающих представление этой информации в потомках.

Метод class function Pointer;

возвращает указатель на таблицу информации времени выполнения (RTTI).

Эта информация используется в среде разработки и в приложении.

Функция class function ShortString;

возвращает имя типа объекта, которое может быть использовано для иден тификации. Например, один метод-обработчик щелчка на кнопке может работать с несколькими типами компонентов кнопок:

Глава 2. Библиотека визуальных компонентов VCL и ее базовые классы procedure begin if Sender is TBitBtn then := False;

if Sender is TSpeedButton then := True;

end;

Метод class function (const Name: string): Boolean;

позволяет определить, является ли данный объект того типа, имя которого передано в параметре Name. В случае положительного ответа функция воз вращает True.

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

Метод procedure Message);

virtual;

осуществляет обработку сообщений, поступающих объекту. Он определяет, сможет ли объект обработать сообщение при помощи собственных обработ чиков событий. В случае отсутствия таких методов сообщение передается аналогичному методу Dispatch класса-предка (если он есть).

Класс TObject имеет предопределенный обработчик событий:

procedure Message);

virtual;

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

В целом класс TObject может служить для создания на его основе некоторых простых классов для использования в приложениях.

Класс TPersistent "Persistent" в переводе с английского означает "устойчивый", "постоянный".

Что же такого постоянного в одноименном классе? Ответ таков: виртуаль ный метод procedure TPersistent);

Часть I. Объектная Delphi Этот важнейший метод осуществляет копирование содержимого одного объекта (source) в другой (self, т. е. в объект, вызвавший метод Assign).

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

FirstObject := SecondObject;

Ведь в этом случае указатель на одну область адресного пространства, со держащую экземпляр класса (объект), замещается указателем на другую об ласть адресного пространства, содержащую другой объект.

Метод Assign позволяет продублировать объект — присвоить одному объек ту значения всех свойств другого. При этом объекты не обязательно должны быть одного и того же класса;

более того, они не обязательно должны нахо диться в отношениях "родитель-потомок". Данный метод тем и хорош, что позволяет полиморфное присвоение. Конструкция позволяет скопировать содержимое картинки picture в папку обмена Win dows (объект clipboard). Какова здесь логика? Известно, что в папку обмена можно поместить растровую картинку, текст, метафайл, мультимедийные данные и т. п. Метод Assign класса переписан таким образом, чтобы обеспечить присвоение (т. е. реальное перемещение в папку обмена) всех этих данных.

procedure TPersistent);

begin if Source is TPicture then else if Source is TGraphic then else inherited end;

Для обеспечения взаимодействия потомков класса TPersistent со средой разработки предназначен метод function string;

dynamic;

Он возвращает имя объекта для передачи его в Инспектор объектов.

Для взаимодействия с потоками при загрузке и сохранении компонентов предназначен следующий метод:

procedure TFiler);

virtual;

Класс TPersistent никогда не используется напрямую, от него порождаются потомки, которые должны уметь передавать другим объектам значения сво их свойств, но не являться при этом компонентами.

Глава 2. Библиотека визуальных компонентов VCL и ее базовые классы Класс Класс TComponent является предком всех компонентов VCL. Он используется в качестве основы для создания невизуальных компонентов и реализует основные механизмы, которые обеспечивают функционирование любого компонента. В нем появляются первые свойства, которые отображаются в Инспекторе объектов. Это свойство property Name:

Оно содержит имя экземпляра компонента, которое используется для иден тификации компонента в приложении.

( Примечание Тип TComponentName представляет собой обычную строку:

type TComponentName = type string;

property Tag:

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

Для компонентов существует своя иерархия, поэтому в классе введен меха низм учета и управления компонентами, для которых данный компонент является владельцем. Свойства и методы, которые отвечают за управление, приведены в табл. 2.1.

Таблица Свойства и методы для управления списком компонентов Свойство (метод) Описание property Components [Index: спи TComponent;

сок указателей всех компонентов, для которых данный компонент является владельцем property Integer;

Число подчиненных компонентов property Owner: TComponent;

Указывается, какой компонент является владельцем данного property Индекс данного компонента в списке владельца Часть Объектная концепция Delphi Таблица 2.1 (окончание) Свойство (метод) Описание procedure (AComponent: Вставляет компонент ;

в procedure Удаляет компонент AComponent TComponent);

из списка procedure s t r i ng) : Осуществляет поиск компонента имени procedure DestroyComponents;

Предназначен для уничтожения всех компонентов, подчиненных данному Очень важное свойство type TComponentState = set of (csLoading, csReading, csDestroying, csDesigning, csAncestor, csFixups, csFreeNotification, property TComponentState;

дает представление о текущем состоянии компонента. В табл. 2.2 описаны возможные состояния компонента. Состояние может измениться в результа те получения компонентом некоторого сообщения, действий разработчика, выполнения акции и т. д. Это свойство активно используется средой разра ботки.

Таблица 2.2. Возможные состояния компонента Состояние Описание csLoading Устанавливается при загрузке компонента из потока csReading Устанавливается при чтении значений свойств из потока csWriting Устанавливается при записи значений свойств в поток csDestroying Устанавливается при уничтожении компонента csDesigning Состояние разработки. Устанавливается при работе с фор мой во время разработки csAncestor Устанавливается при переносе компонента на форму. Для перехода в это состояние должно быть уже установлено состояние csDesigning csUpdating Устанавливается при изменении значений свойств и ото бражения результата на форме-владельце. Для перехода в это состояние должно быть уже установлено состояние csAncestor Глава 2. Библиотека визуальных компонентовVCL и ее базовые классы Таблица 2.2 (окончание) Состояние Описание csFixups Устанавливается, если компонент связан с компонентом другой формы, которая еще не загружена в среду разра ботки Если это состояние устанавливается, другие компоненты, связанные с данным, уведомляются о его уничтожении Определяет компонент верхнего уровня в иерархии. Ис пользуется для обозначения корневого объекта в развора чивающихся свойствах Определяет корневой компонент на этапе разработки Для обеспечения работы механизма действий (см. гл. 8) предназначен метод function TBasicAction): Boolean;

dynamic;

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

На уровне класса TComponent обеспечена поддержка СОМ-интерфейсов IUnknown И IDispatch.

Через свойство property IUnknown;

вы можете обеспечить применение методов этих интерфейсов.

Таким образом, класс TComponent имеет все для использования в качестве предка, для создания собственных невизуальных компонентов.

Базовые классы элементов управления Вслед за классом TComponent в иерархии базовых классов (см. рис. 2.1) рас полагается группа из трех классов, которые обеспечивают создание различ ных визуальных компонентов. Визуальные компоненты — это разнообразные стандартные для Windows и специальные (созданные разработчиками In prise) элементы управления.

Понятно, что визуальные компоненты должны уметь отобразить себя на экране монитора и реагировать на целый ряд новых событий (реакция на мышь и клавиатуру, движение курсора и т. д.). Для этого в них встроен спе циальный механизм, обеспечивающий взаимодействие компонентов с гра фической подсистемой ОС (GUI).

54 Часть I. Объектная концепция Delphi Существует несколько типов элементов управления, которые существенно отличаются по своим возможностям и поведению. Каждому типу соответст вует собственный класс иерархии.

Класс TWinControi обеспечивает использование в Delphi оконных элементов управления. Главное отличие оконного элемента управления от любых дру гих — наличие дескриптора окна Дескриптор окна — это специальный идентификатор, который операционная система присваивает всем объектам, которые должны обладать свойствами окна. Если элемент управления имеет дескриптор окна, то он должен уметь выполнять следующие операции:

получать и передавать фокус управления во время выполнения приложе ния;

воспринимать управляющие воздействия от мыши и клавиатуры;

уметь размещать на себе другие элементы управления.

Оконными элементами управления являются не только формы, но и прак тически все стандартные элементы управления Windows: и списки, и редак торы имеют дескриптор окна.

Итак, все визуальные компоненты происходят от класса TWinControi. Одна ко нестандартные элементы управления имеют еще одного общего предка.

Это класс Он существенно облегчает использование эле ментов управления, т. к. позволяет управлять отрисовкой компонента пу тем использования специального класса TCanvas — так называемой канвы (см. гл. 11) вместо обращения к системным функциям GUI.

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

Класс TControi Класс TControi является базовым для всех визуальных компонентов и ин капсулирует механизмы отображения компонента на экране. В нем исполь зуется множество новых свойств и методов. Недаром в Delphi в Инспекторе объектов появилась категоризация методов и свойств (рис. 2.2). Большинст во ИЗ НИХ ВВОДЯТСЯ раз В TControi И TWinControi.

Рассмотрим только важнейшие свойства и методы по категориям.

Группа свойств Visual.

Местоположение и размер элемента управления Для определения местоположения и размеров визуального компонента вве дены два опубликованных свойства для задания координат левого верхнего угла:

Глава 2. Библиотека визуальных компонентов VCL и ее базовые классы Properties | ", IS Linkage Locale 2.2. Категории свойств визуального компонента.

Для представления их в таком виде нужно отметить флажок By Category в пункте меню Arrange всплывающего меню Инспектора объектов property Top: Integer;

property Left: Integer;

и два опубликованных свойства для определения размеров:

property Height: Integer;

property Width: Integer;

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

property ClientOrigin: TPoint;

содержит координаты левого верхнего угла элемента управления в системе координат экрана. Координаты любой точки можно пересчитать в экранные при помощи метода function Point: TPoint): TPoint;

и наоборот:

function Point: TPoint): TPoint;

Для приведения компонента в соответствие текущим значениям указанных выше свойств используется метод procedure AdjustSize;

dynamic;

56 Часть I. Объектная концепция Delphi Параметры рабочей области компонента определяются следующими свой ствами:

property Integer;

определяет высоту рабочей области в пикселах.

property Integer;

определяет ширину рабочей области в пикселах.

property ClientRect: TRect;

значение которого есть не что иное, как о, ClientHeight).

Кому-то будет удобнее пользоваться этим свойством.

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

Функция function GetClientOrigin: TPoint;

virtual;

возвращает координаты левого верхнего угла рабочей области.

Функция function GetClientRect: TRect;

virtual;

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

Выравнивание элемента управления Для выравнивания компонента в рабочей области его владельца (обычно это форма) применяется свойство property Align: TAlign;

Тип TAlign объявлен следующим образом:

type TAlign = (alNone, alRight, alClient);

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

property Anchors: TAnchors;

type TAnchors = set of TAnchorKind;

type TAnchorKind = (akTop, akRight, обеспечивает фиксацию элемента управления по сторонам владельца. "Якорь" можно установить по одной, двум, трем или четырем сторонам. При зада Глава 2. Библиотека визуальных компонентов VCL и ее базовые классы нии якоря по любой расстояние между стороной и элементом управления сохраняется неизменным. Комбинируя якоря для сторон, мож но добиться различных вариантов поведения компонента при изменении размеров владельца.

Если по вертикали или горизонтали якорь не установлен вообще, то при изменении размеров владельца компонент остается на том же месте с уче том изменившегося размера.

Если по вертикали или горизонтали установлены оба якоря, то при измене нии размеров владельца размер элемента управления изменяется таким об разом, чтобы расстояния до сторон владельца остались неизменными.

property AutoSize: Boolean;

обеспечивает изменение размеров компонента в соответствии с размера ми его содержимого (текста, изображения, списка, иерархического дерева и т. д.).

Внешний вид элемента управления Для определения цвета компонента используется свойство property Color: TColor;

При нахождении указателя мыши над компонентом его изображение может изменяться в соответствии со значением свойства property Cursor: TCursor;

Для текста компонента шрифт обычно задается свойством property Font: TFont;

Сложный класс TFont, задающий все характеристики шрифта, подробно рассматривается в гл. 10.

property Boolean;

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

Сам текст задается свойством type TCaption = string;

property Text: TCaption;

Длину текста можно определить при помощи функции function GetTextLen: Integer;

Она возвращает число символов в тексте.

58 Часть I, Объектная концепция Delphi Элемент управления можно сделать видимым или невидимым. Для этого применяется свойство property Visible: Boolean;

Этих же результатов можно достичь методами (компонент видим) и Hide (компонент невидим).

Опубликованное свойство property Hint: string;

содержит текст ярлыка — однострочной подсказки, которая появляется в маленькой рамке при остановке мыши на элементе управления.

Для управления ярлыком используется свойство property ShowHint: Boolean;

При значении True ярлык начинает работать, при значении False ярлык выключается.

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

Текущее состояние элемента управления определяется свойством type TControlState set of csPalette, csReadingState, csFocusing, csCreating, csPaintCopy, csDocking,);

property Controistate: TControlState;

Описание возможных состояний элемента управления представлено в табл. 2.3.

Таблица 2.3. Возможные состояния элемента упра Состояние Описание csLButtonDown Левая кнопка мыши нажата, но еще не отпущена. Исполь зуется для реализации события OnMouseDown csClicked Левая кнопка мыши нажата, но еще не отпущена. Исполь зуется для реализации события Onclick csPalette Состояние соответствует режиму изменения палитры. Это реакция на сообщение W M _ P A L E T T C H A N G E D csReadingState Осуществляется чтение значений свойств из потока (см.

табл. 5.1) csAlignmentNeeded Осуществляется выравнивание компонента визуальных компонентов ее базовые классы Глава 2. Библиотека VCLи Таблица 2.3 (окончание) Состояние Описание ing Элемент управления получает фокус csCreating Элемент управления и его дочерние элементы создаются csPaintCopy Отрисовывается копия элемента управления Элемент управления выполняет нестандартные операции отрисовки, заданные разработчиком Указатель на объект элемента управления уничтожается csDocking Элемент управления находится в режиме присоединения В зависимости от совокупности установленных свойств, элемент управления может соответствовать одному из возможных стилей, который задается type TControlStyle = set of (csAcceptsControls, csSetCaption, csDoubleClicks, csFixedHeight, csNoDesignVisible, csReplicatable, csNoStdEvents, csReflector, csActionClient, property ControlStyle: TControlStyle;

Доступность элемента управления в целом определяется свойством property Enabled: Boolean;

При значении True элемент управления полностью работоспособен. При значении False элемент управления неактивен и отображается серым цветом.

Для получения контекста устройства HDC элемента управления используется метод function HWnd): HDC;

virtual;

Набор свойств и методов класса обеспечивает функционирова ние механизма перетаскивания (Drag-and-Drop) и механизма присоедине ния (Drag-and-Dock).

Связь с родительским элементом управления Механизм связывания визуального компонента с родительским компонен том (владельцем) позволяет автоматически задавать для нового элемента управления некоторые свойства, отвечающие за его внешний вид (см. вы ше). В результате все дочерние элементы управления для одного родитель ского (формы, панели) будут выглядеть одинаково оформленными.

60 Часть I. Объектная концепция Delphi Родительский компонент задается свойством property Parent: TWinControl;

Для каждого дочернего элемента управления можно задать значения не скольких свойств:

property ParentBiDiMode: Boolean;

property ParentColor: Boolean;

property ParentFont: Boolean;

property ParentShowHint: Boolean;

Каждое из них управляет одной характеристикой визуализации элемента.

Метод function HasParent: Boolean;

override;

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

В классе TControl впервые появляются методы-обработчики событий, кото рые обеспечивают передачу в элемент действия мыши, присоединение и перетаскивание.

Класс TWinControl Класс TWinControl обеспечивает создание и использование оконных элемен тов управления (см. выше). Напомним, что оконный элемент управления имеет системный дескриптор окна hwnd. Однако оконными элементами яв ляются не только формы и диалоги, но и большинство стандартных элемен тов управления.

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

Дескриптор окна содержится в свойстве property Handle: HWND;

При создании оконного элемента управления вызывается метод procedure virtual;

который заполняет структуру TCreateParams необходимыми значениями:

type TCreateParams = record Caption: PChar;

Style: DWORD;

Глава 2. Библиотека визуальных компонентов VCL и ее базовые классы ExStyle: DWORD;

X, Y: Integer;

Width, Height: Integer;

Pointer TWndClass;

of Char;

end;

Для создания дескриптора окна для элемента управления используется метод procedure CreateHandle;

virtual;

Операционная система создает дескриптор окна только вместе с самим окном. Поэтому метод CreateHandle только создает окно, а для присваива ния свойству Handle значения дескриптора окна вызывает метод createwnd.

Для передачи фокуса между элементами управления на одной форме часто используется клавиша Порядок перемещения фокуса между элемен тами определяется свойством type TTabOrder = property TabOrder: TTabOrder;

В первую очередь фокус передается компоненту с минимальным значением свойства. Далее — по возрастанию значения. При переносе компонента на форму это значение задается автоматически в соответствии с числом ком понентов на форме.

Компонент можно заставить не откликаться на клавишу Для этого свойству property TabStop: Boolean;

необходимо присвоить значение False.

Для передачи фокуса прямо элементу управления применяется метод procedure SetFocus;

virtual;

Чтобы узнать, имеет ли элемент управления фокус, в настоящее время ис пользуется метод function Focused: Boolean;

dynamic;

Все оконные элементы имеют рамку по контуру (впрочем, она может быть не видна). Ее параметры задаются группой свойств:

property TBevelEdges;

задает, какие стороны входят в рамку;

62 Часть I. Объектная концепция Delphi property TBevelCut;

property BevelOuter: TBevelCut;

задают внешний вид рамки;

property TBevelKind;

определяет стиль рамки;

property задает размер рамки.

Свойство property Brush: TBrush;

определяет параметры кисти (цвет и заполнение), которой рисуется фон элемента.

Оконный элемент может содержать другие компоненты. Для управления ими применяется индексированный список указателей, представляющих property Integer]: TControl;

Общее число дочерних элементов управления содержится в свойстве property ControlCount: Integer;

Внешний вид оконного элемента определяется свойством property Ctl3D: Boolean При значении True элемент управления имеет трехмерный вид. Иначе эле мент Для вызова темы контекстной помощи для конкретного элемента управле ния предназначено свойство type THelpContext = property HelpContext: THelpContext;

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

В классе добавлена возможность использования редакторов способа ввода (Input Method Editor, IME). Такие редакторы позволяют при способить стандартную раскладку клавиатуры для символьных языков для ввода нестандартных символов (иероглифов и т. д.). Редакторы IME пред ставляют собой специально устанавливаемое в операционной системе про граммное обеспечение (ПО). Имя такого редактора задается в свойстве Режим работы редактора определяется свойством В классе TwinControi добавлено еще несколько методов-обработчиков собы тий, обеспечивающих реакцию на ввод с клавиатуры, получение и потерю фокуса.

Глава 2. Библиотека визуальных компонентов VCL и ее базовые классы Класс Класс TCustomControl предназначен для создания на его основе нестандарт ных оконных элементов управления. Процесс визуализации в нем упрощен за счет использования специального класса TCanvas, инкапсулирующего канву (см. гл. 11).

Доступ к канве осуществляется через свойство property Отрисовка элемента управления осуществляется методом procedure HDC);

override;

после получения сообщения Возможности этого класса унаследовали классы TGroupBox, TStringGrid И Т. Д.

Класс TGraphicControl Класс TGraphicControl предназначен для создания на его основе визуальных компонентов, не получающих фокус в процессе выполнения приложения.

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

Для визуализации элементов управления на основе этого класса использует ся канва, инкапсулированная в классе TCanvas.

Доступ к канве осуществляется через свойство property Отрисовка элемента управления осуществляется методом procedure HDC);

override;

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

ГЛАВА Обработка исключительных ситуаций Любому, кто писал более или менее сложные программы, интуитивно ясно, что такое обработка исключительных ситуаций (ИС). Всякое взаимодейст вие с операционной системой на предмет получения ресурсов — места на диске, в памяти, открытие файла — может завершиться неудачно. Любое вычисление может закончиться делением на ноль или переполнением. До полнительный фактор возникновения исключительных ситуаций содержит ся в данных, к которым могут обращаться программы. Особенно это акту ально в приложениях баз данных.

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

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

При разработке приложений в Delphi программист имеет возможность ис пользовать несколько механизмов, обеспечивающих обработку исключи тельных ситуаций. Это и специальные операторы языка Object Pascal, и классы, предназначенные для программирования реакции на ошибки.

Поэтому эта глава посвящена... нет, не тому, как писать безошибочно;

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

Исключительная ситуация как класс Что же такое исключительная ситуация? Интуитивно понятно, что это — некое нештатное событие, могущее повлиять на дальнейшее выполнение программы. Если вы ранее писали в среде Turbo Pascal или подобной, то вы Глава 3. Обработка исключительных ситуаций наверняка пытались избежать таких ситуаций, вводя многочисленные про верки данных и кодов возврата функций. От этого громоздкого кода можно раз и навсегда избавиться, взяв на вооружение механизм, реализованный в Delphi.

Компилятор Delphi генерирует код, который перехватывает любое такое нештатное событие, сохраняет необходимые данные о состоянии програм мы, и выдает разработчику... Что можно выдать в объектно-ориентирован ном языке программирования? Конечно же, объект. С точки зрения Object Pascal исключительная ситуация — это объект.

Вы можете получить и обработать этот объект, предусмотрев в программе специальную языковую конструкцию (try. Если такая конструкция не предусмотрена, все равно исключение будет обработано — в недрах VCL есть соответствующие обработчики, окружающие все потенциально опасные места.

Чем же различаются между собой исключительные ситуации? Как отличить одну исключительную ситуацию от другой? Поскольку это объекты, они отличаются классом (объектным типом). В модуле SYSUTILS.PAS описан объектный тип Exception. Он является предком для всех других объектов — исключительных ситуаций. Вот он:

Exception = private string;

Integer;

public constructor string);

constructor Msg: string;

const Args: array of const);

constructor Integer);

overload;

constructor PResStringRec);

overload;

constructor Integer;

const Args: array of const);

overload;

constructor PResStringRec;

const Args:

array of const);

overload;

constructor Msg: string;

Integer);

constructor Msg: string;

const Args:

array of const;

AHelpContext: Integer);

constructor Integer;

AHeipContext: Integer);

overload;

constructor PResStringRec;

AHelpContext: Integer);

overload;

constructor PResStringRec;

const Args: array of const;

AHelpContext: Integer);

overload;

3 Зак. Часть I. Объектная концепция Delphi constructor Integer;

const Args: array of const;

AHelpContext: Integer);

overload;

property HelpContext: Integer read FHelpContext write FHelpContext;

property Message: string read write FMessage;

end;

ExceptClass = class of Exception;

Как видно из приведенного описания класса Exception, у него имеется две надцать (!) конструкторов, позволяющих задействовать при создании объек та текстовые строки из ресурсов приложения (имя включает строку Res), форматирование текста (включает связь с контекстом справочной сис темы (включает Help).

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

If > Limit then raise allocate more than %d bytes',[Limit]);

Если в названии присутствует подстрока Res, это означает, что текст сооб щения будет загружаться из ресурсов приложения. Это особенно полезно при создании локализованных версий программных продуктов, когда нужно сменить язык всех сообщений, ничего не компилируя заново.

И наконец, если в названии фигурирует подстрока Help, то такой конструк тор инициализирует свойство HelpContext создаваемого объекта. Естествен но, система помощи должна быть создана и в ней должна иметься статья, связанная с этим контекстом. Теперь пользователь может затребовать по мощь для данной ситуации, скажем, нажав клавишу в момент показа сообщения об Тип Exception порождает многочисленные дочерние типы, соответствующие часто встречающимся случаям ошибок ввода/вывода, распределения памяти и т. п. Дерево исключительных ситуаций Delphi 7 приведено на рис. 3.1.

Заметим, что тип Exception и его потомки представляют собой исключение из правила, предписывающего все объектные типы именовать с буквы Т.

ПОТОМКИ Exception начинаются С Е, EZeroDivide.

Для экономии места потомки нескольких важных объектов не показаны.

Ниже приведены табл. 3.1—3.3, содержащие описания этих групп исключи тельных ситуаций.

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

Глава 3. Обработка исключительных ситуаций В Exception •,, Scope | Inheritance | | *,,, FMessage, Public, • Create, • CrealeFmt CrealeHelp, ! i i CreateRes, EHeapException • i, CreateResFmtHelp, CreateResFmtHelp, CreateResHelp, i, \.

, Message i LJ Public •,,, З.1. Дерево объектов исключительных ситуаций Delphi Таблица Исключительные ситуации при работе с памятью (порождены ОТ EHeapException) Тип Условие возникновения Недостаточно места в куче (памяти) Нехватка системных ресурсов Недопустимый указатель (обычно ni l ) Таблица 3.2. Исключительные ситуации целочисленной математики (порождены ОТ tError) Тип Условие возникновения Попытка деления на ноль (целое число) EDivByZero Число или выражение выходит за допустимый диапазон ERangeError Целочисленное переполнение Часть I. Объектная концепция Delphi Таблица 3.3. Исключительные ситуации математики с плавающей точкой (порождены ОТ thError) Тип Условие возникновения Неверная операция EZeroDivide Попытка деления на ноль Переполнение с плавающей точкой Исчезновение порядка Неверный аргумент математических функций Для этого используется оператор raise, за которым в качестве параметра должен идти экземпляр объекта типа Exception. Обычно сразу за оператором следует конструктор класса ИС:

raise но можно и разделить создание и возбуждение исключительной ситуации:

var E: EMathError;

begin Е := [");

raise E;

end;

Оператор raise передает созданную исключительную ситуацию ближайшему блоку try. (см. ниже).

if С = 0 then raise на else А := В/С;

Самостоятельная инициализация ИС может пригодиться при программиро вании реакции приложения на ввод данных, для контроля значений пере менных и т. д. В таких случаях желательно создавать собственные классы ИС, специально приспособленные для ваших нужд. Также полезно исполь зовать специально спроектированные исключительные ситуации при созда нии собственных объектов и компонентов. Так, многие важнейшие классы VCL — списки, потоки, графические объекты — сигнализируют о своих (или ваших?) проблемах созданием соответствующей ИС — EListError, EPrinter И Т. Д.

Самый важный отличительный признак объекта Exception — это все же класс, к которому он принадлежит. Именно факт принадлежности возник Глава 3. Обработка исключительных ситуаций шей ИС к тому или иному классу говорит о том, что случилось. Если же нужно детализировать проблему, можно присвоить значение свойству Message. Если и этого мало, можно добавить в объект новые поля. Так, в ИС (ошибка ввода/вывода) есть поле значение ко торого соответствует произошедшей ошибке — запрету записи, отсутствию или повреждению файла и т. д.

try except on E: EinOutError do case of {=2}: не найден ! ' ) ;

{=5}:

{=112}: переполнен!');

end;

end;

Впрочем, ИС EinOutError возникают только тогда, когда установлена опция компилятора {$IOCHECKS ON} (ИЛИ иначе В противном случае провер ку переменной (известной еще Turbo Pascal) нужно делать са мому.

Еще более "продвинутый" пример — ИС EDBEngineError. Она имеет свойства и свойство-массив Errors: одна операция с базой данных может породить сразу несколько ошибок.

Защитные конструкции языка Object Pascal Для работы с объектами исключительных ситуаций существуют специаль ные конструкции языка Object Pascal— блоки except и try.

Они контролируют выполнение операторов, помещенных внутри блока до ключевого слова except или finally. В случае возникновения исключитель ной ситуации штатное выполнение вашей программы немедленно прекра щается, и управление передается операторам, идущим за указанными клю чевыми словами. Если в вашей процедуре эти блоки отсутствуют, управле ние все равно будет передано ближайшему блоку, внутри которого возникла ситуация. А уж внутри их предостаточно.

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

70 Часть I. Объектная концепция Delphi Блок try..except Для реакции на конкретный тип ситуации применяется блок Синтаксис его следующий:

try <Оператор> except on EExceptionl do < Оператор обработки ИС типа EExceptionl >;

on EException2 do < Оператор else { } <Оператор> (обработчик прочих ИС} end;

Выполнение блока начинается с секции try. При отсутствии исключитель ных ситуаций только она и выполняется. Секция except получает управле ние в случае возникновения ИС. После обработки происходит выход из за щищенного блока, и управление обратно в секцию try не передается;

вы полняются операторы, стоящие после end.

Если вы хотите обработать любую ИС одинаково, независимо от ее класса, вы можете писать код прямо между операторами except и end. Но если об работка отличается, здесь можно применять набор директив on. опреде ляющих реакцию приложения на определенную ситуацию. Каждая директи ва связывает ситуацию (on...), заданную своим именем класса, с группой операторов (do...).

U := 220.0;

R := 0;

try I := / R;

except on EZeroDivide do замыкание!');

end;

В этом примере замена if. на try. может быть, не дала оче видной экономии кода. Однако если при решении, допустим, вычислитель ной задачи проверять на возможное деление на ноль приходится не один, а множество раз, то выигрыш от нового подхода неоспорим — достаточно одного блока try. на все вычисления.

При возникновении ИС директивы просматриваются последовательно, в порядке их описания. Каждый тип исключительной ситуации, описанный Глава 3. Обработка исключительных ситуаций после ключевого слова on, обрабатывается именно этим блоком: только то, что предусмотрено в нем, и будет являться реакцией на данную ситуацию.

Если при этом обработчик родительского класса стоит перед дочерним, по следний никогда не получит управления.

try i:=l;

j:=0;

k:=i div except on do on EDivByZero do end;

В этом примере, хотя в действительности будет иметь место деление на ноль (EDivByZero), вы увидите сообщение, соответствующее родительскому классу Но стоит поменять две конструкции on. местами, и все придет в норму.

Если возникла ситуация, не определенная ни в одной из директив, выпол няются те операторы, которые стоят после else. Если и их нет, то ИС счи тается не обработанной и будет передана на следующий уровень обработки.

Этим следующим уровнем может быть другой оператор кото рый содержит в себе данный блок.

С Примечание Мы детально обсудили, что и как помещают в блок t r y. Но в ряде случаев можно... не размещать там ничего. Пустой блок применяют, когда хотят просто проигнорировать возникновение некоторых ситуаций. Скажем, у вас в программе предусмотрена некая обработка данных, которая может завер шиться неудачно, но это не повлияет на дальнейшую работу, и пользователь может об этом не знать. В этой ситуации вполне уместно изолировать ее в пус том блоке t ry. Важно только не поместить туда больше кода, чем нужно — иначе "с водой можно выплеснуть и ребенка".

Если вы не предусмотрели блоков обработки ИС в своем коде, это не должно привести к аварийному завершению всего приложения. Все места в VCL, где управление передается коду разработчика (в том числе, конечно, все обработчики событий всех компонентов), заключены в такие блоки. Но, увы, в Borland не знают о конкретных проблемах вашей программы, и мак симум, что они могут сделать для вас, — это проинформировать о типе и месте возникновения ИС. Стандартная обработка подразумевает вывод на экран панели текстового сообщения (из свойства с ука занием типа ошибки. Можно получить и развернутую информацию с име нем модуля и адреса, она имела место (рис. 3.2).

Часть I. Объектная концепция Delphi Exception in modul e Project 1 at Floating point division by zero.

З.2. Типовое окно сообщения об ошибке Для этого нужно вызвать процедуру procedure ExceptAddr: Pointer);

имеющуюся в модуле SYSUTILS.PAS.

Если предусмотренной вами обработки ИС недостаточно, то можно про должить ее дальше программно при помощи оператора raise.

Этот оператор уже встречался нам при описании создания пользовательских ИС. Там за ним следовал вызов конструктора ИС. Здесь же конструктор опущен: возбуждаться будет уже существующий объект ИС, приведший нас в блок:

sl:= TStringList.Create;

try except raise;

end;

В этом примере в случае возникновения исключительной ситуации создан ный список строк должен быть уничтожен. Сама же обработка предоставля ется "вышестоящим инстанциям".

Блок Параллельно с блоком except в языке существует и try. Он соответствует случаю, когда необходимо возвратить выделенные программе ресурсы даже в случае аварийной ситуации. Синтаксис блока таков:

try <Оператор> <Оператор> Глава 3. Обработка исключительных ситуаций finally end;

Смысл этой конструкции можно описать одним предложением: операторы, стоящие после finally, выполняются всегда.

Следующие за try операторы исполняются в обычном порядке. Если за это время не возникло никаких ИС, далее следуют те операторы, которые стоят после finally. В случае, если между try и finally произошла ИС, управле ние немедленно передается на операторы после finally, которые называют ся кодом очистки. Допустим, вы поместили после try операторы, которые должны выделить вам ресурсы системы (дескрипторы блоков памяти, фай лов, контекстов устройств и т. п.). Тогда операторы, освобождающие их, следует поместить после finally, и ресурсы будут освобождены в любом случае. Блок try. как можно догадаться, еще называется блоком защиты ресурсов.

Важно обратить внимание на такой факт: данная конструкция ничего не де лает с самим объектом — исключительной ситуацией. Задача try. — только прореагировать на факт нештатного поведения программы и проде лать определенные действия. Сама же ИС продолжает "путешествие" и во прос ее обработки остается на повестке дня.

Блоки защиты ресурсов и обработчики ИС, как и другие блоки, могут быть вложенными. В этом простейшем примере каждый вид ресурсов системы защищается в отдельном блоке:

try try Allocate2ndResource;

finally end;

finally end;

Можно также вкладывать обработчики друг в друга, предусмотрев в каждом специфическую реакцию на ту или иную ошибку:

var : Integer;

begin 74 Часть I. Объектная концепция Delphi i := j :- 1 - i;

try к := 1 div i;

try к := 1 div j;

except On EDivByZero do end;

except On EDivByZero do 2 :

end;

end;

Но все же идеально правильный случай — это сочетание блоков двух типов.

В один из них помещается общее (освобождение ресурсов в finally), в дру гой — особенное (конкретная реакция внутри except).

Использование исключительных ситуаций Если произошла ошибка и возбуждена исключительная ситуация, то она будет обрабатываться по такому алгоритму:

1. Если ситуация возникла внутри блока то там она и будет обработана. Если ИС "продвинута" дальше при помощи оператора raise, а также если она возникла в блоке try. обработка продол жается.

2. Если программистом определен обработчик события то он получит управление. Обработчик объ явлен следующим образом:

TExceptionEvent = procedure (Sender: TObject;

E: Exception) of object;

3. Если программист никак не определил реакцию на ИС, то будет вызван стандартный метод showException, который сообщит о классе и месте возникновения исключительной ситуации.

Пункты 2 и 3 реализуются в методе Собст венно, выглядят они следующим образом:

if not (ExceptObject is EAbort) then if then Глава 3. Обработка исключительных ситуаций else Обработчик onException нужен, если требуется выполнять одно и то же дей ствие в любой исключительной ситуации, возникшей в вашем приложении.

К примеру, назвать себя, указать координаты для обращения или предупре дить, что это еще бета-версия.

program uses Forms, //добавлено вручную — там описан класс Exception Dialogs, in {Forml};

{$R *.RES} type TExceptClass = class public procedure TObject;

end;

procedure TObject;

begin исключительная ситуация ' + + ' : ' + + с разработчиками тел. 222-33-44');

end;

begin with do begin := GlobalExceptionHandler;

Forml);

Run;

Free;

end;

end.

Здесь класс TExceptClass создается только для того, чтобы быть носителем метода Обработчик любого события — метод, и он должен относиться к какому-либо объекту. Поскольку он здесь нужен еще до ини циализации форм приложения и других его составных частей, то и объект класса TExceptClass создается первым. Теперь пользователь знает, что бла 76 Часть I. Объектная концепция Delphi годарить за неожиданности нужно по указанному в сообщении об ошибке телефону разработчиков.

( Примечание Есть и более простой способ присвоить обработчик событию Application.OnException. Для этого поместите на форму компонент типа TApplicationEvents (страница Additional Палитры компонентов), роль которо го — предоставление "визуального" доступа к свойствам невизуального объек та TApplication. Среди его событий есть и OnException.

Но как "пощупать" переданный при исключительной ситуации объект?

Обычная конструкция on EExceptionType do...

указывает на класс объекта, но не на конкретный экземпляр. Если во время обработки требуется доступ к свойствам этого экземпляра, его нужно по именовать внутри указав перед именем класса некий идентифи катор:

on EZD: EZeroDivide do := на ноль!';

Здесь возникшее исключение выступает под именем EZD. МОЖНО изменить его свойства и отправить дальше:

var APtr : Pointer;

:

try APtr := Forml;

with as do;

except on EZD: do + Raise;

{ теперь обработка будет сделана в другом месте } end;

Но как поименовать исключительную ситуацию, не попавшую ни в одну из директив Или, может быть, в вашем обработчике вообще нет on. а поработать с объектом надо? Описанный выше путь здесь не под ходит. Для этих случаев есть пара системных функций Exceptobject и ExceptAddr. К сожалению, эти функции инициализируются только внутри конструкции в работать с объектом— исключи тельной ситуацией не представляется возможным.

Протоколирование исключительных ситуаций Часто нужно иметь подробный материал для анализа причин возникнове ния Разумно было бы записывать все данные о них в файл, чтобы по том прогнозировать ситуацию. Такой подход важен для программ, которые Глава 3. Обработка исключительных ситуаций так или иначе будут от разработчика: в случае возникновения непредвиденной ситуации это позволит ответить на вопросы "кто виноват?" и "что делать?". В следующем примере предложен вариант реализации про токолирования const LogName : string = 'c:\appexc.log';

procedure LogException;

var fs: : : of char;

begin if then fmOpenReadWrite else := fmCreate;

fs := fs.Seek(0,soFromEnd);

ExceptionErrorMessage StrCat(Buf,#13#10);

end;

procedure var real;

begin try try z := x/y;

except LogException;

raise;

end;

except on E:EIntError do on E:EMathError do end;

end;

Здесь задачу записи информации об ИС решает процедура LogException.

Она открывает файловый поток и пишет туда информацию, отформатиро ванную ПОМОЩИ уже упоминавшейся функции ExceptionErrorMessage.

В качестве ее параметров выступают значения функций Exceptobject и ExceptAddr. К сформированной строке добавляется время возникновения ИС. Для каждого защищаемого блока кода создаются две вложенные конст рукции try. Первая, внутренняя — для вас;

в ней ИС протоколиру 78 Часть I. Объектная концепция Delphi ется и продвигается дальше. Внешняя — для пользователя;

именно в ней проводится анализ типа ИС и готовится сообщение.

В Object Pascal существует и расширенный вариант употребления оператора raise:

raise объекта типа Exception> [at <адрес>] Естественно, объектный тип должен быть порожден от Exception. что в таком типе ничего не переопределено, не столь важно — главное, что в обработчике ИС можно отследить именно этот тип.

ELoginError = If > then raise регистрации пользователя');

Конструкция at <адрес> используется для того, чтобы изменить адрес, к которому привязывается возникшая ИС, в пределах одного блока обра ботки Коды ошибок в исключительных ситуациях Если ваше приложение уже готовится к продаже, если вы планируете его техническую поддержку, то пора задуматься о присвоении числовых кодов ошибкам, возникающим в нем. Сообщение типа "Exception in module at addr $0781BAB0" ГОДИТСЯ ДЛЯ разработчика, вателя же оно повергнет в полный ступор. Если он позвонит в вашу службу техподдержки, то, скорее всего, не сможет ничего объяснить. Гораздо гра мотнее дать ему уже "разжеванную" информацию и, в том числе, числовой Один из путей решения этой проблемы — размещение сообщений об ошибках в ресурсах программы. Если же вы еще делаете и несколько на циональных версий программы на разных языках, то этот путь — единст венный.

"Классический" способ поместить текст в файл ресурсов — 3-этапный:

1. Создается исходный файл ресурсов с расширением в который поме щаются необходимые строки с нужными номерами.

2. Файл обрабатывается компилятором ресурсов brcc32.exe (находится в пап ке bin в структуре папок Delphi). На выходе образуется одноименный файл с расширением res.

3. Файл включается в программу указанием директивы например {$R Чтобы совместно использовать константы-номера ошибок в файле ресурсов и в коде на Delphi, вынесем их в отдельный включаемый файл с расширени ем inc:

Глава 3. Обработка исключительных ситуаций const = 1000;

FileOpenError = IOError + 1;

FileSaveError = IOError + 2;

InternetError = 2000;

NoConnectionError = InternetError + 1;

ConnectionAbortedError = InternetError + 2;

Взглянув на файл, вы увидите, что ошибки в нем сгруппированы по катего риям. Советуем вам поступить так же, разделив константы категорий проме жутком в 1000 или даже 10 000.

Сам файл ресурсов может выглядеть так:

"strids.inc" STRINGTABLE { FileOpenError, "File Open Error" FileSaveError, "File Save Error" NoConnectionError, "No Connection" ConnectionAbortedError, "Connection Aborted" "Вытащить" строку из ресурсов можно несколькими способами, но самый простой из них — просто по числовому идентификатору, переданному в функцию Loadstr (модуль покажет сообщение "NO Если же строка используется при возбуждении ИС, то место идентификато ру—в перекрываемом конструкторе один из вариантов которого работает подобно функции Loadstr:

if = INVALID_HANDLE_VALUE then raise Таким образом, решена половина проблемы: возможным исключительным ситуациям присвоены номера, им в соответствие поставлен текст. Теперь о второй половине — как в обработчике ИС этот номер использовать.

Ясно, что нужно объявить свой класс ИС, включающий в себя свойство-код ошибки.

= private FErrCode : Integer;

Часть I. Объектная концепция Delphi public constructor PResStringRec);

property ErrCode: Integer read FErrCode write FErrCode;

end;

Тогда любой обработчик сможет к нему обратиться:

if E is then code: '+ + #13# + text: ' + Присвоить свойству ErrCode значение можно двумя способами:

1. Добавить к классу ИС еще один конструктор, содержащий код в качест ве дополнительного параметра:

constructor Integer);

begin FErrCode := Ident;

inherited end;

2. Присвоить значение свойства в промежутке между созданием объекта ИС и его возбуждением:

var E: EExceptionWithCode;

begin Е := := NoConnectionError;

Raise E;

end;

Вот, казалось бы, последний штрих. Но как быть тем, кто заранее не загото вил файл ресурсов, а работает со строками, описанными в PAS-файлах? Если вы используете оператор resourcestring, то помочь вам можно.

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

PResStringRec = TResStringRec = packed record Module:

Identifier: Integer;

end;

Глава 3. Обработка исключительных ситуаций Если вы еще раз посмотрите на список конструкторов объекта Exception, вы увидите, что те из них, которые работают с ресурсами, имеют перегружае мую версию с параметром типа Вы угадали правильно: они — для строк из А взглянув на приведенную выше структуру, вы увидите в ней поле identifier. Это то, что нам надо.

Чтобы у программиста, пользующегося resourcestring, голова не болела об уникальных идентификаторах ресурсных строк, среда Delphi берет на себя заботу об этом. Номера назначаются компилятором, начиная от 65 (-1)) и ниже (если рассматривать номер как тип то вы ше): 65 534, 65 533 и т. п. Сначала в этом списке идут несколько сотен resourcestring-констант, описанных в VCL (из модулей, чье имя заканчива ется на const или consts: DBConsts и т. п.). Затем очередь доходит до пользовательских констант (рис. 3.3).

С одной стороны, отсутствие лишних забот — это большой плюс;

с другой стороны, разработчик не может задать строкам те номера, какие хочет.

Все остальное почти ничем не отличается от работы с "самодельными" ре сурсами. Так выглядит перегружаемая версия конструктора нашего объекта constructor PResStringRec);

begin := ResStringRec'4. Identifier;

inherited end;

А так — возбуждение самой ИС:

resourcestring sErrorl = Raise Результат обработки показан на рис. 3.3.

Error code: Error text: Exception 3.3. Результат обработки ИС типа EExceptionWithCode 82 Часть I. Объектная концепция Delphi Исключительная ситуация EAbort Если вы внимательно просмотрели код системной процедуры то увидели там упоминание класса EAbort. ИС EAbort служит единствен ным — и очень важным — исключением из правил обработки. Она называ ется "тихой" (Silent) и отличается тем, что для нее обработка по умолчанию не предусматривает вывода сообщений на экран. Естественно, все сказан ное касается и порожденных от нее дочерних объектных классов.

Применение EAbort оправдано во многих случаях. Вот один из примеров.

Пусть разрабатывается некоторая прикладная программа или некоторое се мейство объектов, не связанное с Если в них возникает ИС, то нужно как-то известить об этом пользователя. А между тем прямой вызов для это го функции или даже не всегда оправдан. Для ма ленькой и компактной динамической библиотеки не нужно тащить за собой громаду С другой стороны, в большом и разнородном проекте нельзя давать каждому объекту или подпрограмме самой общаться с пользователем.

Если их разрабатывают разные люди, такой проект может превратиться в вавилонскую башню. Тут и поможет EAbort. Эта исключительная ситуация не создается системой — ее должен создавать и обслуживать программист.

Применение EAbort — реальная альтернатива многочисленным конструкци ям и тем более (упаси боже!) goto. Эта ИС не должна подменять собой другие, вроде ошибки выделения памяти или чтения из файла. Она нужна, если вы сами видите, что сложились определенные условия и пора менять логику работы программы.

I f Logi cal Condi t i on t hen Rai s e 1' ) ;

Если не нужно определять сообщение, можно создать EAbort и проще — вызвав процедуру Abort (без параметров), содержащуюся в модуле Функция Assert Эта процедура и сопутствующая ей ИС специально пере несены в Object Pascal из языка С для удобства отладки. Синтаксис ее прост:

procedure : Boolean [;

const string]);

При вызове функции проверяется, чему равно значение переданного в нее булевого выражения Если оно равно True, то ровным счетом ничего не ПРОИСХОДИТ. ЕСЛИ же ОНО равно False, ИС EAssertionFaiied. Все это было бы довольно тривиально с точки зрения уже изученного, если бы не два обстоятельства:

Глава 3. Обработка исключительных ситуаций Предопределенный обработчик EAssertionFaiied устроен таким образом, что выдает не шестнадцатеричный адрес ошибки, а имя файла с исход ным текстом и номер строки, где произошла ИС, как показано на рис. 3.4.

Pages:     || 2 | 3 | 4 | 5 |   ...   | 10 |



© 2011 www.dissers.ru - «Бесплатная электронная библиотека»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.