Обложка 1
Титульный
Аннотация
Предисловие. Зачем нужно объектно-ориентированное программирование  для Windows
Глава 1. Введение в Windows 3.0
Что нового в версии 3.0
Многозадачность
Перехват экрана
Калькулятор
Работа с числовыми данными
Управление усовершенствованным 386 режимом
Управление многозадачностью
Инициализационные файлы
Клавиши активизации, определяемые прикладной программой
Управление памятью
Microsoft Word for Windows
Работа с заголовками
NewWave
Office Tools
Toolbook
Wingz: электронная таблица нового поколения
Сеанс работы с Windows 3.0
Глава 2. Что такое объектно-ориентированное программирование
Введение
Парадигмы программирования
Объектно-ориентированный подход
Метафоры объектно-ориентированного программирования
Активные данные
Передача сообщений
Классы: порождение экземпляров и наследование
Типы объектно-ориентированных систем
Как работают объектно-ориентированные системы
Использование объектно-ориентированных систем
Почему применяют объектно-ориентированное программирование
Некоторые выводы
Дополнительные заботы программиста
Глава 3. Объектно-ориентированные расширения Сн: языки и инструментальные средства
Обзор средств Си++ 2.0
Структуры и классы
Множественное наследование
Функции-члены
Регулируемая степень инкапсуляции
Функциц-друзья
Перегрузка операторов и функций
Прототипы
Виртуальные функции и классы
Конструкторы и деструкторы
Компилятор фирмы Zortech
Модели памяти
Карта памяти программ
Отладчик для Си++
Классы библиотеки Zortech Tools
Заключительные замечания о Си++
Язык Objective-C
Иерархия классов
Коллекции
Графика
Символьная отладка
Обсуждение
CtaUc
Синтаксис CtaUc
Базовые классы Ctalk
Создание исполняемых программ
Заключительные замечания о CtaDc
VIEWS: объектно-ориентированный инструмент разработки Си-программ для Windows
Броузер Си++ системы VD3WS
Модель MVC пользовательского интерфейса
Класс Appview
Назначение окон в системе VLEWS
Класс View
Создание диалогов с помощью генераторов интерфейсов
Создание меню
Классы редакторов текстов
Таймер
Коммуникационный класс
Графика
Ускорители приложений
Заключительные замечания о VffiWS
Глава 4. Разработка программ для Windows 3.0
Что нового появилось в SDK Windows 3.0
Как работает система Windows
Функции, которые создают объекты
Классы окон
Сообщения
Функции окон
Контекст дисплея
Элементы оконных пользовательских интерфейсов
Управляющие элементы, перерисовываемые владельцем
Пиктограммы
Меню
Управляющие элементы
Кнопки
Диалоги
Окошки списков
Комбинированные окошки
Всплывающие меню
Интерфейс с графическим устройством
Машинно-независимая цветная графика
Компилятор ресурсов
Редакторы ресурсов и инструменты
Редактор диалогов
Утилита SDKPaint
Редактор шрифтов FontEdit
Лупа
Профайлер
CodeView for Windows
Отладчик Symdeb для реального режима
Обеспечение совместимости между версиями 2.0 и 3.0
Структура прикладных программ для Windows
Стиль пользовательского интерфейса Windows
Описания ресурсов
Заключение
Управляющие сообщения редактирования
Новые функции версии 3.0
Новые сообщения Windows 3.0
Новые структуры данных Windows 3.0
Глава 5. Введение в систему программирования Актор
Язык Актор, версия 3.0
Среда программирования Актор
Броузер Актора
Инспекторы
Классы Актора
Классы MS-Windows
Элементы управления
Динамические меню и диалоги
Callbacks
Списковые структуры
Программирование на Акторе
Основы синтаксиса Актора
Немного музыки
Программные блоки
Форматы файлов
Тренировки в языке Актор
Сеанс с классом Number
Строки
Структуры управления
Циклы
Коллекции
Массивы
Другие коллекции
Упорядоченные коллекции
Словари
Текстовые коллекции
Очереди
Переменные класса
Вызов библиотек с динамическим связыванием
Представление знаний
Отладка
Профайлер
Выполнение внешних программ
’’Распечатывание” приложений
Класс Application
Добавление примитивов в Актор
Измерение времени
Работа с датами и временем
Что такое время
Цифровые часы
Описания классов для работы со временем
Дополнительный пример программ
Глава 6. Объектно-ориентированный пользовательский интерфейс для Windows
Создание окон
Текстовые окна
EditWindows
FileEditor
Описания класса
Класс Actorapp
Клавиши, диалоги и другие элементы управления
Создание кнопок
Создание файловых диалогов
Рабочие области
Класс TextfiIe
Создание динамических меню
Создание динамических диалогов
Внережимные диалоги
Обычный динамический диалог
Создание списковых окон
Создание комбинированных окошек
Создание полосок меню главного окна
Простой контроллер исполнения
Некоторые извлечения из примера ресурсного файла
Директории Dos
Глава 7. Объектно-ориентированное графическое программирование для Windows
Встроенные графические классы
Класс Point
Описание класса Scribble
Прямоугольники
Прямоугольники с закругленными краями
Эллипсы
Многоугольники
Цветные битовые карты
Трехмерная точка
Диаграммы
Анимация
Некоторые итоги
Описания графических классов
Глава 8. Программирование при помощи ObjectGraphics
Обзор ObjectGraphics
Демонстрационный пример SampleDraw
Добавление меню и новых форм в SampleDraw
ObjectDraw
Расширенный Актор
Фильтры платформ
Класс Color
Битовые карты
Прямоугольники
Формы
Графические пространства
Пиктограммы
Полиформы и ломаные линии
Кривые
Рисование треугольников
Палитры
Группировки
Применение инструментов формирования изображения
Полимарки
Иерархия класса ObjectGraphics
Расширения класса Window в ObjectGraphics
Глава 9. Пример приложения в Windows 3.0
Краткий обзор Executive Control
MDI-поддержка в Акторе
MDIFrameWindow
MDIFileWindow
Иерархия классов MDI
Управление большой и сложной системой меню
Иерархические меню
Комбинирование статических и динамических меню
Динамические файловые диалоги
Факторизация командных методов
Глава 10. Вопросы проектирования в объектно-ориентированной среде Windows
Дизайн для пользователей
Что же реально делает компьютер
Разделение процедур и протоколов
Проектирование классов
Экземпляры дублирующихся приложений
Объектно-ориентированное проектирование графического пользовательского интерфейса
Пример проектирования объектно-ориентированной электронной таблицы
Открытые цепочки действий
Факторизация командных методов
Объектно-ориентированное проектирование для MS-Windows
Иерархические меню
Управление большой и сложной системой меню
CommonView
Классы для изображений в C+4
Классы CommonView
Проектирование управления памятью в Windows
Типы хранения данных
Сбрасываемая память
Управление памятью повышенной эффективности
Стандартный режим
Усовершенствованный режим 386-го процессора
Несколько правил ’’хорошего тона”
Библиотека WINMEM32.DLL
Управление памятью в Акторе
Своппинг статической памяти
Классы памяти Актора
Описания классов для работы с памятью
Проектирование индексно-последовательной базы данных
Wintrieve — краткий обзор
Объектно-ориентированные системы реляционных баз данных
Разработка объектно-ориентированных баз данных при помощи Wintrieve
Классы Wintrieve
Базисные классы, используемые в Wintrieve
Описания класса Wintrieve
Описания класса Sample
Указатель
Содержание
Выходные данные
Обложка 2
Text
                    Объектно-ориентированное
программирование
в  среде  Windows


OBJECT-ORIENTED PROGRAMMING FOR WINDOWS ™ Ernest R. Tello John Wiley & Sons, Inc. New York e Chichester • Brisbane ■ Toronto ■ Singapore
ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В СРЕДЕ WINDOWS Эрнест Телло Перевод с английского Д. М. Арапова А. К. Петренко Москва Издательство ВЫСШАЯ ШКОЛА Издательство НАУКА-УАЙЛИ Акционерное общество AKME 1993
ББК 24.4.1 T31 Рецензенты: И. С. Кондратьев, В. А. Семичев Телло Э. Р. T31 Объектно-ориентированное программирование в среде Windows: Пер. с англ.— М.: Наука-Уайли, 1993. — 3 47c.: ил. — Пер. изд.: Tello, Ernest R. Object-OrientedProgramming^ for Windows/ Covers Windows З.х. John Wiley & Sons, Inc. США, 1991. — ISBN 5-88182-015-0: 50000 экз. Система Windows фактически стала стандартом для IBM PC АТ и после¬ дующих моделей. Однако программирование в среде Windows является весьма непростым делом. Одним из перспективных методов снижения слож¬ ности разработки программ в Windows является использование технологии объектно-ориентированного программирования (ООП). Наряду с понятиями и методами ООП в книге описаны возможности MS-Windows ЗсХ. Изложение иллюстрировано множеством примеров про¬ грамм на языке Актор, который воплощает в себе лучшие черты классиче¬ ского языка ООП Смолток и языка Си. Для разработчиков пользовательских оконных интерфейсов в различных системах, преподавателей и студентов ВУЗов, изучающих ООП. 2402010000 - 109 без объявл. ББК 24.4.1 001(01) - 93 ISBN 0-471-52754-8 (англ.) © Copyright by John Wiley & Sons, Inc., 1991 ISBN 5-88182-015-0 (рус.) © Д. М. Арапов, А. К. Петренко, перевод, примечания, 1993
■ ПРЕДИСЛОВИЕ Эта книга написана в помощь как начинающим, так и более опытным программистам. Ясные и подробные объяснения призва¬ ны помочь новичкам войти в курс дела, не испытывая при этом неуверенности и страха. Основные подходы проиллюстрированы примерами. Воспользовавшись ими, неофит получает работающую схему, на базе которой он может создать собственную программу. Этому весьма способствует высокая степень модульности, прису¬ щая объектно-ориентированному программированию. Для более опытного программиста предлагаются все приемы использования объектно-ориентированного подхода, дающие доступ к средствам оболочки Windows. ■ ЗАЧЕМ НУЖНО ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ ДЛЯ WINDOWS? Для миллионов машин с операционной системой MS DOS оболочка Windows стала стандартом de facto. В настоящее время в среду Windows перенесены наиболее важные прикладные программы, такие, как Excel, Pagemaker, Designer, Ami, Corel Draw, и сотни других. Одновременно широко стал применяться объектно-ориен¬ тированный подход. Такие фирмы, как Lotus, AT&T, Microsoft, Borland, Apple и Next, восприняли его как самую современную на сегодняшний день методику программирования. Глава 1 дает обзор ключевых аспектов функционирования окру¬ жения Windows. В ней содержатся примеры различных программ, написанных для Windows, и обсуждаются некоторые технические аспекты, скажем, различия управления памятью в разных режи¬ мах: реальном, стандартном и усовершенствованном 386. Глава 2 рассматривает достоинства (а их множество) объектно-ори¬ ентированного подхода и его недостатки (их всего несколько). Преимущества обусловлены в основном высокой модульностью,
6 Предисловие присущей объектно-ориентированному подходу. Объектно-ориен¬ тированный подход упрощает жизнь в основном программисту, однако часть преимуществ напрямую доступна и пользователю. В дальнейшем я перечислю и объясню характерные особенности объектно-ориентированных систем, а также покажу значение каж¬ дого из них для всего подхода в целом. Затем рассматриваются такие инструменты объектно-ориентиро- ванного программирования, как Views (фирма CNS), предназна¬ ченный для разработки прикладных программ для Windows на объектно-ориентированном диалекте Си, язык Actor компании WhiteWater Group, С++ фирмы Zortech. Си в настоящее время — наиболее популярный язык для создания коммерческих программ, поэтому вполне закономерно уделить одну главу объектно-ориен¬ тированному Си, а также рассмотреть другие известные диалекты: С++, Objective-С и CTalk. Приводится несколько примеров про¬ грамм на объектно-ориентированном Си. Предполагается, что чи¬ татель не будет пользоваться каким-либо одним определенным диалектом, поэтому специальные разделы посвящены переводу программ с одного объектно-ориентированного диалекта Си на другой. В ряде случаев, когда это не совсем просто, приводятся версии одной программы для разных диалектов. Поскольку Windows — графическая оболочка, она позволяет включать, в прикладную программу различную графику, начиная с диаграмм, и кончая чертежами и мультипликацией. Графика становится все более важным компонентом современных про¬ грамм. Объектно-ориентированный подход уже оказал значитель¬ ное влияние на программирование графических систем. Отдельная глава посвящена применению объектно-ориентированного подхо¬ да для решения задач, специфичных для среды Windows. Пользовательский интерфейс в средах, подобных Windows, стано¬ вится одной из наиболее трудоемких частей программ. Объектно¬ ориентированный подход особенно ценен тем, что минимизирует эту часть работы за счет повторного использования отдельных частей программ и адаптации существующего кода. Windows способствует применению объектно-ориентированного подхода, поскольку с самого начала естественно представлять в виде клас¬ сов такие кирпичики пользовательского интерфейса, как диалого¬ вые окна, кнопки, меню и т.п., и создавать на их базе специализированные элементы, пригодные в разных случаях. Рассматриваются также подходы к расширению средств пользова¬ тельского интерфейса Windows. Продолжит разговор о технике рассказ о системном подходе к разработке объектно-ориентированных программ. Пользователя следует предостеречь от искусственной подгонки задачи под одну
Зачем нужно объектно-ориентированное программированиедля Windows? 7 из известных моделей и воодушевить на поиски нестандартных решений. Вместе с тем, для любых применений важен набор принципов, разработанных в последние 10 лет. Акцент будет сделан на тех аспектах объектно-ориентированного проектирова¬ ния, которые напрямую связаны с повышенной степенью модуль¬ ности, характерной для объектно-ориентированных инструментов среды Windows. Продвинутая техника программирования для Windows предпола¬ гает умение использовать информацию, содержащуюся в конфи¬ гурационных файлах WDJJNt создавать специализированные версии стандартных объектов Windows и прикладные программы, выполняющиеся при нажатии на клавишу мыши на любом из предназначенных для них файлов данных. Также важно уметь правильно выбирать параметры управления памятью. Одна из наиболее трудных проблем при разработке пользователь¬ ского интерфейса состоит в правильном встраивании приложения в программную среду. Ее автоматическому решению способствует объектно-ориентированное программирование. С помощью ис¬ пользования набора классов можно определять экспортируемые методы, необходимые для широкого спектра прикладных про¬ грамм. Последние могут адаптировать эти методы по мере необхо¬ димости. При создании больших программных проектов преимущества объ¬ ектно-ориентированного подхода становятся особенно очевидны. Пример такого проекта приведен в гл. 9. Эта программа дополняет окружение Windows и значительно расширяет сферу его примене¬ ния. Приводя пример программы, который читатели могут исполь¬ зовать непосредственно, я рассчитываю увеличить число ее пользователей. Поскольку читатели обычно используют примеры в качестве образца, помощь в разборе примера сделает более вероятным то, что они возьмутся за его адаптацию и развитие. Это тот случай, когда достоинства объектно-ориентированного подхода становятся видимыми конечному пользователю. Ключевой особенностью окружений типа Windows является их способ управления памятью. Здесь объектно-ориентированный подход также важен, поскольку модульное построение про¬ грамм — необходимая предпосылка для эффективного управления памятью в Windows. Объектно-ориентированный подход дает про¬ граммисту максимум средств для модульного проектирования и кодирования.
Глава 1 Ш ВВЕДЕНИЕ В WINDOWS 3.0 Новая версия графического интерфейса пользователя (GUI — Graphic User Interface) сводит до минимума различия между стилями программного обеспечения наиболее известных компью¬ теров, лишая смысла высокие цены на одни из них. Пакет Windows способен покорить всех пользователей от новичков до асов, в том числе и программистов. Windows предлагает новый стандарт графического интерфейса пользователя для недорогих компьютеров. Windows стал единой программой, заменив три существовавшие ранее. Сейчас пришло время появления новых важных приложений, способных занять место рядом с редактором Word for Windows, деловой программой Excel и системой настольной типографии PageMaker. В отличие от Presentation Manager для OS/2 Windows не требует ни покупки дополнительной аппаратуры, ни изучения новой операционной системы. В настоящее время почти все важные прикладные про¬ граммы перенесены или переносятся в Windows. Огромное число машин, на которых работает это окружение, и почти отсутствующее функциональное различие между ним и графической средой компь¬ ютеров Macintosh гарантируют его популярность в 90-х годах. Эта глава содержит большинство сведений о среде Windows и тех ее возможностях, которые необходимы пользователю. В ней гово¬ рится о трех режимах функционирования Windows и о том, когда и как их использовать. Приводятся основные концепции и прин¬ ципы, включая концепцию графического интерфейса пользовате¬ ля. Наконец, дается обзор основных средств Windows и их применений на практике. Делается попытка объяснить, чем удоб¬ ны оконные интерфейсы вообще и Windows в частности и дается описание, как с ними работают. Рассматриваются понятие пиктог¬ раммы @соп) и отличия пиктограмм от окон (Window), а также
Многозадачность 9 различные типы окон и способы взаимодействия с ними при помощи манипулятора ”мышь” и клавиатуры. Говорится также о линиях прокрутки (Scroll bars), о распахивании и сжатии окон, о работе с меню. Описывается Не1р-система (контекстно-зависимая система подсказки) оболочки Windows. ■ ЧТО НОВОГО В ВЕРСИИ 3.0 Достаточно взглянуть на экран Windows, чтобы заметить, что все электронные кнопки имеют ”тень”, из-за которой они кажутся выпуклыми. Изменение формы тени при ’’нажатии” кнопки уси¬ ливает визуальный эффект. Старая программа MS DOS Executive заменена двумя другими: Program Manager и File Manager. Новый козырь среды Windows — пиктограммы. Изменены цвета. Еще одно положительное отличие от предыдущих версий — независи¬ мость от внешних устройств. Не последнее место в новом облике Windows занимают пропорциональные шрифты. Для конечного пользователя Windows 3.0 — единая программа. Покончено со специализированными версиями для машин на основе процессоров 286 и 386. Достаточно приобрести одну про* грамму и она будет работать на любом компьютере. Однако запускать ее можно в одном из трех режимов: стандартном, реальном и усовершенствованном 386. Существенно отличает эту версию расширенная поддержка приложений, не рассчитанных на Windows. Особенно это относится к усовершенствованному 386 режиму. Повсеместное наличие средств Help — еще одно достоин¬ ство новой версии. За счет стандартизации Help может быть создан даже для самых небольших программ. В итоге конечный пользо¬ ватель может получить детальную информацию о каждом прило¬ жении. Появилась настоящая многозадачность. Это прежде всего важно для тех, у кого имеется возможность работать в усовершен¬ ствованном 386 режиме. Прежде чем стать экспертом в области программирования для Windows, необходимо хорошо познакомиться с работой в этой среде. Знакомство с сильными и слабыми сторонами этого окружения позволит вам научиться писать более эффективные программы. ■ МНОГОЗАДАЧНОСТЬ Чтобы появилось окно Task List (список задач), достаточно уста¬ новить курсор в любую точку фона экрана и дважды нажать кнопку мыши. В этом окне перечислены все выполняющиеся в данный момент процессы Windows. Над этим списком можно выполнять различные действия, однако самая типичная опера¬ ция — это выбор прикладной программы и вытаскивание ее окна
10 Введение в Windows 3. О наверх. Для большинства пользователей Task List становится привычной деталью сеанса работы в Windows. Особенность новой версии: в закрытом ящике в левом углу каждого окна появилась опция Switch to (переключить на), которая служит для переклю¬ чения управления на любую загруженную в данный момент про¬ грамму Windows. Столь простое переключение с одной прикладной программы на другую порождает эффект слияния приложений в одну общую программу. ■ МЕНЕДЖЕР ПРОГРАММ (PROGRAM MANAGER) Program Manager при помощи пиктограмм изображает доступные группы программ. Эти группы могут быть открыты, превращаясь в окна, которые содержат пиктограммы, соответствующие при¬ кладным программам. Как и другие окиа, окна групп могут менять размер. Имеются два способа автоматической расстановки окон: в виде каскада и вплотную друг к другу, без перекрытий (подобно кирпичной кладке или паркету). Стандартный, поставляемый с Windows набор состоит из групп Main, Accessories, Games, Windows Applications и Non-Windows Applications. Однако поль¬ зователь может изменить этот набор. Когда в менеджере программ открыто более одной группы, соответствующие им окна могут быть расставлены в виде каскада или в виде паркета (tile). ■ ОСНОВНАЯ ГРУППА (MAIN GROUP) В эту группу первоначально входят утилиты File Manager, Print Manager, Clipboard, Dos Prompt, Windows Setup и Control Panel. Чтобы создать новую группу, достаточно выбрать опцию New в меню File и поименовать группу. Если вы не сообщили имя файла, Windows создаст его самостоятельно. Включение программ в груп¬ пу происходит похожим образом: необходимо указать только имя выполняемого файла и директория. Для программ Windows в ”папке” группы немедленно появится пиктограмма этой програм¬ мы. Если при открытии приложения вам понадобится открыть документ или другой файл данных, можно воспользоваться оп¬ цией Run меню File. В этом случае вы просто вводите командную строку с именем загружаемого файла, как если бы это была обычная программа DOS, вызванная из командной строки. ■ МЕНЕДЖЕР ФАЙЛОВ (FILE MANAGER) File Manager служит для обычной работы с файлами и для созда¬ ния и модификации групп программ. Прикладная программа может быть вызвана как из менеджера программ, так и из менед¬ жера файлов. Нажатие кнопки мыши на пиктограмме папки
Менеджер файлов (File Manager) 11 (слева от имени исполняемого файла) запустит соответствующее приложение. Подобно менеджеру программ, менеджер файлов позволяет стро¬ ить каскад окон или расставлять их в виде паркета. Последний способ весьма полезен для одновременного показа исходного и целевого директориев при копировании и перемещении группы файлов. Удобство использования паркетного расположения окон несколько снижается из-за того, что Windows требует, чтобы для каждого открытого поддиректория родительские директории были также открыты, что приводит к загромождению экрана. Чтобы сделать исходный и целевой директории более доступными, вам придется вручную собирать, перемещать порожденные окна, из¬ меняя их размеры. Один из способов уменьшения ’’толчеи” на экране состоит в том, чтобы воспользоваться средством раскрытия поддиректориев до нужной степени в дереве директориев. Те из директориев, которые содержат поддиректории, помечены знаком ”+” на пиктограмме ’’папки”. Единственное нажатие кнопки мыши на этом знаке раскрывает директорий таким образом, что становятся видны его непосредственные поддиректории. Двойное нажатие на пиктог¬ рамме, как и прежде, породит отдельное окно для выбранной директории. По-другому раскрыть несколько уровней директориев можно, воспользовавшись опциями Expand One Level (Раскрыть один уровень), Expand Branch раскрыть связь), Expand All рас¬ крыть все) и Collapse Branch (Свернуть связь) из меню Tree (Дерево). Может оказаться полезным счетчик свободного пространства на диске, расположенный в нижней части окна с корневым директо- рием. Удобную возможность предоставляет команда Move (Пере¬ местить), позволяющая перемещать файлы, не используя команд Сору и Delete. При выполнении операций над группами файлов по умолчанию для каждого из них используются диалоги подтверж¬ дения. Появление соответствующих диалогов может быть подав¬ лено с помощью одного из пунктов меню Options. File Manager позволяет копировать директории целиком. Для этого достаточно выбрать директорий, в меню File указать функцию копирования и сообщить имя директория, в который требуется копировать. File Manager позволяет ассоциировать файлы данных с тем или иным приложением. При выборе файла с указанным расширением автоматически вызывается прикладная программа с данным фай¬ лом в качестве параметра. Наиболее частый повод для редактиро¬ вания файла WIN.INI состоит как раз в построении таких ассоциаций.
12 Введение в Windows 3. 0 Управление принтерами осуществляется через Control Panel (па¬ нель управления). Когда открыто окно Control Panel, пользователь выбирает пиктограмму Printers и открывает соответствующее ди¬ алоговое окно. В этом окне можно задать тип принтера, выбрав его из списка поддерживаемых принтеров или указав одну из опций Generic СРодовой) или Unlisted Printer (Не из списка). Аналогичным образом выбор пиктограммы Font (Шрифт) позво¬ ляет установить шрифт. Среда Windows может многими способами адаптироваться к по¬ требностям и привычкам пользователя. Большинство из них будут рассмотрены в этой главе, исключение составит лишь управление памятью, которое будет описано в гл.10. Прежде всего я объясню назначение четырех инициализационных файлов Windows. Будет рассказано также об использовании средств Control Panel и Macro Recorder для учета специфических требований к окружению. Control Panel дает пользователю возможность создать оригиналь¬ ный фон для Windows и свою собственную схему использования цветов. Это напоминает оформление интерьера для конторы или квартиры. Здесь же могут быть выбраны и добавлены новые шрифты, установлены текущие время и дата, а также режим работы принтеров. Windows поставляется с набором мощных вспомогательных про¬ грамм, подчеркивающих достоинства этой среды. Все эти програм¬ мы, за исключением Cardfile и Setup, будут рассмотрены далее. Итак, перед нами: Clock (часы), Paintbrush (редактор картинок), Notepad (записная книжка), Calendar (календарь), Clipboard (кар¬ ман), Terminal и PJF Editor (редактор файлов типа PD?). ■ КАРМАН (CLIPBOARD) Clipboard — одно из самых важных средств Windows, с его помощью можно переносить данные и изображения из одной программы в другую. Присутствие Clipboard отмечено соответст¬ вующей пиктограммой на экране. Пиктограмма раскрывается в виде окна, позволяющего просматривать, но не редактировать его содержимое. Редактирование запрещено, поскольку обычно требу¬ ет сложных механизмов поддержания целостности. Для редакти¬ рования следует использовать другие инструменты. Содержимое Clipboard можно записывать на диск, это позволяет программам, работающим в разных режимах, иметь общие данные. В этом случае программа-источник помещает данные в Clipboard, послед¬ ний сохраняет их в виде файла на диске. После этого можно выйти из Windows и запустить окружение в режиме, в котором работает программа-приемник. Далее необходимо считать файл в Clipboard, и с его помощью вклеить данные в программу-приемник. В насто¬
Калькулятор 13 ящее время Clipboard имеет средства для показа своего содержи¬ мого в различных форматах. Например, в текстовом режиме, если вы загрузили что-либо в Clipboard, то в меню Display будут доступными опции: Owner Display, Text и OEM Text В зависимо¬ сти от выбранной опции ваш пакет будет выглядеть по-разному. Прикладные программы, не ориентированные на Windows, также могут копировать информацию в Clipboard (это доступно в усовер¬ шенствованном 386 режиме). Возможен и перенос информации в противоположном направлении. Для этого используются два ме¬ ханизма. Первый состоит в том, что, если вы находитесь в не- Windows программе, работающей с экраном, вы можете ужать образ этой программы на экране до пиктограммы DOS. (При этом содержимое экрана сохранится.) Если открыть управляющее меню для этой пиктограммы, то вы обнаружите две новые опции: Edit редактировать) и Settings (установки). Опция Settings содержит диалог, с помощью которого можно обеспечить просмотр текущего экрана не-Windows программы в окне Windows. В этом окне с помощью мыши можно пометить область, которую требуется скопировать на Clipboard. Далее необходимо воспользоваться вто¬ рым механизмом. Выберите опцию Edit, и вы получите подменю с опцией Сору. Вклеивание работает аналогичным образом. ■ ПЕРЕХВАТ ЭКРАНА При необходимости Windows 3.0 позволяет в любой момент сохра¬ нить содержимое экрана. Для этого служит комбинация клавиш Shift-PrintScreen. Вместе с тем можно перехватить и содержимое каждого отдельного окна. Для этого следует воспользоваться ком¬ бинацией клавиш Alt-PrintScreen. Экраны данных не-Windows программ можно перехватить с помощью Clipboard. В усовершен¬ ствованном 386 режиме можно перехватить содержимое окна, в котором работает программа. Это относится и к графическим программам, но реализовано в настоящее время не для всех режимов работы дисплея. В основном это ограничение касается режимов VGA с наибольшим разрешением. ■ КАЛЬКУЛЯТОР Безусловно, наиболее примечательной особенностью нового каль¬ кулятора является режим научных расчетов. Не теряя результатов вычислений, можно переключаться с обычного режима работы на научный и обратно. В новом режиме доступно множество матема¬ тических функций. Возможны три варианта длин чисел: двойное слово, слово и байт. Калькулятор полностью использует возмож¬ ности функциональной клавиатуры. Функциональные клавиши реализуют тригонометрические функции, логарифм, экспоненту,
14 Введение в Windows 3.0 квадратные и кубические корни. Нажатие клавиши ”h” переклю¬ чает между обычными и гиперболическими функциями. Широкие возможности открывает функция biverse, которая обращает по¬ следнюю использованную функцию. Ее можно рассматривать как ”Undo” для калькулятора. Вычисленные на калькуляторе значения могут копироваться в Clipboard и вклеиваться затем в электронные таблицы Excel или в другое приложение. С помощью Clipboard можно копировать и в обратном направлении. Например, полученные в электронной таблице числа могут быть перенесены в калькулятор для последу¬ ющих вычислений. В режиме научных расчетов калькулятор способен выполнять статистическую обработку. Если выбрать кнопку Sta, появится новое окно, в котором можно запоминать списки чисел и вычис¬ лять такие функции, как сумму, среднее, стандартное отклонение и некоторые более сложные. Калькулятор будет особенно полезен программистам, поскольку он позволяет работать в различных системах счисления: десятичной, двоичной, шестнадцатеричной и восьмеричной. Основание системы счисления автоматически учи¬ тывается при отображении текущего значения. Другая вспомогательная программа Calendar отображает даты в виде привычного календаря. Числа записаны в квадратиках и расставлены в столбцы по неделям. Имеются несколько способов поставить метку, благодаря которым с первого взгляда видны даты, на которые назначены встречи. Календари рассчитаны на неопределенный срок вперед и способны удовлетворить даже ав¬ тора фантастического романа. Летоисчисление начато, однако, с 1980 г., и нет возможности использовать даты в прошлом. Cardfile (картотека) — это еще одна удобная программа. С по¬ мощью CHpboard в нее и из нее можно копировать графические изображения. Это значительно расширяет возможности програм¬ мы по сравнению с записной книжкой. Если у вас имеется соот¬ ветствующая аппаратура, вы можете использовать оцифрованные фотографии моделей, предметов, пейзажей и т.п. К сожалению, вне зависимости от выбора цвета для области текстовых данных возможны только черно-белые фотографии. Чтобы получить в распоряжение приложений как можно больший объем памяти, в зависимости от типа используемого компьютера и его конфигурации указываются разные опции. Наличие несколь¬ ких опций, дающих при взаимодействии разные результаты, может несколько озадачить. При проектировании управления памятью для приложения программист должен знать все способы, какими пользователь Windows может конфигурировать память, и понимать, какую их комбинацию следует использовать для дости¬
Работа с числовыми данными 15 жения наилучших результатов. Программистам следует быть осо¬ бенно осторожными при использовании драйверов памяти фирмы Microsoft, таких, как HIMEM. SYS, SMARTDRV.SYS и EMM386.SYS, и представлять, как они влияют друг на друга и их комбинация на вашу прикладную программу. Программисты, разрабатывающие программы для Windows, дол¬ жны быть знакомы с графикой, используемой в этой среде. Про¬ граммисты, не знакомые с ней, могут начать обучение с программы Paintbrush. Это единственная вспомогательная программа Windows, использующая не одну, а две кнопки мыши. Расскажу, как пользоваться этой программой на примере создания закончен¬ ной картинки. В процессе ее рисования использованы почти все возможности программы, проиллюстрированы такие средства, как сжатие и растяжение участков изображения, разработка собствен¬ ной палитры, преобразование файлов в формате Microsoft Paint и печать картинок. В этом разделе рассматриваются общие принципы работы с гра¬ фикой в Windows. Для окружения Windows уже существует набор замечательных графических программ, таких, как Designer, Corel Draw, Zing, Drafix CAD и Picture Publisher. Следует рассказать также о нескольких базах данных для графических изображений. Мы рассмотрим некоторые инструменты, используемые для редак¬ тирования битовых карт и пиктограмм, например Icon Editor. Среда Windows активно используется для подготовки современ¬ ных продуктов мульти-медиа (Multi-media). Программистам, ра¬ ботающим в среде Windows, необходимо быть в курсе этих разработок. ■ РАБОТА С ЧИСЛОВЫМИ ДАННЫМИ Для численного анализа в Windows имеется множество превосход¬ ных средств. В их числе калькулятор с его обычным режимом и режимом научных расчетов, упомянутая ранее электронная таб¬ лица Excel, которую можно использовать вместе с калькулятором. Возможность создания нескольких связанных между собой окон придает электронным таблицам дополнительное измерение. Для выполнения повторяющихся последовательностей действий, не обязательно локализованных в одном приложении, служит Macro Recorder. Для работы с числовыми данными имеется мощный продукт Microcalc фирмы Anderson Consulting. Его возможности по достоинству оценят научные работники. Word for Windows — это самый современный текстовый процес¬ сор. Он может служить примером программы, в полной мере реализующей возможности Windows. Умение эффективно исполь¬
16 Введение в Windows 3. 0 зовать подобный инструмент поможет сберечь много сил и време¬ ни. Достаточно упомянуть средство Glossary, позволяющее изба¬ виться от повторного набора. В среде Windows работают и другие текстовые процессоры, такие, как Ami и Legend. В настоящее время текстовый процессор — это лишь один из участков производствен¬ ного цикла настольной типографии. Pagemaker — современная программа настольной типографии, развивающаяся ”в ногу со временем”. Будут показаны области практического применения этой программы и обсуждены ее достоинства и недостатки. Чтобы закончить рассказ о текстовой обработке в Windows, необходимо рассказать о сканерах и средствах распознавания символов. Обмен данными присутствует в Windows во всем его разнообразии: от передачи информации и картинок средствами динамического обмена данными (DDE) до вывода на печать. В этой главе будет рассказано и об основных сетевых средствах. Чтобы достигнуть наилучших результатов при печати в окружении Windows, необ¬ ходимо освоить программу Print Manager. Terminal — это еще одна программа, используемая для связи с внешним миром. Для тех, кто использует сеть, предназначена специальная версия програм¬ мы Setup. Процесс установки Windows для узла сети будет описан далее. Дается описание коммуникационного программного обеспе¬ чения третьих фирм, скажем, пакета Crosstalk. Читателю уже становится ясно, какие выгоды сулит окружение Windows. Невозможно перечислить все способы, которыми можно свести различные прикладные программы в единый технологиче¬ ский процесс, автоматизируя повторяющиеся действия. В качест¬ ве примера представьте себе настольную типографскую систему, использующую Macro Recorder для переноса в Pagemaker элемен¬ тов, изготовленных при помощи Word for Windows, Paintbrush и Excel. Обратимся к работе с графикой. С помощью Clipboard, утилит перехвата экрана и преобразования графических форматов можно переносить картинки из одной программы в другую, обо¬ гащая изображения специфичными для каждой из них визуаль¬ ными эффектами. Это, по-видимому, наиболее важное достоинство среды Windows. Как бы ни старались программисты, но даже в лучших программах не хватает какой-нибудь возможности. Ин¬ тегрированная среда, подобная Windows, позволяет комбиниро¬ вать лучшие средства всех программ в рамках одного проекта. Реальные преимущества Windows видны при работе с превосход¬ ными программами, доступными в данной интегрированной среде. Лучший способ проиллюстрировать эти преимущества состоит в демонстрации приемов использования таких программ.
Управление печатью (Printer Manager) 17 ■ ЗАПИСЬ MAKPO (MACRO RECORDER) Эта утилита предназначена для записи последовательностей нажа¬ тий клавиш и операций с мышью. Такой последовательности можно сопоставить одну клавишу и записать в файл вместе с другими макрорасширениями. Хотя Macro Recorder способен со¬ хранять и движения мыши, следует, по возможности, избегать таких макро, выполняя те же действия другим способом. Рекомен¬ дуется пользоваться эквивалентными последовательностями на¬ жатий на клавиатуре. К числу удачных особенностей утилиты Macro Recorder следует отнести возможность прервать запись макро в любой момент и запомнить записанное. Это очень важно, поскольку в противном случае пользователь попадает в странную ситуацию, когда ему приходится записывать действия, связанные с перемещением в точку прекращения записи макро. В некоторых случаях необхо¬ димо, чтобы макро переносило бы вас к экрану или окну, которое вы только что покинули, чтобы войти в Recorder. Для этого также служит прерывание в произвольной точке. Достаточно с помощью Ctrl-Break прервать запись в точке, где вы хотите, чтобы макро закончилось, при этом возникнет диалоговое окно, позволяющее окончить процесс записи в нужной точке. Запись макро для повторного воспроизведения в среде, в которой активны одновременно несколько программ, — сложная задача, поэтому имеются несколько режимов записи и воспроизведения. Например, нужно решить, будут ли операции с мышью записы¬ ваться лишь для одного окна или для всего экрана, воспроизводить ли последовательность с той скоростью, что и при записи, или с максимально возможной и т.д. Recorder разработан в расчете на запись длинных последовательностей действий, характерных для демонстрационных программ. Безусловно, у подобных утилит име¬ ется множество заранее неизвестных разработчику применений. Когда макро вызывается изнутри прикладной программы, на экране может возникнуть пиктограмма, изображающая песочные часы. Она призывает пользователя немного подождать, что может несколько испортить демонстрацию. Если возникают подобные проблемы, то имеет смысл разработать более эффективные макро. ■ УПРАВЛЕНИЕ ПЕЧАТЬЮ (PRINTER MANAGER) После того как с помощью Control Panel задана конфигурация принтера, с ним можно работать, используя Printer Manager. Эта программа осуществляет печать в фоновом режиме, т.е. в то время, когда идет печать, на компьютере можно выполнять другую рабо¬
18 Введение в Windows 3. 0 ту. При работе с программами, не ориентированными на Windows, они выполняют вывод обычным способом. При выборе пиктограммы Printer Manager раскрывается окно, отображающее текущее положение дел с печатью. Сюда входят: тип установленного принтера, способ его подключения к компью¬ теру, текущий статус. После имени принтера выдается список файлов, стоящих к нему в очередь на распечатку. Для распечаты¬ ваемого в данный момент файла сообщается процент уже выведен¬ ной части текста. Манипулируя мышью, можно при желании переупорядочить очередь файлов. ■ ПАНЕЛЬ УПРАВЛЕНИЯ (CONTROL PANEL) В версии 3.0 Control Panel полностью переделана. Font Editor (Редактор шрифтов) поддерживает пропорциональные шрифты. Под набором шрифтов понимается совокупность размеров для выбранного типа шрифта, адаптированных под определенные дис¬ плеи и принтеры. Windows поддерживает как экранные (растро¬ вые), так и принтерные (векторные) шрифты. Диалоговое окно Fonts позволяет увидеть пример, набранный указанным шрифтом. Выбор пиктограммы Colors (цвета) открывает диалоговое окно, позволяющее переопределять цвета, используемые стандартно для управляющих элементов, букв и фона текстового окна. Способ задания цветов полностью интерактивен. На одной стороне окна пользователь видит модель экрана Windows. На модели можно указать элемент, для которого требуется изменить цвет. На другой стороне этого окна расположен набор ”ящичков с краской”. Если интересующий цвет отсутствует, то его можно изготовить, смешав существующие. Выбранную схему использования цветов можно сохранить в файле. В исходную поставку Windows входит несколь¬ ко таких схем на выбор. Открыв пиктограмму Desktop, вы сможете поменять цвет ”обоев” для экрана Windows. ’’Обои” могут иметь как регулярный рисунок, так и представлять собой графическую картинку. ■ УПРАВЛЕНИЕ УСОВЕРШЕНСТВОВАННЫМ 386 РЕЖИМОМ Если вы используете усовершенствованный 386 режим, то Control Panel автоматически включает еще одну пиктограмму. Установка конфигурации в этом режиме включает выбор порядка, в котором прикладные программы взаимодействуют с такими устройствами, как принтеры. Опции Control Panel предлагают способы разреше¬ ния конфликтов между прикладными программами, когда по крайней мере одна из них не рассчитана на работу в Windows.
Инициализационные файлы 19 Назначение опции Always Warn 0Гредупреждать всегда) сообщать пользователю о всех попытках захватить порт ввода-вывода со стороны прикладных программ. Пользователь должен выбрать ту единственную программу, которая получит доступ к устройству. Опция Never Warn (Никогда не предупреждать) довольно опасна, поскольку не исключено, что две программы доберутся до принте¬ ра или модема одновременно и в результате выдадут абракадабру. Опция Idle позволяет задать промежуток времени, по истечении которого порт становится доступным запросившей его задаче. Помните, что одни принтеры могут при перезагрузке требовать больше времени, чем другие. Остальные опции усовершенствован¬ ного 386 режима касаются многозадачности и будут рассмотрены далее. ■ УПРАВЛЕНИЕ МНОГОЗАДАЧНОСТЬЮ Опция Minimum Time-SHce служит для задания времени в милли¬ секундах, которое должна отработать прикладная программа, прежде чем управление будет передано другой программе. Не имея возможности точно замерить миллисекунды, я решил не пытаться проверить выполнение этой опции. Опции Windows in Foreground и Windows in Background служат для управления разделением времени между интерактивными и фоновыми задачами. Эти пара¬ метры устанавливают пропорции между ними в квантах времени. Обычно приложения получают по одному кванту времени. Опция Exclusive in Foreground позволяет программам, с которыми идет диалог, получать 100% времени. Многозадачность Windows не распространяется на работу с дис¬ ком. Запись файлов на диск и форматирование не могут выпол¬ няться в фоновом режиме, когда вы делаете другую работу. Это сделано для безопасности. В противном случае могут возникнуть ситуации, когда дисковые операции не в состоянии будут успешно завершиться. ■ ИНИЦИАЛИЗАЦИОННЫЕ ФАЙЛЫ Имеются следующие инициализационные файлы: CONTROL.ENtt PROGRAM.ENTI SYSTEM.mi WIN.INI WEmLE.ENTI SYSTEM.INI и WE4.E4I — это стандартные инициализационные файлы. SYSTEM.INI содержит информацию об аппаратной конфи¬
20 Введение в Windows 3.0 гурации, а WIN.INI — сведения о выбранных в соответствии с набором выполняемых программ и вкусами пользователя режи¬ мах. Разделы используются для разделения режимов на группы. Использование квадратных скобок ([]) обязательно. Для записи каждого режима используется синтаксис: ключ = значение. Ключ — это имя режима, представляющее собой произвольную комбинацию букв и цифр, за которой следует знак равенства (=). Значениями режимов могут быть целые числа, строки или строки в кавычках в зависимости от типа соответствующего параметра. Имеются 4 типа прикладных программ: 1. Приложения Windows 3.0. 2. Приложения для более ранних версий Windows. 3. Не-Windows программы. 4 Резидентные программы. Приложения, рассчитанные на Windows 3.0, слегка отличаются от приложений, подготовленных для более ранних версий этой среды. Между собой они также различаются. Имеются некоторые приложения, разработанные специально для защищенных режи¬ мов 286 и 386 процессоров. Чтобы упростить жизнь пользователю и облегчить конфигурирование системы в зависимости от требова¬ ний приложений, в PJF Editor предусмотрены специальные сред¬ ства. ■ РЕДАКТОР .PIF ФАЙЛОВ (PIF EDITOR) Каждый файл FJF состоит из двух групп опций. Одна предназначена для реального и стандартного режимов, другая-для усовершенство¬ ванного 386 режима. PIF Editor автоматически показывает опции, соответствующие текущему режиму. Однако при желании вы всегда можете изменить режим изнутри редактора. Примечательно, что файлы PJF можно использовать для .BAT файлов DOS, если последние требуется вызывать из Windows. Опция ’’минимум требуемой памяти” файла PIF сообщает Windows, сколько памяти необходимо освободить для запуска прикладной программы. При этом не говорится, сколько памяти будет доступно, если она будет запущена. Файлы PIF можно использовать несколькими интересными способами. Для одного приложения можно определить альтернативный PJF файл, и сде¬ лать оба файла доступными из одной группы, позволив запускать одну программу в разных конфигурациях. Многозадачность включает возможность устанавливать приорите¬ ты основных и фоновых процессов, а также опцию Detect Idle Time (Обнаружение времени ожидания). Приоритеты задаются числами
Microsoft Word for Windows 21 от 0 до 1000 и служат для разделения времени центрального процессора. Так, например, если имеются 4 задачи с приоритетами 150, 100, 50 и 50, то, для того чтобы определить, какая часть процессорного времени достанется каждой из задач, нужно сло¬ жить приоритеты (150+100+50+50=350), при этом каждая из задач получит приблизительно (Приоритет/З50)*100% времени процессора. Иными словами, если 100% времени процессора раз¬ делить на сумму приоритетов всех задач и умножить поочередно на приоритет каждой из задач, то мы получим процент времени, который посвятит ей процессор. Если включена опция Detect Idle Time, то в те моменты, когда активная задача ожидает ввод с клавиатуры, Windows будет переключаться на другие приложения. ■ КЛАВИШИ АКТИВИЗАЦИИ, ОПРЕДЕЛЯЕМЫЕ ПРИКЛАДНОЙ ПРОГРАММОЙ Это средство существует только в усовершенствованном 386 режи¬ ме. Windows позволяет назначить прикладной программе клави¬ шу, при нажатии которой она становится активной. Эта клавиша работает независимо от того, в каком приложении вы в данный момент работаете. ■ УПРАВЛЕНИЕ ПАМЯТЬЮ Какие факторы влияют на количество памяти, находящейся в распоряжении пользователя? В настоящее время их несколько: • общее количество оперативной памяти в машине; • тип памяти; • режим, в котором работает Windows; • установленные драйверы. В реальном режиме Windows может использовать Expanded ото¬ бражаемую память стандарта ЬШ EMS. Если вы стартуете в реальном режиме, Windows автоматически определяет лучший способ использования этой памяти. С помощью опций командной строки вы можете получить более полный контроль над управле¬ нием памятью. ■ MICROSOFT WORD FOR WINDOWS Практическое знакомство с современной прикладной программой даст сведения о наиболее важных аспектах программирования для Windows. Редактор Word for Windows фирмы Microsoft — одна из программ, в максимальной степени использующая возможности
22 Вввдвнив в Windows 3.0 среды Windows 3.0. По этой причине я расскажу вам о наиболее интересных из предоставляемых этой программой возможностях. Word for Windows — это текстовый процессор, которым могут пользоваться все пишущие, независимо от профессии. В этой программе несколько изюминок. Во-первых, она содержит встро¬ енный процессор заголовков (Outline processor). Напомню, что процессор заголовков — это специальная программа, позволяю¬ щая создавать структуру заголовков и подзаголовков, запоминать связанные с ними куски текста, высвечивая нужные из них по мере надобности. Во-вторых, Word for Windows содержит боль¬ шинство средств, характерных для настольных издательских сис¬ тем. ■ РАБОТА С ЗАГОЛОВКАМИ Начну с процессора заголовков, поскольку этим уникальным ин¬ струментом пользуются многие профессионалы. Вне зависимости от типа документа, который вы создаете, он почти наверняка разделен на несколько частей или сегментов. Даже если конечный пользователь не знает о них, они все равно составляют существен¬ ную часть процесса написания. Процессор заголовков позволяет выбирать и рассматривать лишь интересующие в данный момент части документов. Представьте на миг, что вы пишете сценарий для мульти-медиа. При этом вы пользуетесь структурой актов и сцен, типичной для крупных систем. Тогда можно сделать види¬ мыми только те сцены, которые нужны для одной определенной цели. Вам может потребоваться изменить действия одного из лиц. В этом случае необходимо высветить только те сцены, в которых оно появляется. В любой момент вы можете сжать текст, оставив от него один скелет. Вы уже могли заметить, что процессор заголовков можно использовать во многих случаях. Для программистов он может быть полезен, поскольку помогает сохранять структуру програм¬ мы ясной. Опция Arrange All позволяет расставлять открытые окна в виде паркета, нажатие на кнопку мыши позволяет начать взаимодействие с документом из любого окна. ■ NEWWAVE Фирма Hewlett-Packard — одна из тех, чья деятельность не сво¬ дится к копированию принципов графического пользовательского интерфейса, популяризированного фирмой Apple. В окружении NewWave сделано несколько новых важных шагов по сравнению с моделью, предложенной в компьютерах Xerox и Macintosh. Безусловно, общий стиль NewWave объектно-ориентированный,
Office Tools 23 но с появлением этой системы данный термин получил дополни¬ тельный смысл. Объекты для конечного пользователе аналогичны объектам для программиста. В NewWave объект — это файл данных вместе с обрабатывающей его программой. Такая связь более содержательна, чем обычная возможность: указав файл, загрузить программу, которая использует этот файл в качестве параметра. С помощью активных объектов Object Management Facility производит многие операции, в том числе поддержание связей между прикладными программами. Автоматизация упро¬ щает процесс подготовки с помощью нескольких программ состав¬ ного документа, включающего, скажем, текст, статистику и графику. В этой среде может работать средство Agent, позволяю¬ щее записать последовательность операций в нескольких разных приложениях и автоматизировать ее. Если документ состоит из элементов, построенных различными прикладными программами, например текстовым процессором, электронной таблицей и программой построения графиков, он называется составным. NewWave содержит еще один способ объе¬ динения объектов — используя "контейнеры”. ”Контейнер” — это объект, состоящий из других объектов и показывающий их имена или пиктограммы, Объект А содержит объект В, если между ними существует связь по вложенности. Кроме этого объекты могут иметь информационные связи. Когда несколько прикладных про¬ грамм работают с одними данными, при этом не происходит одномоментного копирования последних. Вместо этого строятся информационные связи. При необходимости изменить данные все разделяющие их объекты автоматически получают обновленную версию. Как следствие этого Object Manager для поддержания цело¬ стности должен создавать резервные копии составных объектов. Для того чтобы иметь доступ ко всем возможностям NewWave, прикладная программа должна быть написана в расчете на это окружение. Однако некоторыми его свойствами могут пользовать¬ ся и приложения, специально не ориентированные на NewWave, также есть способ улучшить взаимодействие программы со средой NewWave с помощью процесса инкапсуляции. NewWave Office — окно, аналогичное по своим функциям MS Windows Executive. Оно содержит пиктограммы с изображением папок и мусорной корзины и напоминает пользователю этим обычную конторскую обстановку. ■ OFFICE TOOLS В NewWave имеется уникальный тип объекта — Office Tool (конторский инструмент). Его можно рассматривать как суперобъ¬ ект. Единожды созданный он не имеет себе подобных. Конторские инструменты отличаются от других объектов NewWave Office
24 Введение в Windows 3. 0 пиктограммами, создающими иллюзию трехмерности. Инстру¬ менты не могут создаваться или уничтожаться пользователями. Их создание — прерогатива разработчиков. Стандартные инструменты NewWave включают File Drawer (Ото¬ бражение файлов), WasteBasket (Корзина для использованных бумаг), Printers и Dictionaries (Словари). File Drawer — это кон¬ тейнер, в котором могут помещаться до 200 произвольных объек¬ тов. Эти объекты обычно содержат папки, которые в свою очередь могут содержать до 200 объектов, в том числе и другие папки. Однако один объект может одновременно находится лишь в одной папке. Инструмент Printers позволяет распечатать любое количе¬ ство выбранных объектов на любом из принтеров, установленных для NewWave, в том числе и на подключенных через сеть. Инст¬ румент Dictionary используется для создания словаря, который может использоваться совместно с основным словарем программы проверки орфографии. Объект WasteBasket интересен тем, что это контейнер, но исполь¬ зуется он не для хранения, а длй ’’выбрасывания”. Хотя он позволяет уничтожить файл, но файл не исчезает немедленно, а в течение еще некоторого времени может быть восстановлен. К настоящему времени с помощью программистов в NewWave перенесены некоторые впечатляющие приложения. Разумеется, в первую очередь это уже популярные программы, такие, как элек¬ тронные таблицы Microsoft Excel. Фирма Samna предоставила новую версию программы Ami — Ami Professional, представляю¬ щую собой мощный текстовый процессор. В число многообещаю¬ щих входят такие пакеты, как Graph Plus фирмы Micrografix, Forest & Trees — интеллектуальная СУБД фирмы Channel Computing, программа создания полноценной мультишшкации Video NewWave (фирма New Media Grafix). ■ TOOLBOOK Toolbook — это конструктор, позволяющий ”собирать” электрон¬ ные книги и другие прикладные программы, практически не прибегая к программированию. С применением программирова¬ ния можно добиться еще более впечатляющих результатов. В интерактивном режиме создаются все основные экраны програм¬ мы, а для расширения функциональных возможностей пишутся утилиты. Toolbook содержит средства гипертекста, выполненные в метафоре книги, дающей возможность пользователям, не имею¬ щим опыта разработки программ, создавать программное обеспе¬ чение, ориентированное не гипертекст. Разработке прикладных программ непрограммистами способствует возможность восприя¬
Toolbook 25 тия экрана программы как книги — предмета для большинства людей более привычного. Метафора книги богаче, чем метафора картотеки, поскольку книга — это сложная структура, включаю¬ щая индекс, оглавление, библиографию и приложения. Появляет¬ ся возможность строить электронные книги, сочетающие сильные стороны как бумажных, так и электронных носителей информации. Программирование в стиле Toolbook особенно подходит для постро¬ ения приложений для Windows 3.0. Таким образом могут быть построены отнюдь не все прикладные программы, но большинство из них. Собрать прототип из готовых частей — дело нескольких дней, в то время как на программирование могут уйти недели и даже месяцы. Хотя это кажется преувеличением, но смею утвер¬ ждать, что это правда. Toolbook: Объекты и сообщения Программы в системе Toolbook строятся из объектов и сообщений, что облегчает их разработку. Каждое приложение Toolbook содер¬ жит иерархию взаимосвязанных объектов со встроенной схемой рассылки сообщений. Книги и другие приложения, разрабатыва¬ емые для Toolbook, конструируются в виде иерархии объектов, посылающих и принимающих сообщения, которые отражают со¬ общения уровня Windows. На языке OpenScript можно кодировать обработчики, ответственные за реакцию на сообщения, которые можно послать как из других программ на OpenScript, так и из окна команд. Хотя система объектов ТооШоок не содержит кон¬ цепции класса, а следовательно, и механизма наследования, в ней возможны некоторые ’’трюки’*, в определенной степени компенси¬ рующие эту нехватку. Рассматриваемая в этой книге система программирования позволяет пользоваться большинством из этих трюков, чтобы, насколько это возможно, восполнить отсутствие классов. Графика и анимация К числу наиболее привлекательных особенностей системы Toolbook относится использование объектно-ориентированной гра¬ фики и анимации. Последняя позволяет буквально оживить элек¬ тронные книги. Движущиеся элементы существенно отличают такие книги от обычных пассивных изданий. Они более не пред¬ ставляют собой сухих, инертных наборов текстов. Они как бы танцуют перед пользователем на экране. В Toolbook имеется множество способов анимации графики и текста, включая опти¬ ческие эффекты стирания и растворения. На этих эффектах по¬ строены переходы между страницами.
26 Введение в Windows 3.0 Серьезная работа с Toolbook неизбежно включает подготовку и запись картинок в библиотеку, в которой их легко найти и из которой просто извлечь. Изначально Toolbook включает коллек¬ цию клипов и программу-менеджер. К сожалению, эта программа в определенной степени замкнута. Вы можете добавлять и извле¬ кать изображения и страницы, но создавать новые типы картинок или новые программы нельзя. Средства анимации позволяют перемещать изображения, делая их на время невидимыми, а затем показывая в соответствии с заранее разработанным планом или по требованию пользователя. Элемен¬ ты могут менять размер и место расположения, поворачиваться и даже искажаться. OpenScript Модуль, написанный на языке OpenScript системы Tookbook, — гибкое средство, в максимально возможной для столь простого языка степени напоминающее английский. Чаще всего он исполь¬ зуется для написания обработчиков сообщений. Обработчик на языке OpenScript — это фрагмент кода, определяющий действия объекта в ответ на сообщения. Типичный пример использова¬ ния — создание для электронной кнопки программы, которая определяет действия кнопки в ответ на сообщение ButtonUp. Имеются три основные способа доступа программы на языке OpenScript к ресурсам других приложений MS Windows: 1. Использование протокола DDE (Dynamic Data Exchange — динамический обмен данными). 2. Выполнение внешних функций, записанных на языке ко¬ манд приложения. 3. Вылов функций из DLL (Dynamic Link Library — динами¬ ческих библиотек). Следующие параграфы содержат описания этих трех техник. Динамический обмен данными (DDE) Обмен данными между приложениями Toolbook и Windows — это улица с двусторонним движением. Внешние программы могут запрашивать данные Toolbook, а приложения Toolbook — пользо¬ ваться данными других программ. Это может быть оформлено в виде удобного протокола вида ”дай-возьми”. Так, например, в Toolbook нет электронных таблиц, но в таких случаях вы можете совместить средство вызова внешних функций с динамическим обменом данных и достичь требуемого результата.
Toolbook 27 Доступ к динамически подключаемым библиотекам DLL Одним из средств, увеличивающих мощность язьнсов сценариев типа OpenScript (далее мы будем использовать термин ’’скрипт”), служат динамические библиотеки Windows. DLL — это модульный подход к совместному использованию несколькими программами общего, единожды загружаемого кода. Каждая программа, вместо того чтобы строить собственную копию кода, обслуживается един¬ ственной копией. Скрипт может пользоваться функциями из ди¬ намических библиотек. К Windows 3.0 прилагается пример библиотеки PBrush.DLL, содержащей код утилиты PaintBrush. Toolbook поддерживает одновременную работу с восемью DLL. Для доступа к ресурсам окружения Windows 3.0 включает три таких библиотеки, кроме этого большинство прикладных программ включают в себя по крайней мере одну доступную извне DLL. Внешнее выполнение OpenScript позволяет выполнять команды в других приложениях Windows, например в электронной таблице Excel. Благодаря этому можно строить приложения, пересекающие границы нескольких программных продуктов, интегрируя их в единый процесс реше¬ ния задачи. В идеале пользователь должен быть в состоянии полностью сосредоточиться на задаче, не отвлекаясь на использу¬ емые инструменты. В настоящее время программное обеспечение чрезвычайно ’’навязчиво” в том смысле, что пользователь должен думать и об инструментальной программе, и о решаемой задаче. Возрастающие интеграция и стандартизация графического интер¬ фейса позволяют сделать важный шаг к созданию окружения, не требующего от пользователя понимания, какую программу он использует на каждом этапе. Интерфейс высокого уровня дает возможность не задумываться над тем, какая программа активна в данный момент. Средство внешнего выполнения языка OpenScript предназначено для создания таких программных систем. Благодаря процедурам модульной разработки, многие из которых могут быть закодированы в виде скриптов, автоматизируется большая часть работ. Примером такой процедуры будет создание фона для новой страницы путем модификации существующего фона. Последний может быть отредактирован внутри той же книги без внесения изменений в исходный фон. Хотя это и не очень сложные процедуры, они могут оказаться не очевидными. По-видимому, один из наиболее трудоемких процессов цикла разработки в среде Toolbook — это выбор ключевых слов и соот¬ ветствующих им скриптов. Было бы замечательно найти способ автоматизации процесса поиска вхождения в текст ключевых слов. Сложность состоит в том, что на момент кодирования трудно
28 Введение в Windows 3.0 предсказать все интересующие пользователя детали. Один из возможных подходов состоит в том, чтобы программа останавли¬ валась на всех страницах в указанном пользователем диапазоне и спрашивала, следует ли занести в определенное поле ключевое слово. Программу такого типа можно без труда написать на языке OpenScript. Электронные библиотеки Средства окружения Windows и язык OpenScript позволяют созда¬ вать не только электронные книги, но и целые электронные библиотеки. Пользовательский интерфейс рассчитан на поддерж¬ ку целой системы книг, скажем энциклопедии. Одно из главных ограничений системы Toolbook — невозможность одновременного открытия более 10 файлов. При разработке больших программ следует помнить об этом. В приложениях, загружающих дополни¬ тельные книги, необходимо следить за тем, чтобы общее их число не превышало 10. При необходимости загрузить дополнительно еще одну книгу требуется спросить пользователя, какую из уже загруженных книг следует выгрузить. ■ WINGZ: ЭЛЕКТРОННАЯ ТАБЛИЦА НОВОГО ПОКОЛЕНИЯ Wingz — еще одно достижение в программировании для среды Windows. Этот продукт фирмы Informix представляет собой элек¬ тронную таблицу. Электронные таблицы — весьма консервативная область программного обеспечения, однако Wingz обогатил ее несколькими новшествами. Дальнейшее изложение призвано по¬ казать, что представляет собой с точки зрения пользователя гра¬ фический интерфейс Windows. В настоящее время на рынке много мощных и привлекательных программ, и вы можете подумать, что Wingz еще одна в этом ряду. Однако за последние 10 лет число изобретений в области электрон¬ ных таблиц можно пересчитать по пальцам. Прогресс в основном состоял во внесении всевозможных дополнений. Я считаю, что в этой области имеется широкий простор для творчества, и не удивлюсь, если увижу здесь что-либо действительно новое. Таблицы Wingz первоначально были разработаны для компьютера Macintosh. Сейчас они доступны на ГОМ PC под управлением Windows 3.0 и на рабочей станции Sun. Существенный прорыв в технологии электронных таблиц выразился в появлении следую¬ щих трех вещей: • быстрых, гибких трехмерных таблиц; • управляемой пользователем многоцветности;
Wingz: электронная таблица нового поколения 29 мощного языка программирования HyperScript. Безусловно, нельзя не упомянуть пиктограммы Tool Box и Operators, позволяющие создавать таблицы практически при по¬ мощи одной мыши. Познакомимся с Wingz После того как вы поняли, что представляет собой Wingz, у вас, естественно, мог возникнуть вопрос: "Почему никто не сделал этого раньше?” Это риторический вопрос, главное, что мы имеем новые важные возможности, которые, вероятно, в скором времени появятся и у других таблиц. Что появилось в Wingz сравнительно со стандартным набором средств электронной таблицы? В общем, всего несколько вещей. Наиболее существенной будут мощные и изящные 3-мерные таблицы и графики двадцати типов. Но есть и кое-что еще. Оригинальным образом использован цвет. Каждая клетка может использовать свою комбинацию цвета фона и букв, включая цвета из палитры пользователя. Обладателям цветных мониторов открыт совершенно новый путь к ясным, понятным и привлекательным таблицам. Но это еще не главное. Wingz содер¬ жит встроенный язык HyperScript, оставивший далеко позади другие макроязыки для электронных таблиц. Одного этого доста¬ точно, чтобы считать Wingz революционно новым продуктом, а это еще не все. Средний пользователь Excel немедленно полюбит Wingz. Правиль¬ ное сочетание презентационных возможностей и вычислительной мощи делает этот продукт привлекательным. Как я заметил, ранее за десять последних лет электронные таблицы не претерпели существенных изменений. По-прежнему требуется чудовищно нудная работа и упорство, чтобы с их помощью получить что-ни¬ будь полезное. Язык HyperScript, однако, дает возможность суще¬ ственно изменить ситуацию. Правильное использование этого простого, но мощного язьпса позволяет автоматизировать многие вещи. Еще важнее возможность строить родовые приложения, обслуживающие широкий спектр запросов и непрерывно совер¬ шенствующиеся с течением времени. Это придает смысл описан¬ ной технологии. В настоящее время, когда электронные таблицы обрабатывают огромные объемы данных, новые подходы способны сократить бесконечные часы нудной работы. Из сказанного стано¬ вится ясным, что Wingz и его производные имеют большое будущее. Новое в редактировании электронных таблиц Wingz включает некоторые новые средства редактирования элек¬ тронных таблиц, в частности набор инструментов, основанный на пиктограммах. Например, пиктограммы Operators служат для
30 Введение в Windows 3.0 внесения знаков операций (например, знака доллара, обозначаю¬ щего абсолютный адрес клетки) в формулу с помощью одного нажатия кнопки мыши. Функции склейки усовершенствованы по сравнению со способом, реализованным в Excel. Тип склейки выбирается в диалоговом окне при помощи электронных кнопок. Большая часть типов, среди которых осуществляется выбор, при¬ сутствует на экране одновременно. Изменение размеров строк и столбцов таблицы осуществляется простым нажатием кнопки мы¬ ши на рамке строки или столбца и последующим растягиванием (сжатием) элемента до требуемого размера. Между клетками раз¬ ных таблиц могут устанавливаться связи, правда, для этого вторая таблица должна быть активна, а пересчет не происходит автома¬ тически после внесения изменений. Мощная графика Пакет Wingz предоставляет пользователю 20 типов 3-мерных графиков, включая даже топографические карты. Поддерживают¬ ся также графики, использующие полярную систему координат. Каждый тип графиков позволяет осматривать себя со всех сторон, изменяя угол зрения с помощью включенных в диалог регулято¬ ров. Возможен учет перспективы при построении изображений. Но на этом дело не кончается. Имеются средства рисования, с помощью которых можно импортировать и наклеивать на графики созданные вне пакета картинки и клипы. Предусмотрены допол¬ нительные возможности для создания пользовательской палитры путем смешения цветов. Пользовательские управляющие элементы и диалоги Wingz позволяет в интерактивном режиме создавать элементы управления, скажем кнопки, и размещать их в нужном месте на экране; делать такие рутинные действия, как добавление к тек¬ стовому полю электронной таблицы линии прокрутки. Вы можете записать последовательность операций в виде макроса и связать его с кнопкой пользователя. Элементы управления и другие объ¬ екты могут объединяться в группы и работать сообща. Объекты присоединяются к клеткам таблицы. Изменение размеров клеток влечет за собой изменение размеров объектов, поэтому их обычно создают после завершения разметки таблицы. Для объектов и управляющих элементов существует множество стилей изображе¬ ния, включая трехмерный стиль Drop, использующий создавае¬ мый тенями эффект объемности. Доступ ко всем этим средствам можно получить с помощью языка HyperScript.
Wingz: электронная таблица нового поколения 31 Секреты мастерства В системе Wingz объектами называют почти все расширения таблицы. Для создания объектов служат инструменты иа Wingz Tool Box. Пользователь может разработать целиком весь экран, раскрывая при этом все возможности ориентированных на графи¬ ку таблиц. При первом взгляде на систему Wingz можно не заметить таких ее достоинств, как наличие многоформульных критериев поиска в базах данных, использование многооконности, возможности создания таблиц, зависящих от двух переменных, нестандартные форматы чисел и средства выбора сетки. Клетки Wingz, например, не обязаны иметь один и тот же размер и, как уже упоминалось, могут быть раскрашены в разные цвета. Запом¬ нив несколько правил и немного поэкспериментировав, вы сможе¬ те за счет упомянутых средств придать данным таблицы удивительный вид. Использование различных цветовых схем по¬ зволяет мгновенно по цветовым пятнам оценивать содержащуюся в таблицах информацию. Электронные таблицы превращаются из места хранения данных в привлекательный инструмент рекламы. Построение баз данных Подобно большинству современных электронных таблиц Wingz позволяет строить базы данных, поддерживающие быстрое и лег¬ кое редактирование и хорошую визуализацию данных. Для работы с базами данных можно использовать разнообразЕые макросы и программы. Для разработки табличных баз важно строить форму¬ лы, макросы и программы, не зависящие от числа строк таблицы. Вы должны уметь добавлять или удалять записи по мере надобно¬ сти, не заботясь о том, как работает та или иная утилита. Wingz содержит развитые средства задания запросов на поиск в базе данных. Например, имеется возможность задавать поля, не вклю¬ чаемые в результат поиска, который по выбору пользователя может быть подсвечен или занесен в отдельную группу клеток. Операции с матрицами Массив клеток, с которым оперируют как с целым, называется матрицей. Если у матрицы число столбцов равно числу строк, она называется квадратной. Wingz позволяет работать с очень боль¬ шими матрицами. Удавалось строить и выполнять операции с матрицами 96 * 96. Можно задать и большие матрицы. Матрицы могут быть использованы для разных целей, например для реше¬ ния систем, включающих до 100 линейных уравнений. В клетки матрицы можно занести коэффициенты системы и автоматически получить решение системы.
32 Введение в Windows 3.0 Разработка совершенствуемых моделей бизнеса В области деловых применений электронные таблицы удобно использовать для разработки точных численных моделей бизнеса. Подобные модели нельзя сделать за один прием. Идеальный метод разработки табличной модели состоит в непрерывном внесении изменений, повышающих точность модели. Существенным пре¬ пятствием на этом пути бывают форматы формул. У электронных таблиц отсутствует простой способ уточнения формул. Однако такой способ можно предусмотреть. Один из вариантов состоит в том, чтобы располагать коэффициенты формул в клетках таблицы. При этом вместо констант в формулах будут стоять ссылки на эти клетки. Изменение формул можно свести к изменениям значений клеток коэффициентов. Можно придумать процедуры, автомати¬ зирующие процесс уточнения. Прогноз продаж и прибылей Wingz позволяет использовать апробированные методы прогнози¬ рования, такие, как многокритериальная регрессия, алгоритм Box- Jenkins, временные ряды, метод Census X-11, систему Foran. При возможности следует строить родовые таблицы, которые можно легко адаптировать, уточнять и использовать повторно. В этом и заключена суть новых электронных таблиц. Многие из современ¬ ных таблиц содержат средства, приспособленные в основном для презентаций. Предполагается, что в определенных обстоятельст¬ вах подобные украшения окупаются. Основное достоинство Wingz состоит в том, что эта система делает наилучшим образом все, что умеют делать современные электронные таблицы. Усовершенствованная техника моделирования Более совершенная техника моделирования предполагает постро¬ ение модели некоторого предприятия вместе с включающей его отраслью. Такая модель способна предсказывать необычные и важные события. В статистическом моделировании обычно выби¬ раются различные индикаторы, про которые известно, что они позволяют прогнозировать изменения рынка. Часто эти индикато¬ ры никак напрямую не связаны с данным бизнесом. Зависимость имеет корреляционный характер. Предложены методы использо¬ вания знаний об индустрии, не сводящиеся к экстраполяции предшествующих тенденций. Продвинутая техника моделирова¬ ния позволяет не только экстраполировать предшествующую ста¬ тистику, но и строить предсказания исходя из полной модели. Несмотря на всю сложность подобной деятельности, Wingz позво¬ ляет пытаться решать подобные задачи прогнозирования.
Wingz: электронная таблица нового поколения 33 Введение в Hyperscript Третьей (после 3-мерной графики и многоцветных таблиц) особен¬ ностью Wingz,,дeлaющeй эту систему поистине уникальной, явля¬ ется язык Hyperscript. Это язык программирования "полного профиля”, содержащий набор поддерживающих разработку инст¬ рументов. Это самый мощный язык программирования для элек¬ тронных таблиц, какой я когда-либо видел. Кроме обычных макросов, автоматизирующих повторяющиеся действия, Hyperscript позволяет создавать четыре специализированные типа макросов: связанные с таблицей, с полем, с кнопкой и управляю¬ щие. Эти макросы связываются с объектом соответствующего типа и обрабатывают поступающие от пользователя события. Кроме выполнения макросов из файлов команды Hyperscript могут выполняться из строки ввода и через систему меню Wingz. Строка ввода удобна для интерактивного управления сеансом работы, а также для проверки возникающих идей, перед кодированием их в виде макросов. Я должен также упомянуть поддержку современ¬ ных управляющих структур и рекурсии. Создание приложений на языке Hyperscript Процесс написания приложений Hyperscript включает редактиро¬ вание, отладку, компиляцию макросов и связывание их с табли¬ цами, текстовыми полями или управляющими элементами. Макросы редактируются в специальном окне, которое называется окном макросов. Поиск синтаксических ошибок производится при компиляции. Для отладки используется традиционная техника: включение выдачи сообщений и использование комментариев для изоляции сомнительных мест. Из языка Hyperscript можно до¬ браться до любой формулы или пункта меню. С помощью этого языка можно задавать собственные функции. Эти функции ничем не отличаются от других функций Hyperscript. Подстановка, а точнее операторы GET SCREPT позволяют ссылаться на них из других файлов макросов. Правильное использование этого средст¬ ва позволяет приспособить язык к вашим потребностям. Таким образом открытый язык для открытого продукта позволяет созда¬ вать открытые приложения. Программирование интерфейса с пользователем Язык Hyperscript содержит средства полного контроля над поль¬ зовательским интерфейсом, включая возможность добавления но¬ вых подменю и даже новых строк меню. Wingz содержит пример, из которого ясно, как с помощью Hyperscript может быть построен пользовательский интерфейс Wingz. Использование ключевого слова submenu позволяет создавать иерархические меню. Имеется 2-857
34 Введение в Windows 3.0 средство создания ’’всплывающих” меню, которые не привязаны к определенной позиции основного меню. С помощью команды ADD POPUP MENU вы можете определить, в каком месте экрана оно будет появляться. Программирование графиков Команда DRAW помогает создавать интересные графики. Можно разработать свои собственные типы двух- и трехмерных графиков и, как это принято, связать их со специальными электронными кнопками или пунктами меню. Пользовательская графика может включать сплайны, дуги, стрелки, специальные символы и З-мер- ные тени. Можно разрабатывать большие графические приложе¬ ния, включающие интерактивное взаимодействие с пользователем, пошаговое построение графиков, допускающее в любой момент прерывание его пользователем, который может изменить параметры таблицы. В принципе, описанная ниже воз¬ можность использования внешних функций позволяет построить графическую систему любого типа. Hyperscript дает возможность строить открытые системы, зависящие только от мастерства поль- зователя-разработчика. Ограничения Поскольку вывод из прочитанного очевиден, я в заключение обзора постараюсь сказать о недостатках этого продукта, если смогу их найти. Одним из них является ограничение на функцию GOAL. Ей необходимо задать начальное приближение, затем она проведет до 20 итераций, уточняющих результат. В таблицах Excel, Quattro Pro и 123/G это сделано удачнее. Если кто-нибудь скажет, что для этого можно воспользоваться языком Hyperscript, то он будет не прав. Во-первых, большинство пользователей не умеют писать алгоритмы такого рода, а, во-вторых, для того, чтобы достичь приемлемой скорости выполнения, их следует кодировать на язы¬ ке ассемблера. Другой недостаток состоит в отсутствии формульного задания матричных операций. В этом отношении Wingz напоминает Quattro Pro, в котором матричные вычисления выполняются ин¬ терактивно из меню. Использовавшим формулы с матрицами системы Excel придется потрудиться, переходя в Wingz. Я бы посоветовал включить в следующие версии Hyperscript набор операций для выполнения других программ Windows и для пере¬ дачи им команд. Это сделает Hyperscript не только языком для Wingz, но и для Windows в целом. И наконец, в краткий список моих пожеланий входит возможность вызова из языка Hyperscript
Сеанс работы с Windows 3.0 35 функций, содержащихся в библиотеках DLL. Может быть, для этого придется ввести специальный формат вызова. Это средство откроет перед языком Hyperscript целый мир. ■ СЕАНС РАБОТЫ С WINDOWS 3.0 Я решил отобразить на экране предопределенную схему использо¬ вания цветов. Для этого, не покидая свой текстовый процессор, вызвал наверх Task Switch и переключил управление на Program Manager, а оттуда запустил Control Panel. Пока я это делал, окна начали накладываться ’’кипой” поверх текстового процессора, оставляя, однако, для меня некоторую часть его окна, таким образом сохраняя за мной возможность вернуться в него одним нажатием кнопки мыши. Я раскрыл пиктограмму Colors (цвета) окна Crayon и как только появился макет экрана Windows, нажал кнопку мыши на элементе Color Palette. Раскрылось новое окно с ”ящичками красок”, среди которых нужно было выбирать. Я решил покрасить верхнюю строку меню в темно-зеленый цвет. Для этого я выбрал соответствующую часть макета окна, а затем нажал кнопку мыши на зеленом цвете. В результате верхняя линия меню на макете сделалась зеленой. Я выбрал заголовок и нажал кнопку мыши на желтом цвете, его цвет также изменился. Текст меню должен быть ярким, четким и ясно видимым. Я выбрал малино¬ вый. Затем я проделал это же с линией прокрутки, и она стала светло-зеленой. Я нашел, что это сочетание очень приятно глазу, и направился в меню File с целью сохранить эту схему использо¬ вания цветов на жестком диске. Далее я захотел попробовать, как работает ”горячая клавиша” PrintScreen на одной графической программе, не ориентированной на работу в Windows. Я вернулся в Program Manager и вошел в группу не-Windows программ. Там я выбрал пиктограмму Autosketch. Запустилась программа Autosketch фирмы Autodesk, в которой я загрузил выполненный на синем фоне чертеж локомо¬ тива. Когда построение было закончено, я нажал комбинацию клавиш Shift-PrintScreen и монитор мигнул. По этому признаку я понял, что, вернувшись в Clipboard среды Windows 3.0, я получу результат. Я покинул программу Autosketch, и на экране вновь возникла среда Windows. Я открыл пиктограмму Paintbrush. С помощью опции View этой программы удалил палитру и набор инструментов с экрана, тем самым получив его в свое распоряже¬ ние целиком. Затем я выбрал опцию Paste в меню Edit и после нескольких подготовительных помигиваний курсора мой локомо¬ тив импортирован в окружение Windows, где и был сохранен в форме побитового изображения.
36 Введение в Windows 3. 0 Далее я попытался использовать эту картинку в качестве фона экрана Windows. Однако она напомнила мне стол моего отца, сплошь покрытый синими чернильными пятнами. Поскольку это не совсем то, что мне нужно, я вызывал другую графическую программу — Music Printer Plus, которая используется для редак¬ тирования и проигрывания музыки в студии MIDI (цифровой интерфейс с музыкальными инструментами). Я загрузил аранжи¬ ровку моей песенки ,rForever” для небольшого ансамбля, нажал Shift-PrintScreen, и экран снова подмигнул мне. Я вышел из программы, опять вошел в PaintBrush и скопировал содержимое Clipboard. Получив в программу, предназначенную для графиче¬ ского дизайна, ноты, я не удержался и раскрасил нотный лист в несколько цветов. Меня всегда интересовало, что произойдет, если раскрасить ноты. Действительно, получается неплохая картинка. Я сохранил ее. Далее я вошел в утилиту Desktop и использовал многоцветный нотный лист в качестве фона экрана. Мне захотелось выполнить обратное действие: перенести инфор¬ мацию из Clipboard в не-Windows программу. Для начала я вызвал CTALK VIEWS Browser — объектно-ориентированный инстру¬ мент, весьма полезный, но со слабым редактором. Я хотел найти какой-нибудь код, похожий на тот,что хотел написать сам, чтобы отредактировать его в соответствии со своими потребностями. Я нашел искомый текст, выделил его при помощи мыши и скопиро¬ вал на Clipboard. Затем я вызвал Programmer’s Workbench, нажал на AltEsc и свернул его в пиктограмму DOS. Затем я вызвал управляющее меню этой пиктограммы и поочередно воспользовал¬ ся опциями Edit и Paste. Затем снова раскрыв пиктограмму, увидел, что в редактор была вклеена моя программа. Я сохранил результаты и закончил сеанс работы с Windows, удовлетворен¬ ный тем, что эта система работает в полном соответствии с документацией.
Глава 2 Я ЧТО ТАКОЕ ОБЪЕКТНО¬ ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ? ■ ВВЕДЕНИЕ Несмотря на то что эта книга посвящена объектно-ориентирован¬ ному программированию (ООП) для Windows, для начала важно познакомиться с объектно-ориентированным программированием как таковым, вне связи с окружением Windows. В результате читатель сможет более объективно подойти к тому, что будет рассматриваться далее. Предполагается, что читатель умеет про¬ граммировать, но не знаком с объектно-ориентированным подходом. С моей точки зрения, ООП представляет собой самую высокую стадию развития технологии программирования. В той же мере, в какой структурный подход к программированию в языках Алгол и Паскаль был новым шагом по сравнению с Фортраном, ООП представляет собой новую стадию эволюционного развития. По мере развития технологии, раньше или позже, большинство про¬ граммистов оценит его по достоинству. По этой причине в индуст¬ рии производства программ ему уделяется постоянное внимание. ■ ПАРАДИГМ Ы П РОГ-РАМ M И PO ВАН И Я Для некоторых покажется удивительным, что в программирова¬ нии существуют различные парадигмы, представляющие собой разные подходы к написанию программ. Большинство программи¬ стов знакомы лишь с одной из них: процедурным программирова¬ нием. Кроме этого существует объектно-ориентированная, о которой пойдет речь в этой книге, а также имеется программиро¬ вание, основанное на правилах, логическое программирование, параллельное программирование, визуальное программирование,
38 Что такое объектно-ориентированное программирование? программирование, основанное на потоках данных, и другие пара¬ дигмы. Почему же их столько? Отчасти потому, что программирование сравнительно новая дис¬ циплина, а отчасти из-за желания людей решать разные задачи. Кроме того, наиболее популярная в данный момент компьютерная архитектура не является единственной. В настоящее время прово¬ дится большое число экспериментов с машинами, имеющими нетрадиционные архитектуры, многие из которых рассчитаны на применение других парадигм программирования. Общая природа цифровых машин позволяет с большей или меньшей эффективно¬ стью моделировать одну архитектуру с помощью другой. Из архи¬ тектур наиболее удачны те, в которых за счет аппаратуры и программного обеспечения достигнуты наивысшая скорость и про¬ стота использования. ■ ОБЪЕКТНО-ОРИЕНТИРОВАННЫЙ ПОДХОД Если вы зададите вопрос? ”Что такое объектно-ориентированное программирование?”, то будьте готовы услышать от разных людей разные ответы на него. Во многих случаях этот термин употреб¬ ляется не в его точном смысле. Даже в тех случаях, когда вопрос касается не конкретных продуктов или инструментов, акцент опять-таки будет поставлен по-разному, в зависимости от того, какие именно стороны объектно-ориентированного программиро¬ вания собеседник считает наиболее полезными и интересными. Получается, что каждый пользователь вкладывает в ответ свое собственное понимание этого подхода. В каком смысле этот термин используется в данной книге? Прежде чем двигаться дальше, попытаемся исключить всякую путаницу в терминологии. Это необходимо, поскольку термин ’’объектно-ориентированный” используется по крайней мере еще в одном смысле, а именно для обозначения специального типа прикладных графических программ. Этот термин в последнем случае служит для противопоставления пиксел-ориентированных и экранно-ориентированных графических программ. Объектно¬ ориентированная графика предполагает, что полная картина скла¬ дывается из нескольких самостоятельных объектов, над каждым из этих объектов всегда можно выполнить отдельное преобразова¬ ние. Обычная же практика предполагает хранение массива пиксе¬ лов. В этой книге слова ”объектно-ориентированный” имеют другой смысл. Я использую этот термин для описания стиля и инструмента программирования, независимо от типа приложения. Верно также, что с помощью объектно-ориентированной техники программирования удобно создавать графические программы опи¬ санного типа. Впрочем, объектно-ориентированные графические приложения могут быть закодированы и без применения объект¬
Объектно-ориентированный подход 39 но-ориентированной техники программирования, и, наоборот, пиксел- и экранно-ориентироваыные графические программы мо¬ гут быть написаны с применением объектноориентированного подхода. В данной книге прикладная программа будет называться объектно-ориентированной, если она написана с применением объектно-ориентированной методологии программирования. Для начала мы будем пользоваться следующим предварительным определением ООП. Объектно-ориентированное программирование (ООП) — это способ программирования, обеспечивающий модульность программ за счет разделения памяти на области, содержащие данные и проце¬ дуры. Области могут использоваться в качестве образцов, с кото¬ рых по требованию могут делаться копии. Сделаем остановку, чтобы разобраться, что имеется в виду. В соответствии с этим определением объект понимается как участок памяти. Самое время развеять некое недоразумение. Многие вос- прияимают слова ’’объектно-ориентированный” буквально, счи¬ тая, что это тин программирования, имеющий дело с реальными, земными объектами, а не с состоянием программируемого компь¬ ютера. Доля правды в этом, конечно, есть, но это не определяющая сторона ООП. Наиболее важен выбранный способ разделения памяти компьюте¬ ра, позволяющий каждому из модулей или объектов функциони¬ ровать относительно независимо друг от друга. Такое разделение обладает многочисленными преимуществами при программирова¬ нии. Правда, большинство программистов выделит в первую оче¬ редь те из них, которые наиболее интересны для них самих или произвели на них наибольшее впечатление. Какой смысл вкладывается в слова ’’разделение памяти компью¬ тера”? Это означает, что имеется система разбиения памяти ком¬ пьютера на функционально относительно независимые области. Эти области независимы в том смысле, что могут использоваться в разных программах без модификации с полной уверенностью, что ни одна из них не будет затерта при включении ее в другое окружение. В нашем определении сказано, что в выделенных областях разме¬ щаются не только данные, но и код выполняемых процедур. Это разделение существенно для защиты объектов. Если бы любой процедуре был разрешен доступ к памяти объекта, то с его данными могли бы происходить непредсказуемые события, что, очевидно, сказалось бы на выполнении им своих функций. По этой причине активные процессы объектно-ориентированной системы оформляются как локальные функции и процедуры. Они,и только они, имеют доступ к данным объекта. Таким образом объект защищает себя от разрушения данных в результате внешних
40 Что такое объектно-ориентированное программирование? событий. В результате, как только функциональный элемент про¬ граммы написан и полностью отлажен, он становится работоспо¬ собным независимо от последующих дополнений и модификаций в использующей его программе. Наконец, в определении сказано, что структура может использо¬ ваться в качестве образца для изготовления копий. Это означает, например, что как только определен объект ”окно”, так сразу можно создать столько таких окон, на сколько хватит памяти. При этом не понадобится создавать дополнительный код, позволяющий убедиться в отсутствии взаимодействия. Утверждая возможность копирования, я при этом имею в виду только способ поведения объектов. Внешне все выглядит так, как если бы существовало несколько полных копий. Пожалуйста, отметьте, что из сказанно¬ го не следует способ реализации. Я МЕТАФОРЫ ОБЪЕКТНО-ОРИЕНТИРОВАННОГО ПРОГРАММИРОВАНИЯ Каждая парадигма программирования имеет свои метафоры, по¬ могающие программисту думать о структуре программы. Инфор¬ матика полна метафор, постепенно переходящих в программистский жаргон. Двумя яркими примерами могут слу¬ жить термины ’’память” и **окно”. Если остановиться и подумать, то мы обнаружим, что новые значения этих слов очень далеки от первоначальных. Память и окна, о Kotopbix говорится в курсе информатики, слабо напоминают предметы реального мира, дав¬ шие им имена. Так или иначе, но термины — это просто ярлыки, которыми мы без труда пользуемся. К метафорам, объясняющим важные концепции программирова¬ ния, надо относиться особенно аккуратно. Чему можно уподобить ООП? Прежде чем ответить на этот вопрос, отметим, что такие основные концепции ООП, как ’’передача сообщений” и ’’наследо¬ вание”, имеют метафорический смысл. По крайней мере в случае этих двух метафор аналогия может быть успешно продолжена. Некоторые из метафор, используемых для объяснения объектно¬ ориентированных систем, менее прозрачны и, как следствие, ме¬ нее удобны. В своей книге ’’Объектно-ориентированное программирование” ее автор Брэд Кокс использует для объяснения основных принципов объектно-ориентированных систем метафо¬ ры программной интегральной схемы и фабрики. Как будет пока¬ зано далее, несмотря на определенное педагогическое значение, эти метафоры не могут быть развиты глубже для описания объек¬ тно-ориентированных систем. Эти системы содержат средства, делающие их технологически совершеннее, чем интегральные схемы и фабрики. Причина этого кроется в их предельной модуль¬ ности. Нельзя воспользоваться стандартной ИС для построения
Передача сообщений 41 новой интегральной схемы. Продукция фабрик обычно мало напо¬ минает фабрики, на которых она произведена, в то время как для объектно-ориентированного программирования характерно ис¬ пользование существующих объектов для изготовления более но¬ вых, наследующих все свойства своих предшественников. S АКТИВНЫЕДАННЫЕ Весьма удобно рассматривать объекты как попытку создания активных данных. Смысл, вкладываемый в слова ’’объект пред¬ ставляет собой активные данные”, основан на объектно-ориенти¬ рованной парадигме выполнения операций, состоящей в посылке сообщений. В посылаемых объекту сообщениях указывается, что мы хотим, чтобы он выполнил. Так, например, если мы хотим вывести на экране строку, то мы посылаем строке сообщение, чтобы она изобразила себя. В этом случае строка — это уже не пассивный кусок текста, это активная единица, знающая, как правильно производить над собой различные действия. По многим причинам имеет смысл объединять операции с объектами с самими объектами. Наиболее существенно различие, проводимое в программирова¬ нии, между данными и процедурами. Оно в определенной степени отражает различие между аппаратурой процессора и схемами памяти. • На такой архитектуре машины основана модель програм¬ мы, в которой данные запоминаются в памяти, а инструкции процессоров манипулируют этими данными. В этой модели данные выполняют пассивную роль, а оперирующие над ними инструкции являются активными элементами. ООП — это шаг на пути к другой модели программирования. Объект одновременно представ¬ ляет собой и данные и код. Это форма активных данных. ■ ПЕРЕДАЧА СООБЩЕНИЙ Один из основных способов взаимодействия объектов в ООГ1 — это передача сообщений. Связь между объектами осуществляется пу¬ тем передачи сообщений. Сообщения могут быть приняты или отвергнуты. Объект принимает те сообщения, которые опознает, а остальные игнорирует. В этом случае способы взаимодействия объектов полностью контролируются. Поскольку этому протоколу следуют все объекты, внешний по отношению к объекту код не может непредсказуемым или нежелательным способом влиять на функционирование этого объекта. Посылка сообщений требует, как минимум, указания адресата и имени посылаемого сообщения. Часто требуется также задать параметры. В качестве аргументов могут использоваться имена переменных, известных исключительно принимающему сообще¬ ние объекту, или имена глобальных переменных. Объекту можно
42 Что такое объектно-ориентированное программирование? посылать сообщение записать себя на диск, вывести на экран, уничтожить себя и т.д. Имеется ли глубинная связь между пере¬ дачей сообщений и активными данными? Это сложный вопрос. Для правильного ответа на него изложенных до сих пор сведений недостаточно. Пока можно сказать, что передача сообщений при¬ дает поведению объектов (с точки зрения внешнего наблюдателя) характер активных данных. Изнутри объекта по-прежнему суще¬ ствует четкое разделение пассивных данных и активных процедур. Какое влияние на систему оказывает использование передачи сообщений? Вновь с окончательным ответом придется повреме¬ нить. В общих чертах ответ состоит в том, что передача сообщений поддерживает продвинутую технику программирования, которая использует разделение труда между принимающими сообщения объектами. Хотя тот же результат можно было бы получить и без передачи сообщений, как правило, это гораздо труднее и требует больших знаний и мастерства от программиста. В целом, объекты в системах, основанных на обмене сообщениями, обладают уни¬ кальной степенью автономности. В определенном смысле каждый объект ведет себя как маленький специализированный компью¬ тер, взаимодействующий с другими специализированными компь¬ ютерами. Это, разумеется, тоже метафора. Как мы увидим далее в этой книге, модель передачи сообщений дает множество инте¬ ресных и мощных алгоритмов, которые, не будь этой модели, могли бы не прийти в голову программистам и разработчикам. ■ КЛАССЫ: ПОРОЖДЕНИЕ ЭКЗЕМПЛЯРОВ И НАСЛЕДОВАНИЕ Концепция класса является одной из базисных для ООП. В этом контексте класс понимается как образец для создания настоящих, работающих объектов определенного типа. Во многих объектно¬ ориентированных системах класс сам считается объектом, облада¬ ющим, правда, очень ограниченным набором возможностей. Говорится, что создается экземпляр класса, когда класс использу¬ ется в качестве ’’штампа” для изготовления одного или нескольких объектов его типа; созданный объект называется экземпляром класса. Практическая применимость структуры класса обусловле¬ на наличием средств наследования, т.е. способности создавать классы, автоматически моделирующие поведение других классов. Если класс В моделирует свойства класса А, то мы называем В подклассом А, а класс А соответственно суперклассом для В. В таком случае говорят, что В полностью наследует поведение класса А. Отдельно взятая возможность создания класса, наследующего поведение другого класса, лишена смысла. Подкласс создается для того, чтобы добавить новые элементы в унаследованное поведение. Обычно подклассы характеризуются более специализированным
Классы: порождение экземпляров и наследование 43 по сравнению с соответствующими им суперклассами поведением. Некоторые объектно-ориентированные системы содержат средства множественного наследования. Это означает, что у класса может быть более одного суперкласса. В таких системах новый класс иногда может быть создан только за счет наследования от двух или более суперклассов, в других случаях дополнительные аспек¬ ты поведения вносятся с помощью ручного кодирования. В любом случае полученный класс может быть использован для ’’штампов¬ ки” объектов, поведение которых будет отличаться от поведения суперкласса или суперклассов. В процессе определения подклассов в объектно-ориентированной системе появляется так называемая иерархия классов. Эта иерар¬ хия представляет собой дерево или сеть классов. Дерево (сеть) начинается с наиболее общих классов и спускается к наиболее специализированным листьям. Одной из основ, определяющих мощь объектно-ориентированного окружения, служит иерархия классов и ее возможности. Программисты расширяют объектно¬ ориентированный язык, наращивая иерархию классов и словарь сообщений, которыми обмениваются объекты. Активные элементы объектов называются методами, они напоми¬ нают функции, локализованные в данном объекте. Имена методов часто называют селекторами, поскольку они позволяют системе выбрать, какой следует выполнить код при вызове по имени. Стабильность протокола взаимодействия объектов при расшире¬ нии языка составляет одно из наиболее важных достоинств объек¬ тно-ориентированных систем, определяющее способность эффективно управлять процессом разработки больших систем, в котором заняты несколько программистов. Эффективность дости¬ гается за счет минимизации числа связей между программистами и объема программной документации. Для достижения этих целей используется перегрузка. Перегруз¬ ка — это соглашение, по которому методы разных классов, выпол¬ няющие одинаковые действия, носят одно и то же имя. В большинстве языков со строгой типизацией функции, выполняю¬ щие одни и те же действия, но над элементами разных типов, должны называться по-разному. Так, например, для деления целых и действительных чисел необходимо иметь две разные функции, скажем, divideInteger и divideReaL В системах с боль¬ шим количеством классов это может значительно осложнить про¬ граммирование, ибо программист должен будет непрерывно искать имена функций. Перегрузка дает возможность пользовать¬ ся одним именем, скажем divide, для функций нескольких клас¬ сов. При этом реализация метода может различаться от класса к классу. За счет этого пользователь получает единый протокол для широкого спектра операций классов, облегчающий использование
44 Что такое объектно-ориентированное программирование? системы. Программист освобождается от обязанности просматри¬ вать написанный другими программистами код для того, чтобы узнать, как к нему обратиться и как его использовать. ■ ТИПЫ ОБЪЕКТНО-ОРИЕНТИРОВАННЫХ СИСТЕМ Имеются несколько критериев классификации объектно-ориенти¬ рованных систем. Наиболее четко различаются чистообъектные и гибридные системы. В чистообъектной системе всё является объек¬ том. Примером такой системы служит Смолток. В этой системе даже классы являются объектами, экземплярами некоторого клас¬ са. На первый взгляд это кажется странным, но никакого пара¬ докса здесь ыет. Это прямой результат стремления получить полностью согласованную систему. Подобно тому как объекты создаются из классов, служащих для них моделью или образцом, так и сами классы представляют собой объекты, создаваемые в соответствии с шаблоном, заключенным в определенном классе. Этот класс обычно называют метаклассом. Въедливый читатель несомненно почувствовал знакомые очерта¬ ния замкнутого круга. В голову приходит мысль: ’’Если классы всегда являются экземплярами какого-то другого класса, то отку¬ да взялся первоначальный класс?” Ответ прост: в этом месте принцип нарушен. Ибо объектно-ориентированная система не логическая загадка, а практический инструмент. И то, что мета¬ класс, используемый в качестве образца, не является экземпляром класса, не недостаток системы. Она вполне работоспособна и берет все, что можно, от объектно-ориентированной парадигмы. В гибридных системах объекты сосуществуют с обычными элемен¬ тами языков программирования. В качестве примеров можно привести различные объектно-ориентированные расширения Си и Лиспа, некоторые детали которых будут рассмотрены в следующей главе. Поскольку объекты включены в существующий язык про¬ граммирования, не все элементы гибридных систем представляют собой объекты. Возможности инкапсуляции и защиты данных зависят от структуры и реализации базового языка. Обычно суще¬ ствуют средства обхода защиты. Другим критерием классификации объектно-ориентированных си¬ стем служит тип механизма наследования. Имеются два механиз¬ ма: одиночное и множественное наследование. Как уже говорилось, системы с множественным наследованием позволяют классу наследовать более чем от одного суперкласса. Множествен¬ ное наследование появилось сравнительно недавно, и многие реа¬ лизации этого средства не содержат. Множественное наследование не столь просто. Одна из причин этого состоит в потенциальных конфликтах имен переменных и методов в разных классах, из которых ведется наследование. И
Как работаютобъектно-ориентированные системы? 45 хотя эта ситуация сравнительно редка, все же необходим строгий механизм разрешения конфликтов. Пока не придуман общеприз¬ нанный метод обработки таких ситуаций. Последний критерий состоит в наличии или отсутствии параллель¬ ной обработки. Так, в параллельной системе обмен сообщениями может происходить одновременно, в то время как в последователь¬ ной системе объект ожидает возврата сообщения. В табл. 2.1 перечислены пять признаков, определяющих степень объектной ориентированности системы. Таблица 2.1. Степени объектной ориентированности систем Kpumepuu уровня 1. Классы и множество экземпляров 2. Инкапсуляция функций и данных 3. Связывание периода выполнения 4. Множественное наследование 5. Обмен сообщениями ■ КАК РАБОТАЮТ ОБЪЕКТНО-ОРИЕНТИРОВАННЫЕ СИСТЕМЫ? В некоторых приложениях программист может не учитывать прин¬ ципы ООП, однако для большинства приложений, связанных с искусственным интеллектом, это невозможно. Одна из целей объек- тно-ориентированных систем — позволить использовать код, не заставляя программиста вдаваться в детали его устройства. Многие современные программные проекты требуют до предела и даже сверх того использовать возможности системы программирования. Вот причины, по которым я хочу завершить эту главу общим рассказом о базисных механизмах, используемых в объектно-ориентированных системах для раскрытия присущих им возможностей. Что в действительности происходит, когда объекту передается сообщение? В большинстве объектно-ориентированных систем объекты состоят из видимой и приватных частей. Нет необходи¬ мости создавать копии неизменяющихся частей объектов, это приведет к разбазариванию ресурсов памяти. Как правило, общие для всех объектов одного типа части запоминаются на уровне класса. В эту часть чаще всего попадают код методов и переменные класса, которые составляют разделяемую часть объектов. Когда послано сообщение оно обрабатывается подпрограммой, которая просматривает область памяти класса. Имена и значения перемен¬ ных экземпляров приватны. Каждый объект (экземпляр) содер¬ жит свои собственные переменные. Класс содержит информацию
46 Что такое объектно-ориентированное программирование? о том, какие переменные создавать, но запоминаются они самим экземпляром. Если разыскиваемые данные или код унаследованы у другого класса, они запоминаются единожды на более высоком уровне. Использующие их подпрограммы переадресуются по иерархии до тех пор, пока не достигнут класса, у которого эти данные или код наследуют остальные. ■ ИСПОЛЬЗОВАНИЕ ОБЪЕКТНО-ОРИЕНТИРОВАННЫХ СИСТЕМ В основном благодаря Смолтоку, первому подлинно объектно-ори¬ ентированному языку, системы этого типа славятся специальными средствами пользовательского интерфейса, облегчающими про¬ граммисту жизнь. Многооконная, ориентированная на мышь сре¬ да, ставшая популярной для всех типов прикладных программ, первоначально прочно ассоциировалась с объектно-ориентирован¬ ными системами. Некоторые уникальные особенности и требова¬ ния объектно-ориентированного программирования способствовали популярности визуализирующих программу инст¬ рументов. Так, например, с объектно-ориентированными система¬ ми ассоциируется программа броузер (Browser). Как и следует из названия, броузер — это средство, позволяющее программисту по желанию просматривать некоторые части программного окруже¬ ния. В частности, в Смолтоке имеется System Browser, позволяю¬ щее рассматривать иерархию классов системы, а затем перейти к одному из ее элементов для редактирования. Объектно-ориентированные системы содержат множество возмож¬ ностей, но они доступны только тем, кто знает, как ими пользо¬ ваться. В таких системах существует уникальное число соглашений, ответственность за соблюдение которых ложится на программиста. Начнем с того, что программист должен знать базисные действия, такие, как определение класса, создание объ¬ екта, выполнение операций. Кроме этого он обязан знать, какие методы доступны объектам какого класса. Зачастую в объектно¬ ориентированной системе имеется множество полезных классов и методов, однако программист не получит от них никакой пользы, если не будет знать, что они делают и как их использовать. ■ ПОЧЕМУ ПРИМЕНЯЮТ ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ? Существуют разные причины, по которым программисты прихо¬ дят к выводу о важности и даже незаменимости ООП. Одного наиболее привлекает исключение избыточного кода, другого — возможность защиты объектов от кода других частей программы.
Некоторые выводы 47 Для третьего важна экономия времени за счет построения програм¬ мы из готовых, отлаженных частей, вместо того чтобы начать кодирование с пустого места. Наконец, существуют программи¬ сты, которым нравится возможность создавать произвольное ко¬ личество независимых друг от друга объектов. Большинство объектно-ориентированных систем предоставляют программам все эти возможности. Какие из них окажутся наиболее важными,будет зависеть от требований проекта и предпочтений реализатора. По мере роста знаний и опыта в области объектно-ориентированных систем ваши предпочтения могут изменяться. Но это не столь важно, поскольку теоретически программы могут пользоваться всеми этими возможностями одновременно. Когда программисты прекратили использовать двоичный код и перешли к ассемблерам, понимающим заменяющие двоичные команды мнемонические имена, шансы на выживание старого метода сильно упали. С тех пор прогресс в технологии программи¬ рования стал более плавным, и существенные изменения не всеми воспринимаются одновременно. Разработка компилируемых язы¬ ков высокого уровня привела к широкому их использованию. Но компиляторы не вытеснили ассемблеры. Правильнее считать их следующим слоем программного обеспечения, вставшим между машиной и программистом, чтобы дополнительно облегчить про¬ цесс написания программ. ■ НЕКОТОРЫЕ ВЫВОДЫ ООП нельзя рассматривать как абсолютно новое, не имеющее прецедентов направление в информатике. Оно просто аккумули¬ рует последние достижения в области языков программирования, делая новый шаг в сторону ясности, модульности и эффективно¬ сти. С некоторой точки зрения, ООП можно рассматривать как попытку довести идеи структурного программирования до логиче¬ ского завершения. В структурном программировании переменные могут быть локализованы в процедурах, передающих друг другу в качестве аргументов строки и числа. Объектно-ориентированное программирование делает здесь небольшой шаг вперед. Перемен¬ ные локализуются теперь не только в процедурах. Основным строительным блоком становятся объекты, т.е. защищенные обла¬ сти памяти, которые могут содержать как локальные данные, так и локальные процедуры. Более того, строительные блоки не взаи¬ модействуют друг с другом посредством передачи параметров. Локализованные в объектах процедуры, обычно называемые ме¬ тодами, служат сообщениями, которые посылают и принимают объекты. В этом отношении объекты напоминают маленькие ком¬ пьютеры, находящиеся внутри основного компьютера, каждый из которых обладает своими областями данных и кода.
48 Что такое объектно-ориентированное программирование? В большинстве объектно-ориентированных систем имеются два раз¬ ных типа объектов: классы и экземпляры. Классы могут быть логически связаны друг с другом. При этом один из них называется подклассом, а другой — суперклассом. В целом, суперкласс — это более абстрактный класс, а подкласс — более конкретный. Так, например, мы можем создать класс Мебель, а затем создать класс Шкаф как подкласс класса Мебель и КнижныйШкаф как подкласс класса Шкаф. В этом примере класс Мебель будет суперклассом для класса Шкаф, а Шкаф — суперклассом для класса КнижныйШкаф. У объектно-ориентированных систем имеются как минимум три непосредственных преимущества: одно состоит в том, что если вы написали код для класса, то вы можете иметь столько экземпляров этого класса, сколько позволяет память. Класс — это просто образец, по которому строится каждый экземпляр, который в свою очередь снабжается собственной областью памяти, недоступной для других объектов иначе, как посредством обращения к локаль¬ ным методам этого объекта. Это, в частности, означает, что в объектно-ориентированной системе могут совершенно свободно сосуществовать, не влияя друг на друга, произвольное количество таких объектов, как графические окна, редакторы, интерпретато¬ ры и т. п. Другое преимущество предоставляется механизмом наследования. Подклассы автоматически получают все перемен¬ ные и методы своих суперклассов. То есть более специализирован¬ ные функции могут быть написаны за счет добавления частей, делающих их уникальными. При эгом все остальные свойства будут унаследованы автоматически. Возможность иметь один и тот же интерфейс с широким спектром типов объектов составляет третье преимущество. Это достигается за счет того, что для методов разных объектов, имеющих разную реализацию, может использо¬ ваться одно и то же имя. При этом различие в реализации остается невидимым пользователю. Например, мы можем создать несколь¬ ко различных классов, соответствующих разным многогранникам. Затем в каждом из этих классов мы зададим методы, вычисляю¬ щие объем и площадь поверхности тела. Формулы и их програм¬ мная реализация могут различаться, но имена, с помощью которых вызываются эти методы, будут одинаковы. К этим мето¬ дам можно обратиться, написав Тетраэдр.Объем или Куб.Объем. В каждом случае будет вызван требуемый метод, который вернет искомый объем. Некоторые считают основной особенностью ООП возможность ис¬ пользовать один и тот же код в разных программах. Однако подобную возможность дают и библиотечные функции. Ключевые преимущества ООП становятся очевидными не сразу, но опытные программисты, которые поработали с такими системами, подтвер¬ дят, что объектно-ориентированные языки значительно облегчают работу над большими программами. Это не приходит, однако, само
Дополнительные заботы программиста 49 собой. Ключ к успеху кроется в правильном разбиении программы на части. Одновременно необходимо изучить правильную технику кодирования и приемы, упрощающие работу членам команды программистов. Границы объектно-ориентированных систем обычно несколько размыты. Части приложений разбросаны по большому количеству классов и подклассов. Для эффективного программирования в таких системах важно иметь адекватные инструменты и эффективные методы поддержания целостности и правильной организации приложения. Очень важно отметить, что ООП не следует рассматривать как нечто, что можно освоить в один момент. Дело в том, что парадиг¬ ма ООП существенно отличается от всего того, к чему привыкли программисты. Подготовка требует интенсивного пополнения зна¬ ний. Другими словами, легкодоступный модульный код полезен только тем программистам, которые владеют имеющимся у них инструментом и техникой его использования. Многие программи¬ сты сопротивляются изучению новых языков, не говоря уже о новых программных парадигмах, поэтому имеет смысл вновь перечислить преимущества ООП для практического программиро¬ вания. Как вы видите, их четыре: 1. Стандартные соглашения вызова для широкого диапазона операций, реализующих различные модификации одной темы. 2. Возможность управлять очень большими программными проектами, разбивая большие задачи на небольшие, неза¬ висимые и легко обозримые части. 3. По-настоящему модульное программное окружение, сводя¬ щее повторы при кодировании к минимуму. 4. Возможность порождать множество экземпляров функции или объекта, используя один и тот же код. Разные экземп¬ ляры не оказывают влияния друг на друга. ■ ДОПОЛНИТЕЛЬНЫЕ ЗАБОТЫ ПРОГРАММИСТА До сих пор при изложении материала подразумевалось, что ООП предъявляет дополнительные по сравнению с обычным подходом требования к программисту. В чем они состоят? Во-первых, это задача модульного проектирования. Определенная степень мо¬ дульности возможна и при разработке программ, использующих и обычную процедурную парадигму. Объектно-ориентированные системы допускают большую степень модульности. Разработка систем со столь высокой степенью модульности — дело довольно сложное. В ООП требуется как можно лучше предвидеть потреб¬ ности системы. В идеальном случае программист, разрабатываю¬ щий поведение объекта, кодирует его в наиболее абстрактном виде.
50 Что таков объвкт.чо-орнвнтированнов программирование? Код должен быть написан так, чтобы быть применимым в макси¬ мально возможном количестве случаев. Последнее правило очень важно. Я сформулирую некоторые след¬ ствия из него. Если вы иезнаксмы с ООП, вам кое-что будет не вполне понятно, и я дам некоторые вводные объяснения. Если вы не поймете их с первого раза, то у вас будут еще возможности. Как я заметил ранее, с использованием механизма наследования мож¬ но строить специализированные версии существующих классов, наследующих поведение родовых. В этом состоит один из ключей к модульности в ООП, Если при проектировании программы вы задумаетесь чуть-чуть о всех подобных программах, то сможете написать более общий код. Рассмотрим пример, из которого будет ясно, что я имею в виду. Представим, что мы пишем программу, которая будет использова¬ на неопытными пользователями. Вначале мы напишем упрощен¬ ную версию программы, а затем поэтапно добавим более сложные возможности. Рассмотрим небольшую часть программы, отвечаю¬ щую за ввод-вывод файлов. Для начала мы разработаем класс FILE с методами readFile и writeFile для чтения и записи файлов. Они неплохо работают, но не предусматривают средств защиты для неопытных пользователей. Далее, когда мы будем писать оконча¬ тельную версию системы, содержащую дополнительные средства защиты, нам не понадобится переписывать методы readFile и writeFile. Мы создадим подкласс SAFEFILE класса FILE с собст¬ венными версиями этих двух методов. Начнем с того, что будем контролировать наличие достаточного для размещения буферов объема оперативной памяти и места на диске. Затем напишем новую версию метода writeFile, которая вначале вызывает этот проверочный код, а затем обращается к родительской версии метода. Если первоначальная версия writeFile была корректной, созданный метод будет применим в любой программе, которая что-либо записывает на диск. Нам не потребуется более переписы¬ вать этот метод, разве что мы захотим увеличить его производи¬ тельность. Недостающие средства могут быть добавлены описанным модульным способом. Из сказанного становится понятна важность родового кода. Опи¬ санный класс FILE не был создан специально для разрабатываемой программы. В идеале такие классы пишутся единожды, в расчете на все будущие программы. Подкласс SAFEFILE был написан в расчете на нашу программу, но нет причин, по которым он не мог бы быть вставлен в другой код с другими данными. Это функцио¬ нально завершенная программная единица, которую включить в другую программу столь же просто, как сменить лампочку. Лам¬ почка ничего не знает ни о форме абажура, ни о материалах, из которых он сделан. Ее задача светить, и благодаря модульному проектированию она займет точно то же место, что и старая. В
Дополнительные заботы программиста 51 случае объектно-ориентированного программного обеспечения этот принцип доведен до совершенства. Мы изначально знаем, что новые части без всяких проблем заменят старые, поскольку они являются их наследниками. Они наследуют все, что мы пожелаем, от родителей и предоставляют новые услуги. Как вы увидели из этого примера, я не зря говорил о том, что программист, использующий ООП, должен думать о системе в целом. Прежде чем начать работу над программой, он должен тщательно изучить систему. Давайте уточним это. Совокупность элементов, из которых строятся объектно-ориентированные про¬ граммы, называется библиотекой классов. С ней-то и должен быть хорошо знаком программист. Это знакомство позволит ему не изобретать велосипед и не создавать массу кода, дублирующего существующие в библиотеке классов средства. Однако для этого необходимо знать, какими возможностями обладает библиотека классов и как это Может быть использовано. На практике фотографическая память от программистов, исполь¬ зующих ООП, не требуется. Они подобно остальным полагаются на справочные материалы. Был разработан удивительный инстру¬ мент, в котором соединены система экранных подсказок и окру¬ жение для разработки и редактирования программ. Этот инструмент называется броузер (Browser). Популярность ему при¬ несла система программирования на языке Смолток. Благодаря броузеру, программист получает отличный обзор биб¬ лиотеки классов, имея инструмент для более детального изучения отдельных классов и их поведения. Не покидая этого средства, он может проектировать и кодировать собственную программу. Это основной, самый важный инструмент для объектно-ориентирован¬ ного программирования. Одну из ключевых возможностей про¬ граммы Browser предоставляет окно, в котором находится список всех классов системы. При выборе одного из классов в специаль- ных окнах отображаются его локальные функции и переменные. Затем при выборе одного из методов на отдельной панели высве¬ чивается его код. Обычно в системе присутствуют средства для добавления и удаления классов из библиотеки. В результате появляется гибкий инструмент, сочетающий возможность про¬ смотра ресурсов системы с разработкой и кодированием новых программ. Программа Browser — это не просто визуализатор, Это основной, интегрирующий инструмент, который помогает одно¬ временно рассматривать существующую систему и разрабатывать программный проект.
Гпава 3 Я ОБЪЕКТНО-ОРИЕНТИРОВАННЫЕ РАСШИРЕНИЯ СИ: ЯЗЫКИ И ИНСТРУМЕНТАЛЬНЫЕ СРЕДСТВА "Си++ — молодой язык, e нем имеется множество темных закоулков, из которых не видно хороших выходов” Справочник по компилятору Zortech С++ Эта гдава будет полезна читателю, программирующему на Си и интересующемуся объектно-ориентированными инструментами, основанными на Си, и расширениями самого языка. В настоящее время растет число инструментов, позволяющих программировать на объектно-ориентированных диалектах Си для окружения Windows. Те, кто решился использовать один из них, найдут здесь кое-какую информацию, касающуюся этих становящихся все бо¬ лее популярными языков и инструментов. ■ ОБЗОР СРЕДСТВ СИ++ 2.0 Язык Си++ представляет собой объектно-ориентированное расши¬ рение языка Си, разработанного Бьерном Строуструпом для ком¬ пании АТ&Т. Архитектура Си++ основана на классах, понимаемых как определяемые пользователем типы. Как вы по¬ мните, классами в объектно-ориентированных языках называют шаблоны, по которым может быть изготовлено неограниченное число экземпляров, и родовой код, который может быть исполь¬ зован в более специализированных классах. Из этого не видно никакой непосредственной связи с типами данных. Реализация классов в Си++ как способа определения пользовательских типов представляет собой специфичный для Си подход к объектно-ори¬
Структуры и классы 53 ентированному программированию, призванный обеспечить есте¬ ственный способ расширения языка. Си++ использует четыре спецификатора класса памяти: auto, extern, register и static. Я специально буду называть их классами памяти, чтобы не путать их с классами в смысле ООП. В табл. 3.1 перечислены зарезервированные слова языка Си++. Таблица 3.1. Зарезервированные слова Си++ asm auto class const do double float for inline int overload private return short struct switch unsigned virtual break case continue default eke enum friend goto long new protected public signed sizeof this template void volatile catch char delete extern if operator register static typedef union while ■ СТРУКТУРЫ И КЛАССЫ Каждая структура или класс Си++ содержит набор данных, кото¬ рые могут принадлежать как базисным типам, так и объединениям или другим структурам или классам. Как структуры, так и классы могут содержать функции-члены. Как это проявляется при коди¬ ровании? Си++ просто использует ключевое слово class для зада¬ ния новых классов. Приведем пример определения класса set (множество): class set { struct set_member{ int member; set_member* next; set_member(int m,set_member*n); }; }; Гораздо чаще класс определяется как подкласс какого-либо из существующих классов. Например, мы можем написать такое определение класса competitor (конкурент): class competitor: public business{ //.. . } /‘ Это означает, что класс competitor является подклассом класса business.
54 Объектно-ориентированные расширения Си: языки и инструментальные средства ■ МНОЖЕСТВЕННОЕНАСЛЕДОВАНИЕ Возможность производить подкласс одновременно более чем от одного класса — очень существенное свойство языка Си++. При определении класса может быть указан список, перечисляющий все родительские классы, у которых данный класс наследует содержащиеся в них данные и функции-члены. Для определения класса, наследующего более чем от одного родительского класса, используется расширенная нотация. Приведем пример: class competitor: public business,public adversary{ //.. . } ; Класс competitor (конкурент) определен как производный одновре¬ менно двух классов: business и adversary (противник). ■ ФУНКЦИИ-ЧЛЕНЫ Методы, которые в Си++ называют функциями-членами, задаются посредством двойного двоеточия (::). По синтаксису вначале ста¬ вится имя класса, затем двойное двоеточие, а после него имя функции. Например set::setsize определяет setsize как функцию-член класса set. Допускается создание объектов типа void*, представляющих собой объекты неизвестного типа. ■ РЕГУЛИРУЕМАЯ СТЕПЕНЬ ИНКАПСУЛЯЦИИ Си++ обладает уникальным количеством опций, регулирующих степень инкапсуляции данных объекта. Элемент может быть оп¬ ределен как friend (друг), private (приватный), protected (защи¬ щенный) или public (общедоступный). По умолчанию элементы класса считаются приватными. Это означает, что доступ к ним ограничен функциями-членами данного класса. Если элемент оп* ределен как protected, он будет во всем подобен приватным, за исключением того, что будет разрешен доступ к нему функциям — членам классов наследников. ■ ФУНКЦИИ-ДРУЗЬЯ В Си++ могут быть определены функции-друзья, которые, не будучи функциями-членами класса, имеют доступ к защищенным переменным класса. Зачем это нужно? Во многих случаях функ¬ ция производит операции над объектами разных классов, не связанных отношениями наследования. Рассмотрим, например, матрицу и вектор. Если операция одновременно выполняется и
Перегрузка операторов и функций 55 над матрицей, и над вектором, то ее следует определить как функцию-член одного из классов, скажем класса ’’матрица”, и указать в качестве функции-друга в другом классе. При этом она получит доступ ко всем необходимым для выполнения операции элементам. Создание экземпляра класса сводится к объявлению элемента, так же как это происходит и для других типов данных. Ключевое слово volatile при декларации объекта сообщает компилятору, что объ¬ ект может изменять значение в результате неявных действий. Возможность декларировать переменную в любом месте блока кода, а не только в его начале, отличает Си++ от Си. Это удобно, поскольку вы можете располагать объявления переменных рядом с использующими их операторами. Другое существенное различие между Си++ и ANSI Си связано с семантикой декларации void fun () ; которая в Си++ означает, что функция не имеет ни одного пара¬ метра, а в ANSI Си — что у функции имеется неизвестное число аргументов неизвестных типов. Определять функцию без аргумен¬ тов лучше всего следующим образом: void fun(void); В этом случае места разночтениям нет. Эта декларация имеет один и тот же смысл в ANSI Си и в Си++. Есть и другие правила, которым необходимо следовать во избежа¬ ние коллизии имен с названиями внутренних объектов компиля¬ тора. Главным является запрет на использование символа подчеркивания в качестве первого или последнего знака иденти¬ фикатора. Таким образом, на идентификаторы _wrong и wrong_ наложено табу. ■ ПЕРЕГРУЗКА ОПЕРАТОРОВ И ФУНКЦИЙ Перегрузка позволяет операторам и функциям, работающим с различными типами данных и классами, использовать одно и то же имя даже в тех случаях, когда новые операторы и функции кодируются совершенно иным способом. Подобные вещи выглядят неуклюже в языках со статическим контролем типов и разрешением всех связанных с типизацией проблем во время компиляции. Языкам обработки списков и интерпретируемым языкам, таким, как Лисп, Пролог и Смолток, вполне свойственны функции, способные оперировать данными многих типов, не требуя при этом различного кода для разных типов операндов. Именно это вносит определенное изящество в программы, поскольку всегда удается делать вызовы функций логичными, уместными и легко запоминаемыми.
56 Объектно-ориентированные расширения Си: языки и инструментальные средства Перегрузка операторов — это изначально присутствующая в Си возможность, позволяющая операторам, к примеру +, работать с целыми числами, числами с плавающей точкой и указателями. Для определения новых функций, которые перегружают имеющи¬ еся, используется ключевое слово overload. Так, например, если мы хотим определить универсальную функцию печати, следует записать: overload print (int) ,print (double) ,print (long) ,print (char*) ,k В Си++ имеется полезная возможность определять функции, при¬ нимающие неопределенное число параметров. Такие средства обычно ассоциируются с языком Лисп и подобными ему. Для задания неопределенного списка аргументов служит многоточие. Например, декларация может иметь вид: int read( char*...); Ее можно использовать вместе с произвольным числом идущих подряд аргументов. ■ ПРОТОТИПЫ При задании прототипа новой функции обычно указываются че¬ тыре ее характеристики: 1) имя, 2) тип возвращаемого результата, 3) *сласс памяти и 4) число и тип передаваемых ей параметров. Прототип или декларация функции имеет вид: класс-памяти тип имя-функции (список-параметров); Пример прототипа функции для Си++ имеет вид extern int multiply (int x,int у, ...); В этой декларации многоточие означает, что функция может принимать более двух аргументов. В прототипе функции разреша¬ ется определять значение по умолчанию для ее параметров. Для этого мы можем написать: extern int multiply (int x=2,int y=3, ...); В таком случае вызов функции может вовсе не содержать парамет¬ ров multiply () ; Будет возвращено произведение выбираемых по умолчанию зна¬ чений, т.е число 6. В Си++ статические функции-члены играют ту же роль, что й методы классов в других объектно-ориентирован¬ ных языках. Эти функции могут быть вызваны без ссылки на какой-либо конкретный экземпляр класса. То же самое относится и к статическим данным класса, играющим роль переменных класса в других объектно-ориентированных языках.
Виртуальные функции и классы 57 ■ ВИРТУАЛЬНЫЕ ФУНКЦИИ И КЛАССЫ Концепция виртуальных функций — одна из наиболее сложных и трудных для понимания в Си++. Сами по себе виртуальные функции дают ограниченный вариант позднего связывания. Когда функция-член объявляется виртуальной, она может быть переоп¬ ределена в любом из производных классов. На первый взгляд это не отличается от перегрузки функций, также позволяющей пере¬ определять функции в других классах. Однако имеется существен¬ ное различие. На деле перегрузка функций означает лишь перегрузку имен, которые компилятор "расщепляет” на несколько разных внутренних имен. Перегруженные функции, как правило, никак не связаны друг с другом. Они просто изображаются с помощью одного и того же имени. Виртуальные функции, напро¬ тив, полагаются на дополнительные структуры данных, поддер¬ живающие связь между разными версиями функции. Они используют механизм косвенных указателей на функцию. В этом месте во время выполнения допускается модификация. Хотя поч¬ ти все в коде скомпилировано и жестко связано, благодаря при¬ менению указателей на указатели модификация данных может изменить вызываемую функцию. Мы имеем имя, указывающее на другой указатель, который, в свою очередь, указывает на функ¬ цию. Если адрес этого второго указателя заменить на адрес указа¬ теля другой функции, то во время выполнения будет вызвана она. Для формализации этого механизма служат виртуальные \ функ¬ ции. Другое важное различие между перегруженными и виртуаль¬ ными функциями состоит в том, что при переопределении виртуальной функции она остается верной форме исходной функ¬ ции. Она должна иметь то же число параметров тех же типов. Это происходит из-за того, что связь между ними не ограничивается использованием одного имени. В исходной версии Си++ Строуст- руп предполагал использовать виртуальные функции для написа¬ ния интерактивных программ, в которых пользователю нет необходимости точно знать способ достижения результата. Он определил это так: ”Этот стиль особенно удобен в интерактивных программах, когда базовое программное обеспечение не делает различия между объектами разных типов. Пользователь в типич¬ ном случае указывает на объект и спрашивает: ”Кто ты?” или ”Вьшолни свое делоГ’ При этом не сообщается ничего о типе объекта. Программа может и должна понимать это самостоятель¬ но.” (Строуструп Б. Язык программирования Си++. — М.: Радио и связь. — 1991. — С. 37). Виртуальные классы не имеют ничего общего с виртуальными функциями. Они возникли из-за потребности управлять механиз¬ мом множественного наследования. Обычно, если класс наследует
58 Объектно-ориентированные расширения Си: языки и инструментальные средства у двух классов, имеющих общего предка, он ведет себя так, как если бы содержал несколько копий данных и функций этого общего предка. Если предок определен как виртуальный, то во всех его наследниках используется единственный экземпляр дан¬ ных и функций независимо от того, сколькими путями он косвенно унаследован у предка. ■ КОНСТРУКТОРЫ И ДЕСТРУКТОРЫ Экземпляр данных декларируется подобно любой другой структу¬ ре данных, однако все экземпляры должны быть инициализиро¬ ваны. Это можно сделать, вызвав функцию-член, называемую конструктором. Конструкторы должны кодироваться для каждого класса объектов отдельно, поскольку они представляют собой то, что в действительности создает объект. Кроме конструкторов существуют деструкторы, которые уничтожают объекты, освобож¬ дая занятую ими память и делая ее значение неопределенным. Си++ не содержит множества встроенных средств для обеспечения ввода-вывода. Они могут быть добавлены разработчиками. Однако стандартизация в этой области имеет критическое значение. В Си++ существуют три вида областей видимости: локальная, файл и класс. Локальная область видимости предполагает задание имен в блоке кода и их локализацию внутри него. Область видимости класса соответствует именам, определенным внутри класса и не¬ видимым вне операций, принадлежащих этому классу. Область видимости файла содержит имена, объявленные вне блоков и классов. Для получения готовой программы на Си++ обычно требуется выполнение четырех шагов: 1) предварительная обработка, 2) трансляция, 3) компиляция и 4)связывание. После всего ска¬ занного многим захочется попробовать написать что-нибудь на этом языке. Что представляет собой обычная программа на Си++? Сколь легко ее читать? Чтобы помочь ответить на этот вопрос, я включил пример программы, реализующей хорошо известный алгоритм решета Эратосфена. Вы можете оценить Си++ по листин¬ гу 1. Обратите внимание на то, что для выделения комментариев вместо обычной для Си комбинации /* используется пара //. ЛИСТИНГ 1 #include am.h class prime { int n; //номер простого числа int p; //само простое число public: // Конструктор prime( int nn=l,int pp=2){ n=nn;p=pp; } / / Переход к следующему простому числу
Конструкторы и деструкторы 59 prime& operator++(); // Определяет номер простого числа int n_th() {return n} / / Определяет значение простого числа int prm() {return p} i, // Вывод простого числа ostream& operator<(ostream& s,prime &p) { return s<p.prm(); //Переход к следующему простому числу prime& prime::operator++() { ++n; // Увеличим порядковый номер на 1 // Специальный случай для 2 if(p==2) //Следующее простое равно 3 ++p; return (*this); 1/ // Ищем следующее нечетное простое число for (;;) { p =+ 2; int is_prime = 1; int loop_limit = (p/2); for (int a=3; а; ++а) //Число p простое? if ( ((p/а)*a)==p) { is_prime = 0; break; } 1/ // Мы нашли простое? if(is_prime) break; // Не простое, смотрим дальше } return(*this); 1/ // Простейшая головная программа, использующая класс // prime. main() { const int n_primes = 100; //Напечатать 100 простых чисел prime number; while (number.n_th()) { // // Печатаем номер простого числа и само число cout<number.n_th()<"\t"<number<"\n"; // Переходим к следующему простому ++number; }
60 Объектно-ориентированные расширения Си: языки и инструментальные средства ■ КОМПИЛЯТОР ФИРМЫ ZORTECH К числу достоинств этого быстрого и гибкого компилятора с языка Си++ относится поддержка разработки программ для MS Windows. Этот Си++ в отличие от многих систем представляет собой не препроцессор, а полноценный компилятор. Интегриро¬ ванная среда дает возможность компилировать программы, не выходя из редактора. Компилятор Zortech позволяет отключить пропуск неявных определений функций. По умолчанию использу¬ ется режим автопрототипирования. Он состоит в том, что при использовании функции, у которой отсутствует прототип, компи¬ лятор изготовит его в соответствии со способом вызова функции. Zortech С++ стремится выйти за рамки языка программирования и стать средой программирования. Полная система включает редактор, отладчик и библиотеку инструментальных классов. ■ МОДЕЛИ ПАМЯТИ Компилятор Zortech поддерживает 5 моделей памяти: ТДМ,С и L. Их спецификации приведены в табл. 3.2. По умолчанию исполь¬ зуется малая (small) модель памяти. Архитектура определяется выбором длинной или короткой схемы адресации. Ближние (near) адреса позволяют ссылаться только в пределах текущего сегмента, дальние (far) адреса дают возможность сослаться на любое место памяти^ Указатели на функции и данные в Zortech С++ могут быть как ближними, так и дальними. Различные комбинации приводят к перечисленным в таблице пяти моделям. С помощью ключевых слов near и far можно строить программы, использующие смешанные модели памяти. С наличием моделей памяти связана проблема, состоящая в труд¬ ности изменения всевозможных адресов при смене модели памяти, Таблица 3.2. Модели памяти Название Объем Объем Дополнительные модели кода данных условия T (Tiny) 64 К 64 К Код+Статические+ Крошечная Глобальные < 64K S (Small) 64 К 64 К Малая M (Medium) 1 Мб 64 К Средняя С (Compact) 64 К 1 Мб Компактная L (Large) 1 Мб 1 Мб Большая
Отладчик для Си++ 61 которая может понадобиться из-за увеличения программы. Дру¬ гим недостатком будет специфичность понятий near и far для архитектуры i80x86 и связанное с этим отсутствие переносимости программ. ■ КАРТА ПАМЯТИ ПРОГРАММ Карта памяти программ Zortech С++ содержит следующие основ¬ ные сегменты: Code Код FarData Дальние данные NearData Ближние данные Stack Стек NearHeap Ближняя куча FarHeap Дальняя куча ■ ОТЛАДЧИК ДЛЯ СИ++ Один из немногих отладчиков для Си++, продукт фирмы Zortech, представляет собой программу, имеющую самостоятельное значе¬ ние с посвященным ей отдельным руководством. Подобно боль¬ шинству современных отладчиков он требует включения в код при компиляции специальной информации. Отладчик совместим с отладчиком CodeView фирмы Microsoft, но требует, чтобы вклю¬ ченная в код отладочная информация была преобразована к его формату. При компиляции для включения отладочной информа¬ ции необходимо указать опцию -g. Поскольку подготовка програм¬ мы, состоящей из нескольких модулей, значительно сложнее, рекомендуется использовать для такой компиляции программу Make. В отличие от ориентированного на работу в терминах строк CodeView, отладчик для Zortech С++ ориентирован на операторы, т.е. наименьшей обрабатываемой единицей служит не строка кода, а оператор. Основной экран отладчика разделен на три части. Первая — это строка меню, вторая, занимающая большую часть экрана, содер¬ жит 3 окна. В нижней строке отображаются команды, доступные при помощи функциональных клавиш. Отладчик работает в двух основных режимах: в режиме оконных команд (Window Control Mode) и в режиме меню (Menu Control Mode). Первый используется по умолчанию. Основное различие между этими двумя режимами состоит в обработке нажатий клавиш. Программа поддерживает использование мыши, при работе с ней различие между этими двумя режимами менее заметно. Несмотря на то что отладчик начинает работу с тремя окнами, пользователю доступны 15 окон плюс окно подсказки. Говоря о первых трех окнах, заметим, что в первом и самом большом из них отображается текст программы или машинные коды, в одном
62 Объектно-ориентированные расширения Си: языки и инструментальные средства Таблица 3.3. Окна отладчика Zortech С++ Клавиша Окно Подсказки Автоматических переменных Файлов Функций Данных Буфера трассировки Условных точек остановки Выражений Просмотра памяти Исходного текста Регистров Директория Символов Сопроцессора i8087 Классов Буферов F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 Alt-Fl Alt-F2 Alt-F3 Alt-F4 Alt-F5 Alt-F6 из меньших — данные, а в другом — имена автоматических переменных. В каждый момент времени пользовательский ввод ’’фокусируется” только в одном окне отладчика. В табл. 3.3 приведены список окон отладчика и функциональные клавиши, с помощью которых они появляются на экране. Кроме упомянутых окон в отладчике имеется экран вывода программы, отображаю¬ щий результаты вывода из программы на экран. Обычный экран отладчика возникает только при остановке отлаживаемой про¬ граммы. Отладчик может работать в одном из двух режимов: с использованием исходного текста или отображая ассемблерные команды. Для отладки программ на Си++ в отладчик добавлены окно Class и средство для ’’расщепления” имен перегруженных функций. В окне Class в алфавитном порядке перечислены все классы, исполь¬ зуемые в программе. Изначально видны только имена классов. Каждое из имен можно раскрывать, получая список базовых для данного классов, список прототипов функций, список функций- членов, расположенные лесенкой. Если поместить курсор на имя функции-члена и нажать ENTER, то в окне Source появится исходный текст этой функции. Нажатие Alt-E переводит в режим редактирования этого текста. Если класс наследует от одного или нескольких базовых классов, то ситуация несколько сложнее. Переменные, унаследованные от этих классов, могут находиться в окнах автоматических перемен¬ ных, данных и выражений, скрытые внутри объектов базового
Заключительные замечания о Си++ 63 класса. Для того чтобы увидеть его содержимое, может понадо¬ биться раскрыть его. В отладчике предусмотрена зашита памяти по записи. Это не означает, что отладчик не позволяет записывать в определенные места памяти. Он только отслеживает такие операции. При первой записи в защищенную область памяти на экране появляется сообщение, а все дальнейшие обращения протоколируются для последующего анализа. Это средство используется в сочетании с окном просмотра памяти. ■ КЛАССЫ БИБЛИОТЕКИ ZORTECH TOOLS Bitvec Slist Gslist Gqueue Gstack Iqueue fetack Dnode Dlist Gdlist Dynarr GDynArr Node Bintree Hash Ghsearch Bcd Gvma Gvms Rt_clock Directory Filename Lntvector Ce_handier 3tring_editor Date_editor Window_set Window Money Event Event_queue Самыми интересными из перечисленных классов .* считаю Dlist, Dnode, Dynarr, Rt_clock, Event и Event_queue. Любопытны также и классы Node и Bintree. Наиболее противоречивые чувства вызывают классы, р^ализу^- щие виртуальную память. Очевидно, не стоит судить о них, кг увидев, как они работают. Несмотря на то что дин^мычеокис массивы не столь привлекательны по сравнению с массивами> способными к росту во время выполнения, их все-таки полезно иметь. ■ ЗАКЛЮЧИТЕЛЬНЫЕ ЗАМЕЧАНИЯ 0 СИ++ У языка Си++ я вижу два основных недостатка: 1) неясный синтаксис объектно-ориентированных операций и 2) отсутствие динамического связывания и типизации. Сразу же должен пре¬ дупредить, что механизм виртуальных функций дает средства динамической типизации. Однако по-прежнему сущестъует мно¬ жество причин по которым желательно иметь связывание во время выполнения. Это особенно важно для программ, которые должъ.ы. эффективно работать в ситуациях, явно не предусмотренных прсь граммистом.
64 Объектно-ориентированные расширения Си: языки и инструментальные средства Си++ — необычная, но интересная попытка создания нового типа объектно-ориентированного языка. Если обратиться к перечислен¬ ным ранее критериям, то Си++ содержит отсутствующий в боль¬ шинстве объектно-ориентированных языков для микрокомпьютеров механизм множественного наследования. В то же время у него отсутствуют связывание периода выполнения и средства комбинации методов. Одним из нововведений языка Си++ стали защищенные члены, а именно разрешение доступа к ним со стороны классов-наследников. Я считаю, что эту возможность следу¬ ет включить и в другие объектно-ориентированные системы. Некоторые новые термины, скажем использование ключевого слова static в сочетании с переменными и функциями класса, следует признать полезными. Ключевое слово static выявляет возможность использования переменных класса в качестве кон¬ стант во время выполнения. Я полагаю, что и изменение их во время выполнения может оказаться полезным. В список пожеланий к Си++ следует добавить стандартную биб¬ лиотеку классов, включающую, вероятно, такие возможности, как ввод-вывод. Важно иметь иерархию стандартных структур дан¬ ных. Библиотека Zortech Too!s — существенный шаг в этом направлении. Заключенные в ней идеи могут быть использованы и в других объектно-ориентированных диалектах Си. Другим пожеланием, которое может быть реализовано только при наличии связывания периода выполнения, будет динамическое переопреде¬ ление классов, подобное реализованному в Common Lisp Object System. Идеям еще надо дать оформиться, прежде чем будет достигнуто соглашение по объектно-ориентированному Си. Я ду¬ маю, что разработчики всех диалектов согласятся, что хотя в этой области достигнут существенный прогресс, объектно-ориентиро¬ ванные расширения Си — это новое направление, в котором, прежде чем принимать окончательные решения, требуется нако¬ пить опыт и набор подходов. ■ ЯЗЫК OBJECTIVE-C Первым реализованным объектным расширением Си был Objective-С корпорации StepStone. Он был разработан программи¬ стами, работающими на Си, которые стремились использовать преимущества ООП при работе над традиционными программны¬ ми проектами. Несмотря на такую направленность, я считаю, что Objective-C — это многообещающее окружение для разработки самых современных приложений. Текущая версия представляет собой относительно законченную реализацию Objective-C, отражающую многолетний опыт решения проблем, с которыми сталкиваются^ программисты. Из-за сложно¬
Язык Objective-С 65 стей с некоторыми классами в предыдущих версиях базовой библиотеки она непрерывно подвергалась переработке. Таким об¬ разом, существующая иерархия классов представляет собой резуль¬ тат нескольких лет эволюции. Компилятор Objective-С состоит из двух выполняемых файлов: программы-драйвера OBJCC.EXE и самой программы Objective-C (файл OBJC.EXE). Драйвер вначале вызывает программу CL.EXE фирмы Microsoft, которая проверяет синтаксис, а затем компиля¬ тор с Objective-C. Во время связывания не нужно указывать библиотеки, поскольку ссылки на них встраиваются в .OBJ файлы самим компилятором. Возможность динамического связывания помещает Objective-C в категорию средств, пригодных для построения серьезных, харак¬ терных для реальной жизни приложений. Это ему удается несмот¬ ря на то, что он компилирует код в Си, а далее работает обычный Си-компилятор. Гибридность языка Objective-C существенно отли¬ чает его от других объедтно-ориентированных систем. Он похож на объектно-ориентированный Лисп тем, что позволяет писать на базисном языке (в данном случае — Си). Objective-C и Смолток имеют существенно разные объемы доступного для повторного использования кода в исходных текстах. Хотя Objective-C и пред¬ лагает в этой области значительно больше, чем Си++, он сущест¬ венно отстает от Смолтока, самого старого из объектно-ориентированных языков. В определенном отношении Objective-C скорее консервативный объектно-ориентированный язык. В нем нет новых концепций по сравнению с теми, что появились в Смолтоке много лет назад. В нем реализованы даже не все возможности Смолтока. Если рас¬ сматривать его как гибрид Смолтока и Си, то доминировать безусловно будет Си. Если рассматривать отличия этого языка от Си, мы обнаружим один новый тип данных, объекты и одну новую операцию — сообщение. Синтаксис Objective-C почти везде прост. Для объявления новых классов йспользуется знак равенства (=), двоеточие служит для указания суперкласса. Другие элементы объявления класса выде¬ ляются скобками. Все объявления данных выделяются фигурны¬ ми скобками. Например =Array:Object {short capacity;} объявляет Array как подкласс класса Object с переменной capacity (емкость), определенной как короткое целое. Как и в Смолтоке имеются два типа методов: методы классов и методы экземпляров. Для их выделения служат знаки плюс (+) и минус (-) соответст¬ венно. Как и в Смолтоке, для создания экземпляра следует послать сообщедае (new) соответствующему классу. В Objective-C имеются 3-857
66 Объекгнооривнтированные расширения Си: языки и инструментальные средства два типа выражений-сообщений: унарные выражения и ключевые выражения. Бинарных выражений, подобных имеющимся в Смол- токе, нет. Выражение id myarray = [ByteArray new:80]; создает новый экземпляр класса ByteArray размером 80 байт. Определение метода (new) для класса Object имеет вид: +new {return (*_alloc)(self,0);} При этом для размещения объекта в памяти вызывается встроен¬ ный примитив. Более ничего не требуется, поскольку Object — это самый простой абстрактный класс. Сообщение new: (new с двоето¬ чием) класса Array требует следующего определения средствами Objective-C: +new:(mt)nElements { self = (*_alloc)(self,nElements*[self ndxVarSize]); capacity = nElements; return self;} В Objective-C все сообщения помещаются внутри квадратных скобок. Таким образом, выражение [self ndxVarSize] — это сооб¬ щение. Сообщение ndxVarSize — это метод классаАггау, который переопределен следующим образом: +(int)ndxVarSize {return (int)[self subclassResponsibil..ty] ; } Метод subclassResponsibility просто печатает текст: ”Подкласс должен переопределить сообщение”. Выражение capacity=nElements просто устанавливает емкость массива равной передаваемому с ’new:’ параметру. ■ ИЕРАРХИЯ КЛАССОВ Ниже приведена иерархия базисных классов Objective-C в том виде, в котором ее мог бы отобразить System Browser, если бы он существовал в системе Objective-C: Object Array BytArray IdArray IntArray Assoc Cltn OrdCltn Set Bag Dictionary Stack AsciiFiler BalNode
Иерархия классов 67 SortCltn IPSequence Sequence ObjGraph Point Rectangle String Unknown B Objective-C классы называют объектами-фабриками. Этим спо¬ собом подчеркивается то, что класс представляет собой объект, основная функция которого создавать экземпляры и подклассы. Однако, как вы могли заметить, отсутствуют классы Class и Metaclass. В Objective-C классы не являются экземплярами мета¬ класса и не создаются при помощи посылки ему сообщения, как это принято в Смолтоке, Xerox LOOPS и многих объектных рас¬ ширениях Лиспа. В этом нет ничего плохого. Это просто отражение того факта, что Objective-C — гибридный объектно-ориентирован¬ ный язык. Библиотека классов Objective-C состоит из четырех разделов: ба¬ зовые классы, коллекции, классы ввода-вывода и классы других типов данных, например String. Как видно из таблицы, в новой иерархии отсутствует класс AVL. Вместо него добавлены новые: абстрактный класс BalNode и его подкласс SortCltn. BalNode содержит родовой код, поддерживающий реализацию произволь¬ ных бинарных деревьев. Занимающийся упорядоченными набора¬ ми класс SortCltn заменил AVLTree. Другим существенным изменением стала реализация класса Sequence в качестве подклас¬ са IPSequence. Последний реализует сортировку произвольной коллекции на месте. Чтобы воспользоваться реализованной в классе IPSequence техникой, в класс Collection включен метод contents, возвращающий указатель на экземпляр IdArray, кото¬ рый используется клиентом для запоминания элементов коллек¬ ции. Новый класс AsciiFiler выполняет все свойственные файлам операции и позволяет обмениваться текстами между подключен¬ ными к сети машинами разных архитектур. ObjGraph — еще один интересный класс. Он используется для создания графа иерархии классов, включающей все классы, от которых он наследует. В руководстве описан пример использова¬ ния этого класса для реализации метода broadcast, который пол¬ учает в качестве аргумента имя метода и рассылает его всем доступным объектам. Метод asGraph класса Object был переписан в соответствии с новым способом построения графов. Ранее я показывал некоторые операции над классом Array. В Objective-C класс Array реализован иначе, чем в других объектно¬
68 Объектно-ориентированные расширения Си: языки и инструментальные средства ориентированных языках. В Смолтоке класс Array является под¬ классом класса Collection, хотя и не непосредственным. В Objective-С Array — это формальный или, иначе говоря, абстрак¬ тный класс, непосредственный потомок класса Object. Так сделано по соображениям эффективности, поскольку в Си массивы уже реализованы. Новым в реализации класса Array будет доступ к переменным экземпляров не по имени, а по индексу. Число элементов в массиве фиксировано. В отличие от более хитроумных классов коллекций, речь о которых пойдет позже, размер массивов не может быть увеличен, после того как число элементов достигло указанного для данного массива максимума. Подклассы Array также имеют дело с массивами элементов разных типов. Как и в большинстве объ¬ ектно-ориентированных языков, класс Array не позволяет опреде¬ лять многомерные массивы. Для этого необходимо заводить специальные подклассы данного класса. Как мы видим, емкость классов массивов в Objective-С фиксиро¬ вана. Если создан массив определенного размера, нет способа изменить его длину. Однако для коллекций это не так. Они разрабатывались как ’’растущие” классы, которые могут включать больше элементов, чем было изначально задано. Предназначенный для этого метод expand записывается так: expand {contents=[contents capacity: capacity+=capacity]; return self;} Таким образом на языке определяется метод, изменяющий значе¬ ние переменной contents и удваивающий размер коллекции при помощи операции инкремента. В Objective-С объекты рассчитаны на использование единого ад¬ ресного пространства. Они идентифицируются при помощи своих адресов в оперативной памяти. Из этого следует, что системы, содержащие объекты,расположенные на диске или в других узлах компьютерной сети, не могут быть запрограммированы при помо¬ щи Objective-С. В этом языке все объекты должны располагаться в основной памяти главного компьютера. Objective-C — это гибридный язык, допускающий ’’жульничест¬ ва”. Это означает, что в отличие от чистых языков типа Смолтока на Си можно написать код, непосредственно обращающийся к защищенной памяти объектов. Следует отметить, что это может привести к большим неприятностям и в корне противоречит идее инкапсуляции, одной из самых важных в ООП. Программист должен четко осознавать последствия таких действий. Системы типа Objective-C позволяют идти на риск в расчете на повышение производительности.
Коллекции 69 ■ КОЛЛЕКЦИИ Главную роль при создании структур данных играет группа клас¬ сов Collection. Это справедливо не только для Objective-C, но и для других объектно-ориентированных языков. К этой части иерархии принадлежат следующие классы: Cltn OrdCltn Stack Set Dictionary Ba g BalNode SortCltn Cltn — это абстрактный (формальный) класс, его методы и пере¬ менные предназначены для использования наследниками этого класса, которые и будут работать в программах. Предполагается, что экземпляры будут только у его подклассов. Методы коллекций подразделяются приблизительно на дюжину категорий: создание экземпляров, добавление, удаление, упорядочивание, ’’выполне- ние элемента”, преобразование, печать, освобождение, копирова¬ ние, опрос, сравнение и приватные методы. Чтобы понять их, нужно познакомиться с тем, как работает коллекция. Непосред¬ ственно структуры данных коллекции используются для хранения не элементов, а указателей на элементы класса IdArray, который в действительности и содержит элементы. Методы опроса исполь¬ зуются в приложениях для запросов и поисков, подобных тем, что выполняются в базах данных и обычных системах. Так, например, метод find ищет объекты по имени и, если они содержатся в коллекции, возвращает их. Методы ”выполнить элементы” последовательно выполняют опре¬ деленную операцию над каждым из элементов коллекции, для чего каждому из них коллекции посылается сообщение. Одна из про¬ блем состоит в том, что методы требуют различного числа пара¬ метров, а Objective-C не поддерживает функций с переменным числом параметров. Решение состоит в реализации нескольких методов elementsPerform, требующих разного числа аргументов. Предусмотрены версии, принимающие до трех аргументов. Их можно достаточно просто использовать для построения методов с большим числом параметров. Последовательностями называют коллекции, для элементов кото¬ рых существен порядок. Специализированные подклассы класса OrdCltn реализуют очереди и стеки. В последовательностях не допустимы элементы NIL. При удалении элементов из последова¬
70 Объектно-ориентированные расширения Си: языки и инструментальные средства тельностей происходит упаковка, и место, освобожденное удален¬ ным элементом, заполняется. Методы включейия в последователь¬ ность определяют место, на которое следует поставить элемент. В число методов входят AddFirst (добавить первым), AddLast (доба¬ вить последним), insert:before (вставить до) и insert:after (вставить после). Стек — это последовательность, поддерживающая дисциплину: ’’последним пришел — первым вышел”. Кроме методов push и pop в Objective-C предусмотрены методы at и removeAt для произволь¬ ного доступа к элементам стека. Методы манипуляции стеком включают также изменение порядка элементов (метод swap) и доступ к элементам стека без его модификации topElement и lastElement. Множества — это коллекции, которые могут содержать ровно по одному экземпляру каждого элемента. Повторения не допускают¬ ся. Множества особенно эффективны при построении таблиц сим¬ волов. Реализация класса Set допускает в качестве элементов объекты любых типов. В одном множестве могут содержаться объекты разных типов. Это означает, что добавление новых эле¬ ментов в множество требует полного просмотра всех существую¬ щих элементов. Класс Set включает возможность хэширования. При помощи сообщения ’hash’ множество для ускорения поиска помещает все содержащиеся в нем элементы в хэш-таблицу. Невозможность динамического изменения существенно ограничи¬ вает применимость описанных здесь множеств. Изменение объекта множества нарушит корректность механизма доступа. Класс Dictionary является потомком класса Set и представляет собой множество ассоциаций. В этом случае разрешены повторя¬ ющиеся значения, но ключи должны быть различны. Это дости¬ гается благодаря тесному взаимодействию с классом Assoc. Ассоциации хранят связи между ключами и значениями, допу¬ ская запоминание в словарях пар и организуя доступ к ним по ключу. Ассоциации выполняют сравнения, передавая сообщения ключам. Кроме методов, унаследованных у класса Set и его предков, в классе Dictionary реализованы 6 новых методов: метод with: для инициализации новых словарей и пять методов индекс¬ ного доступа: atKey, atKey:put, values, includesAssociation: и includesKey:. Как было сказано раньше, класс SortCltn заменяет использовав¬ шийся ранее класс AVLTree. Элементы упорядоченной коллекции всегда отсортированы. При добавлении нового объекта он с самого начала вставляется на нужное место. Способ упорядочивания зависит от значения переменной cmpsel. По умолчанию использу¬ ется селектор compare. Переменная может принимать и другие
Графика 71 значения: invertCompare и dictCompare. Это имена методов. Мето¬ ды compare и invertCompare реализованы в корневом классе Object. Метод dictCompare реализован в классе String. Класс SortCltn ведет себя, как если бы он был подклассом Cltn, хотя это не так. Как написано в руководстве, определенные в нем операции '’со¬ вместимы по интерфейсу”. Переменная addDupAction также вли¬ яет на поведение объектов класса SortCltn. Она может принимать одно из 4 значений: ADD Сцобавить), REJECT (отвергнуть), MERGE (слить) и REPLACE (заменить). Выбор опции определяет способ обработки дубликатов. Если выбрано значение ADD, дубликаты разрешаются, для поддержания порядка они вставляются непос¬ редственно после оригиналов. Опция REJECT запрещает повторы. Как следует из названия, опция MERGE выполняет слияние, используя для этого метод merge класса элементов. ■ ГРАФИКА Objeciive-C содержит всего два рудиментарных графических клас¬ са: Point (точка) и Rectangle (прямоугольник). Покуда это так, рано говорить об объектно-ориентированной графической системе, однако конструкция этих классов информативна. Класс Point включает две переменные xLoc и yLoc. К унаследованным у класса Object добавлены методы, используемые для установки координат и доступа к их значениям, изменения координат и для выполнения над их значениями простейших арифметических действий. Чтобы использовать элементы класса Point для рисования, в объектно-ориентированных системах обычно предусматривается что-то вроде реализованного в Смолтоке и Акторе класса Pen, с помощью которого выполняются основные функции ’’черепашьей” графики. В таких системах класс Pen наследует классу BitBlt, ответственному за пересылку битовых блоков. Эти классы не включаются в систему Objective-С, поэтому, для того чтобы вос¬ пользоваться классами Point и Rectangle, необходимо реализовать эквиваленты BitBlt и Pen. Ниже приведены короткая демонстрационная программа на Objective-С и сгенерированный компилятором листинг на Си. Для облегчения чтения текст на Си переформатирован. Листинг 2. Короткая демонстрационная программа на Objective-C = DemoPoint: Object (Practice) [ int xLoc,yLoc; } + create {return [[super new] initialize];} - initialize {xLoc=100;yLoc=100;return self;} - print {print ("Координаты этой точки (%d6%d)\n",xLoc,yLoc) ; }
72 Объектно-ориентированные расширения Си: языки и инструментальные средства Листинг 3. Выходной код на Си #line 2 "demo.c" typedef struct_PRIVATE *id; id_msg(),_msgSuper(); #line 1 "demo.m" #line 5 "demo.c" struct_PRIVATE { struct_SHARED *isa; int xLoc; int yLoc; }; extern id DemoPoint,Object; struct_SHARED { struct_SHARED *isa, *clsSuper; cha(r *clsName; char *clsTypes; shbrt clsSizInstance; short clsSizDict; struct_SLT *clsDispTable; } extern struct_SHARED _DemoPoint,_DemoPoint; extern char *Practice[]; #line 1 "demo.m" #line 3 "demo.m" /* create = Practice[0] */ static id_l_DemoPoint(self,_cmd)id self;char *_cmd; { return_msg(_msgSuper(_DemoPoint.clsSuper,Practice[1] /*new*/),Practice[2]/*initialize*/); } #line 5 "demo.m" /* initialize=Practice[2] */ static id_2_DemoPoint(self,_cmd)id self; char *_cmd; { self-xLoc=100;self-yLoc=100;return self; } #line 7 "demo.m" /*print=Practice[3]*/ static id_3_DemoPoint(self,_cmd)id self;char *_cmd; { printf (’’Координаты этой точки (%d@%d) \n", self-xLoc; self-yLoc); } #line 16 "demo.c" extern struct_SHARED _Object, _Object; struct_SLT { char **_cmd; id(*_imp) () ; }; static struct_SLT _clsDispatchTbl[l] = { &Practice[0],(id(*)())_l_DemoPoint,/*create*/ }; static struct_SLT _nstDispatchTbl[2]= { &Practice[2],(id(*)())_2_DemoPoint, /*initialize*/ &Practice[3],(id(*)(>i_3_DemoPoint, /*print*/ };
Обсуждение 73 static char _bufClsName[]="_DemoPoint"; struct _SHARED _DemoPoint= { &_Object, &_Object,&_bufClsName[0],0,sizeof(struct _SHARED),1, (struct _SLT *)_clsDispatchTbl }; struct _SHARED _DemoPoint= { &_DemoPoint,&_Object,&_BufClsName[1], "#ii",sizeof (struct _PRIVATE),2, (struct _SLT *)_nstDispatchTbl }; line 8 "demo.m" ■ СИМВОЛЬНАЯ ОТЛАДКА Версия Objective-C для MS-DOS позволяет использовать отладчик CodeView фирмы Microsoft, способный работать даже с синтакси¬ сом Objective-C. Для этого при компиляции нужно пользоваться опцией -g; выполнив это, вы можете получить в CodeView исход¬ ный текст программы и сможете установить в нем точки останова, вычислять выражения и т.п. Однако отсутствует возможность непосредственного просмотра объектов. Чтобы увидеть их содер¬ жимое, необходимо с помощью средств низкого уровня получить адреса интересующих объектов. Чтобы разобраться в шестнадца¬ теричном представлении, вам потребуется знать структуру храня¬ щейся в них информации. Применение крманд CodeView к 32-битным указателям позволит вам инспектировать значения. Из этого следует, что часть отладки, которая может быть выполнена в терминах исходного текста, существенно ограничена. ■ ОБСУЖДЕНИЕ При появлении систем типа Objective-C неизбежно встает вопрос, в чем ее преимущества по сравнению с традиционной объектно¬ ориентированной системой вроде Смолтока. Если вы используете ее в среде MS-DOS и не имеете ”поводыря”, то несомненно обнару¬ жите, что возможности изучения системы в интерактивном режи¬ ме сильно ограничены. В Смолток-системе кроме броузеров и других оконно-ориентированных средств вы сможете воспользо¬ ваться различными встроенными методами, помогающими изу¬ чить систему и снабжающими в интерактивном режиме всей необходимой для написания программы информацией. В версии Objective-C для MS-DOS вам придется положиться на печатную документацию. По счастью, она написана очень хорошо и отличие состоит в основном в удобстве. Как и для любых языков програм¬
74 Объектно-ориентированные расширения Си: языки и инструментальные средства мирования, можно написать разнообразные утилиты, такие, как программы построения перекрестных ссылок и другие, которые могут помочь вам. Следующий вопрос касается размера базовой библиотеки классов, поставляемой с системой. По этому параметру Objective-C занима¬ ет промежуточное положение между Си++ и более развитыми окружениями Смолтока и Актора. В определенной степени это компенсируется возможностью использования существующих программ на Си для построения специализированных библиотек классов. Однако в некоторых случаях эта возможность существует и для других языков. Кроме этого, следует иметь в виду, что структуры данных периода выполнения Objective-C могут не всег¬ да сочетаться со всеми системами программирования на Си и не со всеми библиотеками, написанными для Microsoft С. К несча¬ стью, это относится и к программному окружению MS Windows. Их требования к среде выполнения противоречат друг другу и их нельзя использовать вместе. В новой версии, однако, этот недоста¬ ток преодолен. Отсутствие конструкции блока существенно отличает Objective-C от других объектноориентированных языков, в частности Смол¬ тока и Актора, В этих языках блок кода реализуется как непои¬ менованный объект с отложенным выполнением. Блоки представляют собой чрезвычайно мощную конструкцию, внося¬ щую .дополнительную степень модульности в определения мето¬ дов. Ведутся исследования по внесению конструкции блока в Objective-C, однако это средство еще не анонсировано для будущих версий. Мир объектно-ориентированного программирования полон мета¬ фор. Метафоры — ”палки о двух концах”. Без сомнения они выполняют полезную функцию при объяснении основных понятий этой парадигмы программирования. Но они же являются источ¬ ником заблуждений. Поэтому время от времени их необходимо пересматрива ть. В каком смысле разумно сравнивать классы с фабриками? Един¬ ственный реальный их продукт — экземпляры. Подобно фабрике классы предназначены для выпуска одного продукта. Но напоми¬ нает ли фабрика свою продукцию? Разрабатывается ли класс с целью эффективного изготовления большого количества экземп¬ ляров? Это показывает, что классы следует уподоблять скорее лекалам или штампам, чем фабрикам. Однако они превосходят штампы, поскольку могут быть использованы для изготовления других штампов. В этом смысле классы еще более гибкое средство, чем фабрики или штампы.
Ctalk 75 Как обстоит дело с метафорой программной интегральной схемы? Подобно тому как некоторые микросхемы могут применяться для построения различных электронных плат, одни и те же классы можно использовать в разных программах. Но в чем смысл повтор¬ ного использования классов? Важно понять, что классам присущ совершенно новый подход к повторному использованию, не свой¬ ственный ни библиотечным функциям, ни интегральным схемам. Вы не можете использовать стандартную микросхему для конст¬ руирования нового чипа. Однако вы, очевидно, можете строить новые библиотечные процедуры и классы, используя имеющиеся. Существенное отличие между классами и библиотечными функ¬ циями состоит в том, что классы больше по размеру, более сложны и используются разными способами. Обычно класс содержит мно¬ жество библиотечных функций и данных, одновременно включа¬ емых в каждое эксплуатирующее класс приложение. Родовые возможности интегральных схем и библиотечных функций суще¬ ственно ниже. Синтаксис Objective-C очень удачен. Он более удобочитаем, чем Си или Си++. Многие критикуют Си за отсутствие наглядности. В этом Objective-C имеет явные преимущества по сравнению с поро¬ дившим его языком. Вы получаете большинство преимуществ Си, но на более высоком уровне, с большим числом структур и функ¬ ций, включенных в стандартный набор возможностей языка. В принципе, Objective-C позволяет создавать большие высокоуров¬ невые приложения, не опасаясь, что они будут существенно завя¬ заны на машинно-зависимые подпрограммы и специализйрованный код, способные с течением времени стать обузой. Наконец, Objective-C реализует в среде Си динамическое связывание. Это означает, что объекты создаются во время выпол¬ нения, а не при компиляции. В этом, кроме более удобочитаемого синтаксиса, и состоит отличие между Objective-C и Си++. Я использовал версию 40, перенесенную на ЮМ PC/AT. Этот язык реализован также на компьютерах VAX, рабочих станциях Sun и HP 9000. Распространяемая на дискетах высокой плотности вер¬ сия компилятора для MS-DOS включает компилятор, библиотеки, а также основные классы в исходных текстах. В настоящее время система работает только с компилятором Microsoft С 5.1. Предпо¬ лагается поддержка версии 6.0. ■ CTALK Как и следует из названия, Ctalk — это программный инструмент, который ведет себя как гибрид Си и Смолтока. Он более эффекти¬ вен по сравнению с другими попытками скрещивания, поскольку внедряет броузер Смолтока в программное окружение Си. Несмот¬
76 Объектно-ориентированн&е расширения Си: языки и инструментальные средства ря на то что Си не интерпретируемый, а компилируемый язык, броузер Ctalk VTEWS работает в интерактивном режиме. Кроме броузера Ctalk включает и другие важные для настоящего объек¬ тно-ориентированного языка средства: наследование, инкапсуля¬ цию и связывание периода выполнения. В системе Ctalk три основные программы. Кроме броузера имеется препроцессор и утилита Make. Чтобы понять преимущества Ctalk, я опишу новый броузер, служащий настоящим ядром, вокруг которого построена вся система. Удивительно, насколько броузер CtaUc похож на послуживший прототипом соответствующий инструмент Смолтока. Как и послед¬ ний, броузер Ctalk представляет собой комбинацию редактора и менеджера приложений, позволяющий интерактивно и инкремен¬ тально разрабатывать объектно-ориентированные системы. Это существенное достижение для основанного на Си объектно-ориен¬ тированного инструмента. Броузер реализован в виде отдельной программы, которая вызы¬ вается так же, как и любая другая программа. Однако, как будет видно, эта программа предоставляет исключительно могучие сред¬ ства интеграции практически всей системы Ctalk. Она не только дает возможность познакомиться в интерактивном режиме с ок¬ ружением, но и может быть использована для выполнения в диалоге множества действий, включая создание шаке-файлов для пакетной обработки, компиляции и связывания. Ctalk броузер состоит из нескольких панелей, похожих на распо¬ ложенные вплотную друг к другу окна, которые не могут изменять размеры и перекрываться. На левой верхней панели отображается иерархия классов. Как и в броузере Смолтока, если выбрать класс, то его методы будут видны на правой верхней панели. Если, в свою очередь, выбрать один из методов, его текст можно редактировать в нижней текстовой панели. Кроме того, в броузер можно загрузить произвольный текстовый файл, который будет доступен для редак¬ тирования в нижней панели. Таким образом, броузер, с одной стороны, небольшой простой редактор, а с другой — специальное средство для работы с объектами Ctalk. При работе с броузером на разных панелях доступны разные всплывающие меню. Они могут быть активизированы нажатием клавиши или при помощи мыши. В состав броузера входит обо¬ лочка операционной системы, позволяющая выполнять команды MS-DOS не выходя из броузера. Этот тип оболочек стал настолько распространен, что его отсутствие кажется странным. Между броузерами С-talk и Смолтока имеется существенная разни¬ ца, состоящая в том, что первый представляет собой отдельную программу в компилирующей среде, а второй является частью
Синтаксис Ctalk 77 интегрированной системы, все части которой сосуществуют одно¬ временно, Когда броузер Ctalk загружается в первый раз, он, не имея внутри себя описаний классов, просматривает текущий ди¬ ректорий на диске в поиске определений классов, а затем для обеспечения интерактивного доступа к ним компилирует их в базу данных. После того как все загрузилось в броузер, пользователю становит¬ ся доступным множество функций, необходимых для создания приложений. В их число входят средства описания новых прило¬ жений, загрузки существующих классов, определения новых клас¬ сов и их методов и сохранения результатов работы. Протестированная мною версия еще далека от совершенства. На¬ пример, размер текстовых файлов, которые можно редактировать, ограничен. Если загружаемый файл превышает максимальный размер, загружается только часть, но сообщение об ошибке не выдается. Для неосторожных пользователей это очень опасно, поскольку внесение небольшого изменения в файл может повлечь за собой огромные потери данных. По этой причине я должен сказать, что броузер удобен в первую очередь для работы со средствами build. Они позволяют создавать множество небольших файлов, из которых и будут составлены модули законченной системы. Для больших файлов следует пользоваться другими редакторами. ■ СИНТАКСИС CTALK Синтаксис Ctalk слегка отличается от синтаксиса других объект¬ но-ориентированных диалектов Си. Достаточно усвоить несколько правил, и всякий знакомый с Си без труда научится читать и писать на нем. Основное соглашение относится к сообщениям Ctalk, которые всегда ограничены с двух сторон символами @. Например, если мы хотим создать новый экземпляр класса Rectangle, сообщение будет выглядеть так: @Rectangle new_&rect0 Здесь символ & использован для обозначения указателя, т. e. в обычном для Си смысле. В Ctalk имеется весьма мощный механизм отображения одних сообщений на другие. Это означает, что селек¬ тор или имя сообщения может быть передан в качестве аргумента другому сообщению. Синтаксически это оформляется в виде опе¬ ратора присваивания переменной имени селектора сообщения, заключенного в обратные кавычки. Для того чтобы послать сообщение, которое ссылается на другое, запомненное в переменной, в классе Object предусмотрены специ¬
78 Объектно-ориентированные расширения Си: языки и инструментальные средства альные методы, которые наследуются всеми другими объектами. Это perform_, perform_with_ и perform_with_with. Например, пусть переменная gval определена следующим образом: id gval; gval = 'getValue' а объект id obj; int gval; оператор id — это декларация класса id. В программах на Ctalk все классы, на которые имеются ссылки, должны быть определены извне. Отображение ‘getValue* можно записать с помощью слёду- ющего выражения: 0obj perform_ gval with &val0; Здесь подразумевается, что val — слот в obj, а сообщение ‘getValue4 использует его в качестве аргумента. Как и в большинстве объек¬ тно-ориентированных языков, для ссылки на объекты внутри со¬ общений используются псевдопеременные ’self’ и ’super’. Псевдопеременная ’self’ ссылается на объект, которому послано сообщение. Псевдопеременная ’super’ ссылается на суперкласс данного класса. Для доступа к переменным экземпляра, которому послано сообщение, используется нотация self->width Предполагается, что width — имя переменной экземпляра. Другое синтаксическое правило CtaUc касается символа подчеркивания. Когда селектор сообщения заканчивается подчеркиванием, это означает, что за ним должен следовать аргумент. С практической точки зрения одним из наиболее существенных свойств объект¬ но-ориентированной системы является количество встроенных ба¬ зовых классов. Их мы и рассмотрим далее. ■ БАЗОВЫЕ КЛАССЫ CTALK В табл. 3.4 перечислены базовые классы Ctalk. Container представляет собой абстрактный класс, используемый для определения подклассов, поддерживающих динамические данные. Это значит, что любой подкласс класса Container дает возможность создавать экземпляры, хранящие данные перемен¬ ной или фиксированной длины, вместе с последовательными или иными методами доступа к ним. Так, например, если объекту посылается сообщение expand, то его размер удваивается. Для более гибкого увеличения размера используется метод expandBy_, получающий в качестве параметра число дополнительных элемен¬ тов, на которые расширяется объект. Подобным образом метод
Базовые классы Ctalk 79 Таблица 3.4. Базовые классы Ctalk Objact Assoc Container Buffer Stream ByteArray Collection OrdCollect Stack Set Dictionary bitArray String PutRecSize_ устанавливает размер в байтах, занимаемый каждым содержащимся в объекте элементом данных. Для доступа к индек¬ сированному объекту воспользуйтесь методом at_get_nRecs, кото¬ рый возвращает указанное число записей, начиная с заданного номера. Почти все базовые классы — это подклассы класса Container, реализующие различные типы структур данных. Обыч¬ но в объектно-ориентированных системах динамические структуры определяются в более глубоких членах иерархии классов. Предло¬ женный способ нов, и о нем следует рассказать поподробнее. Класс Buffer предназначен для простого хранения больших блоков данных в байтовом или словном форматах. Непосредственное расширение этого класса включает переменную экземпляра, ука¬ зывающую, состоит ли буфер из байтовых или словных объектов. В результате могут быть написаны общие процедуры чтения и записи, которые вначале посылают буферу сообщение, чтобы уз¬ нать, какой формат он имеет: байтовый или словный. В этом случае программа не должна специфицировать тип буфера. Класс Stream (поток) реализован в виде подкласса класса Buffer и включает переменную position, содержащую текущую позицию потока. Класс Collection предоставляет объекты, в которых можно группировать другие объекты. Подклассы Collection различаются между собой порядком доступа к объектам (последовательный или произвольный) и наличием ограничения размера. Например, класс OrdCollect позволяет добавлять к извлекать объекты после¬ довательно, а размер самой коллекции автоматически увеличива¬ ется при добавлении новых элементов.
80 Объектно-ориентированные расширения Си: языки и инструментальные средства Классы текстовых окон Фирма CNS разработала несколько дополнительных классов, под¬ держивающих создание текстовых окон в среде Ctalk. Сгруппиро¬ ванные иерархически эти дополнительные классы приведены в табл. 3.5. Опишем кратко назначение некоторых из них. Класс Browser содержит основные методы для создания броузеров, подобных включенному в систему. Этот класс содержит более 12 методов. Приведен пример тестирующей броузер программы. Этот код реализует текстовый броузер, в котором отсутствуют многие эле¬ менты реального инструмента, но из которого ясно, как устроен настоящий броузер. Те, кто хотел бы сделать собственный броузер, наращиваемый по желанию, могут в качестве отправной точки воспользоваться этим кодом. Подпрограмма инициализации вначале посылает сообщение ме¬ неджеру экрана и передает тип создаваемого экрана. Затем объек¬ ту Mouse сообщают, что нужно создать курсор определенного типа. Для обслуживания списка окон создается объект Ordered Collection. Затем строится сам объект Windows. Задаются его размер и атрибуты. И наконец, создается Event Manager (менеджер событий). Перед нами законченный пример того, как в Ctalk используется одна из черт объектно-ориентированной парадигмы: использование одного объекта для управления другими. Недоста- Таблица 3.5. Дополнительные классы текстовых окон Browser File Menu Mouse Notifier ScreenMgr TxPoint Window ButtonWin ItemWin Scrollbar StdWindow ListWindow HorListWin PopUp Response TxtWindow TxEditor WinManager
Заключительные замечания о Ctalk 81 ток этого подхода состоит в тенденции к ’’увязанию в объектах” при увеличении их числа. ■ СОЗДАНИЕ ИСПОЛНЯЕМЫХ ПРОГРАММ В Ctalk создание исполняемой программы производится за 5 шагов. Первый состоит в написании требуемьгх: исходных текстов на Си и Ctalk. Их нужно записать на диск в файлы с расширениями .PRE. На втором шаге необходимо написать на Си головную программу, содержащую обращения к требуемым модулям. Как только это выполнено, можно приступать к третьему шагу — запуску препроцессора, который преобразует все файлы CtaUc в соответствующие файлы на Си. На четвертом шаге все файлы на Си обрабатываются соответствующим компилятором. Последний шаг состоит в связывании полученных файлов .OBJ в выполняе¬ мую программу. Как было сказано ранее, желающие могут сократить некоторые шаги, воспользовавшись опцией Make броузера. Для этого необхо¬ димо выбрать опцию Make Spec подменю. В результате выполне¬ ния интерактивной процедуры будет создан файл с расширением .MAK, предназначенный для специальной вспомогательной про¬ граммы, которая выполнит в пакетном режиме препроцессирова¬ ние, компиляцию и связывание. Поскольку броузер поддерживает выполнение команд и временный выход в DOS, нет нужды поки¬ дать его ни для изготовления законченной исполняемой програм¬ мы, ни для ее последующего тестирования. ■ ЗАКЛЮЧИТЕЛЬНЫЕ ЗАМЕЧАНИЯ О CTALK Ctalk успешно выполняет свое предназначение, а именно реали¬ зует настоящий гибрид языков Си и Смолток. Среди аналогичных попыток эту выделяет наличие броузера. Наиболее существенным отличием между Смолтоком и всеми появляющимися гибридами Си служит существенное различие в числе базовых классов. Смол¬ ток, в том виде в котором он продается, — это не только язык, но огромная библиотека родовых и по определению стандартных функций. По этой причине описанные мною классы Window служат суще¬ ственным довеском к системе Ctalk. Важность таких библиотек классов можно понять, если вспомнить, что в объектно-ориенти¬ рованной системе рабочими элементами служат объекты, а не отдельные функции. В этом смысле в системе с большой библио¬ текой классов существенная часть программирования уже выпол¬ нена. Необходимость изучения этих классов составляет оборотную сторону медали. Хотя единообразие интерфейсов и входит в число
82 Объектно-ориентированные расширения Си: языки и инструментальные средства достоинств объектно-ориентированного подхода, обучение все-та- ки происходит постепенно. К числу небольших недостатков этого типа систем следует отнести то, что они в основном рассчитаны на работу с исходным текстом на Ctalk при помощи броузера. Если разработчик желает продать библиотеку в виде объектных файлов, то существующая реализа¬ ция не позволит использовать их вместе с броузером. Вероятно, было бы достаточно загружать в броузер наиболее важную инфор¬ мацию, не требуя при этом разглашения исходных текстов. Это позволит использовать броузер в сочетании с библиотеками клас¬ сов в объектной форме. Следует особо подчеркнуть, что Ctalk содержит все существенные черты объектно-ориентированного языка, а броузер сделан настолько хорошо, что позволяет придать интерактивный ’’привкус” Смолтока, сохраняя все преимущества системы, основанной на Си. ■ VIEWS: ОБЪЕКТНО-ОРИЕНТИРОВАННЫЙ ИНСТРУМЕНТ РАЗРАБОТКИ СИ-ПРОГРАММ ДЛЯ WINDOWS В этом разделе я расскажу об инструменте разработки VIEWS для Си++ версии 2.0. Эта среда программирования, базирующаяся на Windows, предназначена для любого из описанных выше объект¬ ноориентированных диалектов Си. Имеются версии VIEWS для Си++ версии 2.0, Objective-C и Ctalk. Вне зависимости от того, какой диалект вам кажется наиболее подходящим, среда VEEWS дает вам возможность использовать его для разработки программ для Windows. Новые средства разработки для Windows обязаны своим появле¬ нием стремлению найти альтернативу сухому подходу, воплощен¬ ному в Software Development Kit (SDK). Список этих средств стремительно сужается, если речь заходит об объектно-ориентиро¬ ванной системе, пригодной для создания реальных приложений. Таким средством является VTEWS, все другие находятся далеко позади. В качестве языка программирования выбран Ctalk, один из наиболее удобочитаемых и применимых объектно-ориентиро¬ ванных диалектов Си. На первый взгляд MS Windows кажется объектно-ориентирован¬ ной системой. Различные типы окон поделены на' классы, и все действия производятся с помощью обмена сообщениями. Все ли это, что требуется от объектно-ориентированного окружения? Ра¬ зумеется, нет. Программирующим для Windows доступна лишь малая часть характерных для ООП преимуществ. Отсутствует возможность исключения повторяющегося кода. Используя SDK,
Броузер Си++ системы VIEWS 83 программист получает нечто противоположное. Результирующий код на Си можно считать самим определением избыточности. В чем состоят другие достоинства ООП? Четкое разграничение час¬ тей программы, ведущее к ясному кодированию реализуемых модулей. Напротив, программы для Windows стремятся вытянуть¬ ся в бесконечную цепочку операторов case; отсутствуют надежные способы проверки правильности логики программ. По этим при¬ чинам инструменты типа Ctalk VIEWS должны привлечь внима¬ ние серьезных разработчиков программ для Windows. ■ БРОУЗЕР СИ++ СИСТЕМЫ VIEWS Достаточно одной причины, чтобы сделать VEEWS достойным предметом изучения. Этой причиной служит уникальный среди компилируемых языков броузер, очень похожий на имеющийся в Смолтоке, самом старом объектно-ориентированном языке j6m,ero назначения. Броузеры для разных объектно-ориентированных ди¬ алектов Си, поддерживаемых системой VEEWS, различны. В этой главе я заострю внимание на броузере для Си++. К числу наиболее важных особенностей этого броузера относится большое количество средств управления визуализацией и редак¬ тированием. Он полностью использует интерактивные возможно¬ сти Windows для задания всех мыслимых режимов, которые могут быть использованы при разработке программ на Си++. Когда броузер начинает работу, вы можете обозреть все классы, которые могут иметь отношение к вашему приложению. Меню Classes содержит возможности добавления подкласса к выбранному клас¬ су, удаления подкласса, сохранения и загрузки классов и даже временной выгрузки классов на диск для освобождения памяти. Весьма важно и меню Make. Броузер VffiWS спроектирован для работы с файлами окружения (,ENV). В этих файлах записана иерархия классов, составляющих приложение. Опция AppHcation основного меню служит для за¬ грузки и сохранения файлов окружения. Когда при первом запу¬ ске броузера эти файлы отсутствуют, в основном меню доступны только опции File и AppHcation. Для полного обзора системы классов следует использовать файл ALL.ENV. Как правило, в файлы окружения включают только те классы, которые использу¬ ются данным приложением. Когда VTEWS отображает файлы классов, определенные разделы замещаются выделенными символами $ тегами. Теги соответству¬ ют разделам, которые удаляются и подклеиваются. Меню View содержит набор опций, ограничивающих набор отобра¬ жаемых на панели редактора аспектов функций членов и классов, а также список функций-членов, видимый на предназначенной
84 Объектно-ориентированные расширения Си: языки и инструментальные средства для них панели. При первой загрузке файла .ENV большинство панелей броузера ничего не отображают. Чтобы увидеть что-либо, нужно сначала выбрать класс в иерархии, отображенной в левой верхней панели. В свою очередь, чтобы броузер показал что-либо для выбранного класса, нужно воспользоваться одной из опций Header (Заголовок) или Source ^сходный текст) в меню View. В первом случае будет загружен файл заголовков данного класса, а в другом — станет доступным для редактирования его исходный текст. Оставшиеся функции меню View (Public, Protected, Private, Variables, Methods, Friends и АИ) определяют, какого вида члены будут отображаться на панели членов. Например, выбор одной из опций Variables или Methods ограничит список отображаемых членов соответственно переменными или функциями-членами. До того как будет выбран один из пунктов меню Members, доступ¬ ной является лишь опция Add. После этого станут доступными другие опции. В отличие от опций View эти опции изменяют природу выбранного члена. Например, опция Virtual делает вы¬ бранный член виртуальным. Опции меню Make управляют подготовкой различных типов фай¬ лов, нужных для построения приложения. Это меню поделено на три части. В верхней части задается предмет редактирования, выполняемого на нижней панели: головной файл, файл Make, файл Link или файл определений. Средняя часть предназначена для управления зависимостями и файлами текстовых подстано¬ вок. Их можно просматривать, добавлять и удалять. Третья часть меню Make управляет объектными файлами библиотек классов. Опция Show в этом меню открывает прокручивающийся список библиотечных объектов, указанных в текущем файле окружения. ■ МОДЕЛЬ MVC ПОЛЬЗОВАТЕЛЬСКОГО ИНТЕРФЕЙСА VTEWS использует основанную на популярной парадигме MVC (Model-View-Control — Модель-Взгляд-Управление) модель орга¬ низации интерфейса Windows в виде классов объектов. Эта пара¬ дигма состоит из трех компонентов: модели — уровень приложения, взгляда — уровень визуализации, управления — уровень взаимодействия. MVC основан на стратегии разделения операций программы на три функционирующих независимо модуля: уровень приложения (модель), содержащий основные данные и операции, уровень ви¬ зуализации, делающий доступными несколько точек зрения на уровень приложения, и уровень взаимодействия, который обраба¬ тывает пользовательский ввод и распределяет его между двумя другими уровнями. Это один из классических подходов к созданию
Класс AppView 85 объектно-ориентированного интерфейса с пользователем. Пример использования этой модели — класс Browser. Модель объектов класса Browser содержит иерархию классов и методов, сохранен¬ ную в экземпляре класса Ordered Collection. ■ КЛАСС APPVIEW Фокальным центром VTEWS MVC служит Notifier. Это класс, не имеющий экземпляров и обрабатывающий сообщения исключи¬ тельно при помощи методов класса. Notifier занимается оповеще¬ нием. Он обязан сообщать различным частям программы о произошедших за время работы изменениях. Notifier может обра¬ батывать шесть типов событий: 1. Передвижение и нажатие кнопок мыши. 2. Изменение точки (фокуса) ввода. 3. Ввод с клавиатуры. 4. Открытие окон. 5. Изменение размеров и перемещение окон. 6. Различные изменения окружения. Для каждого из перечисленных типов событий в классе Window имеются соответствующие методы. Notifier посылает сообщение нужного типа окну, в котором произошло событие. Например, если в результате перемещения одного из окон открывается часть другого, которое было им прикрыто, Notifier посылает сообщение этому окну, чтобы оно перерисовало себя в соответствии с возник¬ шей на экране конфигурацией. Понятие фокуса ввода отражает ситуацию, когда некоторое окно или подокно временно начинает собирать (фокусировать) все со¬ бытия, приходящие от клавиатуры. Обычно, после того как окно выбрано, оно подсвечивается и становится фокусом ввода. Окно можно сделать фокусом ввода, послав ему сообщение takeFocus. Кроме обработки событий класс Notifier предоставляет определен¬ ный сервис окнам. Если по какой-либо причине следует сменить форму курсора, это делает Notifier. Достаточно часто в процессе работы пользователь вносит некоторое изменение, которое должно бьггь йемедленно отображено в опре¬ деленном окне. В модели MVC эта ситуация обрабатывается при помощи последовательности сообщений, курсирующих взад и впе¬ ред. Самый верхний элемент изображения обнаруживает измене¬ ние данных и посылает сообщение обновления элементу изображения, ответственному за эти данные. Все эти сообщения сводятся к тому, что изображенная картинка не соответствует действительности. Это приводит к сообщению перерисовки, кото¬ рое, в свою очередь, отправляет сообщение paint требуемому окну.
86 Объектно-ориентированные расширения Си: языки и инструментальные средства Внутри операции перерисовки элемент изображения посылает сообщение модели, это приводит к тому, что отображается самая последняя версия данных. Элементы изображения имеют семь различных типов классов объектов, используемых для построения пользовательских интер¬ фейсов программ. 1. Классы контроллеров, таких, как Notifier. 2. Окна. 3. Управляющие окна и подокна. 4. Элементы изображений. 5. Окна приложений. 6. Всшп шающие окна. 7. Диалоговые окна. Когда в окне происходит событие, оно должно ответить TRUE или FALSE. Ответ TRUE означает, что окно обработает событие, ответ FALSE приводит к тому, что нижележащая оконная система выполнит предопределенное действие. При запуске приложения вначале создаются модель и элементы изображения, а затем классу Notifier посылается сообщение start, и он начинает обрабатывать и диспетчеризовать события. По соображениям переносимости класс Notifier поддерживает только наиболее общий вид мыши, а именно мышь с одной кнопкой. ■ НАЗНАЧЕНИЕ ОКОН В СИСТЕМЕ VIEWS Слова "родительское” и ”дочернее” по отношению к окнам могут показаться странными, но это просто метафора для обозначения зависимости одних частей экранного интерфейса пользователя от других. В MS Windows у каждого окна есть родитель. В системе VlEWS это отражается следующим образом: каждый элемент подклассов Window имеет некий Ш, содержащий идентификатор родительского окна. В родительском окне находится переменная экземпляра Ш объекта класса OrderedCollection, содержащего дескрипторы всех дочерних окон. Все окна VTEWS поделены на две части: область клиента и системную область. Дочерние окна отличаются от настоящих детей, они всегда пере¬ мещаются вместе с родительским окном. Это относится как к движению в плоскости экрана, так и к перемещениям вверх-вниз между разными слоями оконной системы. Если вы накроете окно, вместе с ним вы накроете и его дочернее окно. Если вы выберете дочернее окно, то и непосредственный родитель этого окна окажется выбранным. Когда окно содержит
Класс View 87 одно или более дочерних окон, ему обычно выделяется максимум места. При этом когда пользователь изменяет размер окна, то следует иметь в виду, что окно нельзя сделать слишком маленьким, иначе дочерние окна исчезнут или станут бесполезными. На самом деле окна прявляются на экране благодаря методу show, который, будучи послан родительскому окну? автоматически рас¬ пространяется вниз на его дочерние окна. Классы VTEWS имеют простые методы для вывода текста. Более сложный вывод текста и графики реализован в классе Port. Абстрактный класс ControlWindow предназначен для элементов управления. Объекты потомков ControlWindow предназначены для совместного исполь¬ зования с другими объектами, обычно принадлежащими одному из классов: View, AppView, PopupView или Dialog. Например, класс Button (электронная кнопка), один из наследников класса ControlWindow, содержит переменную экземпляра feAuto, пред¬ ставляющую собой булевскую переменную. Если этой переменной присвоено значение TRUE,- то при нажатии кнопки мыши элект¬ ронная кнопка немедленно изменяет состояние. Класс Button содержит несколько готовых к употреблению под¬ классов: CheckBox, TriState, PushButton и RadioButton. CheckBox предлагает небольшой квадратик, с помощью которого устанавли¬ вается значение типа ”да-нет”. Когда выбрано значение ”да”, в квадратике появляется знак X. Tristate — это, по сути, аналогич¬ ный подкласс, но имеющий третье состояние — отключено (disabled). В этом состоянии кнопка изображается менее интенсив¬ ным цветом. PushButton — это кнопка закругленной формы, на которой написано ее имя. RadioButton представляет собой малень¬ кий кружок, имя которого располагается поблизости. Такие кноп¬ ки часто используются в группах. .■ КЛАСС VIEW Класс View и его наследники предоставляют средства для програм¬ мирования уровня визуализации модели MVC. View — это абст¬ рактный класс, наследник Window, призванный облегчить построение специализированных элементов изображения. View содержит переменную экземпляра model, содержащую идентифи¬ катор модельного объекта приложения. Как правило, объекты класса View посылают сообщения модельным объектам, чтобы извлечь из них данные, которые должны быть отображены. Как подкласс класса Window View и его наследники представляют собой настоящие окна, снабженные линиями прокрутки (scrollbar). AppView — непосредственный подкласс класса View, представляет собой более проработанный базовый класс, предназначенный для
88 Объектно-ориентированные расширения Си: языки и инструментальные средства обзора программы. Как правило, уровень визуализации приложе¬ ния включает объект одного из подклассов этого класса. Другой подкласс View PopupWindow в качестве потомка имеет Dialog. Он представляет собой класс объектов, появляющихся на экране, используемый для запроса пользователя. Их не следует путать с выпадающими меню, которые активизируются пользователем и содержат список возможностей. Имеются несколько подклассов диалогов, представляющих собой готовые к использованию прото¬ типы для разных диалогов. В их числе классы FileSelect, biput, Report, YesNo и YesNoCancel. В среде Windows диалоги отличаются от всех других видов окон тем, что они приостанавливают доступ ко всему остальному окру¬ жению до тех пор, пока им не уделят внимание. Это делает их реализацию более тонким делом. Она часто вызывает трудности у начинающих программировать для Windows. ■ СОЗДАНИЕ ДИАЛОГОВ С ПОМОЩЬЮ ГЕНЕРАТОРОВ ИНТЕРФЕЙСОВ VIEWS содержит упрощенный подход к объектно-ориентирован¬ ному кодированию диалогов. Этот подход подразумевает, что в вашем распоряжении имеется программа DLA.LOG.EXE, входящая в SDK. VIEWS содержит программу Interface Generator (CTIG.EXE), транслирующую результаты редактора диалогов в исходные тексты Ctalk VIEWS. Полученный код требует некоторой доработки перед употреблением. Сгенерированного достаточно только для создания диалога. Вам по-прежнему понадобится на¬ писать методы взаимодействия с элементами интерфейса, а также методы, проверяющие предусловия и постусловия работы с диало¬ гом. К сожалению, CTIG.EXE — не программа Windows, и вам понадобится вызвать оболочку DOS и выполнить эту программу в традиционной манере — из командной строки. ControlView — еще один интересный класс. Он позволяет наслед¬ никам ControlWindow (всевозможным кнопкам, спискам, тексто¬ вым редакторам и т.п.) иметь титулы, изменять размер и перемещаться. Благодаря этому, текстовый редактор, занимаю¬ щий часть родительского окна, может по требованию пользователя менять размер. ■ СОЗДАНИЕ МЕНЮ Система меню VLEWS использует для изготовления пользователь¬ ского выпадающего меню три класса объектов: Menu, PopupMenu и MemiTtem. Класс Menu обеспечивает коллекцию элементов ме¬ ню, определяющую структуру строки меню верхнего уровня. В
Классы редакторов текстов 89 VIEWS объект Menu может в качестве своих элементов иметь только объекты класса PopupMenu. Поскольку экземпляры Menu содержат идентификатор родительского окна, между окном и меню существует двунаправленная связь. Для добавления и иск¬ лючения пунктов меню служат методы append_ и remove_. Хотя можно удалить произвольный элемент меню, добавить элемент можно только в конец» т.е. справа. В КЛАССЫ РЕДАКТОРОВ ТЕКСТОВ Одна из наиболее часто используемых ветвей подклассов ControlWindow имеет следующий вид: ControlView EditBox EditLine TextEditor EditBox — это класс управления редактировайием самого верхнего уровня. Его экземпляр предоставляет базовую область окна для редактирования текста, снабжая ее вертикальной и горизонталь¬ ной линиями прокрутки. Класс EditLine предлагает объекты, используемые при редактировании текстов, состоящих всего из одной строки, например, для ввода имен файлов и директориев. Средства, обычно требуемые от редактора, реализованы в классе TextEditor. Объекты класса TextEditor хранят редактируемый текст в объектах классов String, Stream и FileStream. Экземпляр класса TextEditor содержит буфера редактирования, которые он может использовать для замены объектов String и Stream, когда редактирование завершается. Объекты TextEditor могут также предоставлять готовые выпадающие меню для редактирования текста. Ниже приведена подпрограмма на Ctalk, обрабатывающая файло¬ вый ввод-вывод объектов TextEditor: id myEditor; if (0myEditor is Charrged0) { if 0YesNo ask_ ’’Текст был модифицирован. Сохранить изменения?” of_ self0) } Edit Box и все его наследники содержат методы, управляющие выравниванием текста в своих окнах. Как и всегда в объектно¬ ориентированных системах, программист использует базовые классы в качестве отправной точки для разработки более специа¬ лизированных версий, применимых к задачам конкретной про¬ граммы.
90 Объектно-ориентированные расширения Си: языки и инструментальные средства ■ ТАЙМЕР Класс Timer позволяет объектам всех типов узнавать время воз¬ никновения события. Имеется, разумеется, множество примене¬ ний такого механизма. Наиболее часто он используется для определения скорости работы программы и в качестве часов в программах дискретного моделирования. Создаваемые объекты этого класса могут либо всю свою жизнь через регулярные проме¬ жутки времени посылать сообщения другим объектам, либо ожи¬ дать изменяющих их поведение сообщений от клиентов. Им может быть приказано на время приостановить извещение других объек¬ тов, а затем восстановить режим извещения. ■ КОM МУН И КАЦИОН H Ы Й КЛАСС VIEWS управляет коммуникацией с помощью класса ComPort, содержащего средства как для ввода, так и для вывода в последо¬ вательный порт компьютера. Поддерживается метод опроса и буферизированный ввод-вывод по прерываниям, включая все не¬ обходимое управление потоком данных, аппаратной синхрониза¬ цией и конфигурацией порта. ComPort использует два буфера: один для приема, а другой для передачи сообщений. Выбор разме¬ ра буферов предоставлен программисту. Благодаря этому удается достичь оптимальной для данной пропускной способности произ¬ водительности. Класс ComPort разрабатывался в предположении, что существует другой объект, который знает, как воспользоваться предоставленным сервисом для нужд конкретного приложения. ■ ГРАФИКА Графические возможности VTEWS основаны на объектах класса Port. Имеются методы рисования простых фигур, линий, разнооб¬ разные шрифты и некоторые общие графические преобразования. Класс Port предусматривает переносимость. Написанные графиче¬ ские программы не зависят от аппаратуры, не считая некоторых тонкостей, связанных с соотношениями масштабов по осям X и Y. Метод openOn_ класса Port содержит параметры, определяющие битовую карту и объекты Window и Printer, на которые направлен графический вывод. Каждому сообщению openOn_ должно соот¬ ветствовать сообщение close. Эти два сообщения должны сопутст¬ вовать друг другу как парные скобки. Для лучшего обеспечения переносимости графики класс Port позволяет для управления объектами-перьями использовать виртуальные координаты. Име¬ ются шесть типов линий: сплошная, штриховая, две штрихпунк- тирные, пунктирная и невидимая.
Заключительные замечания о VIEWS 91 Могут использоваться закрашенные фигуры. На выбор предлага¬ ются 10 шаблонов закраски. Объекты Port функционируют в двух режимах: локальном и виртуальном. В виртуальном режиме все операции рисования используют виртуальную систему координат, что делает код машинно-независимым. Его не требуется настраи¬ вать под конкретную аппаратуру. Локальный режим использует систему координат той аппаратуры, на которой в данный момент работает VDSWS. Для переключения между режимами служат методы setVirtOn и setVirtOff. ■ УСКОРИТЕЛИ ПРИЛОЖЕНИЙ Утилита VTEWS Streamliner находится в файле CTSW.EXE. Она запускается из командной строки DOS. Это оптимизатор кода, предназначенный для изготовления меньшего по размеру и более быстрого кода. Он вычищает ”мертвый”, неиспользуемый код и выполняет статическое связывание везде, где можно. Методы и классы, к которым нет обращений во время работы программы, удаляются из исходного текста, а связывание периода выполнения оставляется только для тех процедур, для которых оно абсолютно необходимо. В результате размер кода сокращается до 50%, а скорость выполнения заметно повышается. ■ ЗАКЛЮЧИТЕЛЬНЫЕ ЗАМЕЧАНИЯ О VIEWS Как и в большинстве реализаций объектноориентированных язы¬ ков на микрокомпьютерах, в Ctalk VIEWS отсутствует множест¬ венное наследование. В программном окружении VIEWS нет специальных отладочных средств для работы в терминах объектов. Приходится вести отладку на уровне Си, используя программы типа CodeView. Идея работать с использованием объектов в окру¬ жении Си несколько дискредитирована отсутствием отладчика. Одним из ограничений, связанных со структурой Windows, явля¬ ется невозможность прямого доступа к объектам из языка Си. Из-за сцецифики работы с указателями в Windows на объекты типа Ю нельзя непосредственно ссылаться из обычных программ на Си. Однако, несмотря на этот недостаток, VEEWS представляет собой одно из наиболее завершенных окружений программирова¬ ния для Windows на объектно-ориентированных диалектах Си.
Глава 4 ■ РАЗРАБОТКА ПРОГРАММ ДЛЯ WINDOWS 3.0 В этой главе среда Windows 3.0 рассматривается с точки зрения разработчика, затрагивается ряд проблем, ссылки на которые будут появляться в остальных главах книги. Сюда вошли новые средства Windows 3.0, основы работы с графическим интерфейсом пользователя, пакет Software Development Kit (SDK) фирмы Microsoft, компилятор ресурсов и отладчики. В конце главы приведен краткий список функций, сообщений и структур, поя¬ вившихся в третьей версии Windows. Новая версия Windows стала заметным событием для разработчи¬ ков программного обеспечения персональных компьютеров. Появ¬ ление многочисленных новых приложений, работающих на базе одного интерфейса, служит сильным стимулом для овладения непростым искусством программирования для Windows. В данной главе я опишу пакет SDK и, образно говоря, рассмотрю через увеличительное стекло внутреннюю кухню Windows. Я последова¬ тельно расскажу о различных серьезных вспомогательных про¬ граммах и других средствах, предоставляемых этим пакетом разработчику. И наконец, я покажу процесс конструирования Windows-программ с помощью SDK и мы обсудим некоторые типичные проблемы, с которыми сталкивается программист в среде Windows. ■ ЧТО НОВОГО ПОЯВИЛОСЬ В SDK WINDOWS 3.0? Для тех, кто работал с ранними версиями SDK, мы предоставим обзор изменений и дополнений, вошедших в версию 3.0, остано¬ вимся также и на нововведениях собственно в среде Windows. В Windows 3.0 появились несколько новых видов объектов. К ним относятся: • плавающие меню;
Как работает система Windows 93 • иерархические меню; • определяемые меню Checkmarks. Перечислим три новые программы, включенные в SDK: • Новый графический редактор ресурсов, названный SDKPaint. • Профайлер, который измеряет время выполнения всех про¬ грамм в Windows. • SWAP — анализатор своппинга. Отметим улучшения, сделанные в реализованных ранее програм¬ мах. • Новый вариант редактор диалога позволяет включать в диалоговые окна традиционные средства управления (зада¬ ния параметров), такие, как выбор цвета в палитре или меню регулировок. Улучшены поддержка файлов-заголовков, а также поддержка новых комбинированных окошек (combo boxes). • Редактор шрифтов теперь позволяет преобразовывать шриф¬ ты с переменной шириной в шрифты с постоянной шириной, многие команды упрощены в результате улучшения обрат¬ ной связи. • В новой версии программа HeapWalk позволяет просматри¬ вать память EMS. • CodeView for Windows теперь поддерживает отладку только в защищенном режиме. Повышена скорость работы; предо¬ ставляется возможность слежения за сообщениями и унич¬ тожения сообщений, при этом, однако, предпочтительнее иметь не только обычный монитор, но и дополнительный монохромный монитор с 25-строчным экраном. • Включены новые средства для работы с классами окон, в частности функция UnRegisterClass, которая освобождает память при уничтожении зарегистрированного класса окон, и функция GetClassbifo, которая выдает информацию о заданном классе окон. ■ КАК РАБОТАЕТ СИСТЕМА WINDOWS Постараемся поближе познакомиться с механизмом работы среды Windows. Все было просто, пока мы рассуждали об общих прин¬ ципах. Теперь речь пойдет о деталях. Пересылка сообщений — это то, из чего состоит и на чем основана работа Windows. Но что же
94 Разработка программ для Windows 3. 0 происходит конкретно? Какие сообщения и куда пересылаются? В Windows имеются общая системная очередь и отдельные вход¬ ные очереди к каждому из приложений. Рабочий цикл каждого из приложений получает входную информацию из собственной очереди. Всегда, когда пользователь выполняет какое-нибудь дей¬ ствие: нажимает клавишу, ’’щелкает” или передвигает мышь, программе посылается некоторое сообщение, которое и поступает во входную очередь. Исключением являются ’’внеочередные” со¬ общения. Без постановки во входную очередь можно послать те сообщения, которые воздействуют непосредственно на окно. Такие сообщения обрабатываются без ожидания. Хотя обычно сообщения создаются пользователем или средой Windows, можно описать и приложения, инициирующие сообще¬ ния, которые будут поступать в очереди к другим приложениям. Можно даже написать приложение, которое в процессе диалога с пользователем будет конструировать некоторое сообщение и посы¬ лать его. Ядро любой программы в Windows — это цикл работы с сообще¬ ниями (message loop), который получает входные сообщения из входной очереди и посылает их в нужное окно. Работа любого приложения состоит из цикла приема сообщений (GetMessage). Суть его в следующем: если функция GetMessage не находит какого-нибудь сообщения в своей очереди, она передает управле¬ ние среде Wiridows. Как только вы выбрали некоторое окно приложенйя, сообщение посылается и функция получает то, что искала. Приложение получает входную информацию, а GetMessage продолжает слежение. ■ ФУНКЦИИ, КОТОРЫЕ СОЗДАЮТ ОБЪЕКТЫ Вероятно, важнее всего программисту знать о сообщениях, которые позволяют создавать объекты. Не удивительно, что в Windows важнейшим из создаваемых объектов является само окно! В табл. 4.1 приведены список основных действий по созданию, унич¬ тожению и модификации окон и список операций, которые выпол¬ няют эти действия. ■ КЛАССЫ ОКОН Прежде чем идти дальше, имеет смысл уточнить терминологию. Будем использовать термин ’’класс окон MS” (MS — MicroSoft) для обозначения классов окон в системе MS-Windows и отличать его от термина ’’класс” как такового, который обозначает некоторую структуру в системе объектно-ориентированного программирова¬ ния (ООП). Так, например, если в некоторой системе ООП имеется клэсс под названием Window, то, когда я буду говорить ’’класс
Классы окон 95 Таблица 4.1. Функции Windows для создания и модификации окон Функция CreateWindow CreateW indowEx DestroyWindow DefWindowsProc AdjustW indowRect AdjustW indowsRectEx GetWindowLong GetWindowWord SetWindowLong SetW indowW ord DefDlgProc DefFrameProc DefMDIChildProc GetClassInfo GetClassLong GetClassName GetClassWord RegisterClass SetClassLong SetClassWord UnregisterClass Назначение Для создания частично перекрывающихся, выпадающих и дочерних меню Для создания окон в расширенном стиле Удаляет окно из системы Для обработки сообщений окон, которые необработаны приложениями Для задания размеров окна в соответствии с размерами области, определенной пользователем Задает размеры окна по указанию пользователя в расширенном стиле Для получения информации об окне Для получения информации об окне (альтернативный вариант) Модифицирует атрибуты окна Модифицирует атрибуты окна Для обработки сообщений диалога, ые обработанных приложениями Для обработки фреймов MDI (Multi-Document Interface) сообщений, не обработанных приложениями Для обработки дочерних сообщений диалога, не обработанных приложениями Для получения информации о классе Для получения информации о структуре WNDCLASS Получает имя класса окна Для получения информации о структуре WNDCLASS Для регистрации класса окон Модифицирует описатель класса окон Модифицирует описатель класса окон Удаляет класс окон из системы Window”, я никогда не буду иметь в виду низкоуровневый ”мик~ рософтовский” класс окон. Это позволит избавиться от двусмыс¬ ленности, возникающей из-за двух значений, которые может иметь в контексте книги слово ”класс”. В настоящее время имеются три типа встроенных Windows-клао- сов: системные глобальные, глобальные классы приложений и
96 Разработка программ для Windows 3. 0 Таблица 4.2. Структура класса Window в Windows Структура класса Назначение Имя класса Однозначно идентифицирует класс окон Адрес процедуры окна Указатель на функцию, обрабатывающую все сообщения, получаемые данным классом Имя приложения, зарегистрировавшего данный экземпляр класса Определяет форму курсора в окнах класса Пиктограмма, используемая, когда окно класса закрыто Определяет цвет и растр фона окна Меню по умолчанию для класса окон Определяют распределение памяти, способ изменения и другие характеристики для класса Определяет дополнительную память, которую необходимо разместить в структуре класса Определяет дополнительную память, которую необходимо разместить в существующих окнах локальные классы приложений. Глобальные системные классы доступны всегда и всем приложениям. Они создаются при старте Windows. В табл. 4.2 даны сведения об основных составляющих структуры класса. ■ СООБЩЕНИЯ Входное сообщение в Windows содержит информацию о: систем¬ ном времени; позиции мыши; нажатии кнопки мыши; состоянии клавиатуры; skan-кодах нажатых клавишей; об устройстве, кото¬ рое возбудило сообщение. Различают пять видов сообщений: 1) оконные сообщения; 2) сообщения ’’кнопок управления”; 3) сообщения для комбинированных окон; 4) управляющие сообщения редактора; 5) сообщения списка окон. Эти виды сообщений предоставляют эффективный внутренний механизм, который дает возможность реализовать много тонких деталей пользовательского интерфейса автоматически, без много¬ Handle (дескриптор) Курсор класса Пиктограмма (icon) Кисть фона класса Меню класса Стили класса Дополнение класса Дополнение окна
Функции окон 97 словного выписывания таких деталей в каждом из приложений. Обычно эти сообщения переносят как сведения о типе события, так и количественную информацию, которая характеризует неко¬ торое точное значение. Так же как функции Windows предостав¬ ляют пользователю интерфейс для того, чтобы определить дальнейшие действия, сообщения Windows аналогичным образом указывают, какие события должны наступить. Система Windows предусматривает также механизм предотвраще¬ ния дедлока (взаимной блокировки) сообщений. Чтобы понять, как работает этот механизм, вначале необходимо понять, из-за чего возникают дедлоки. Дедлок может возникнуть, если одна из задач программы воспользовалась функцией SendMessage для передачи сообщения другой задаче и вынуждена ждать возврата из этой функции, поскольку управление передано другой задаче. Но это еще не дедлок. Последний возникнет, если вторая задача должна передать управление первой. В этом случае каждая из задач требует, чтобы инициатива исходила от другой, и ни одна из задач не может продолжить выполнение. На практике дедлок встреча¬ ется и в других случаях, особенно тогда, когда задача, которой передано управление, не содержит специальнькс средств для пред¬ отвращения подобных ситуаций. ■ ФУНКЦИИ окон Интерфейс разработки программ program Development biterface PDQ Windows содержит большое количество функций. С одним управлением окнами связано не менее 18 различных функцио¬ нальных групп. Оконные функции обычно используются при определении стилей окна. Последние представляют собой заранее заготовленные компоненты, которые могут быть собраны в различ¬ ных комбинациях, для получения различных типов окон. Окон¬ ные стили служат для формирования параметров функции CreateWindow, вызываемой для создания окна. В стандартном окружении Windows используются 4 основные стиля: 1) перекры¬ вающиеся окна, 2) окна с владельцем, 3) всплывающие окна, 4) дочерние окна (табл. 4.3). В табл. 4.4 перечислены функции управления клавишами. Таблица 4.3. Названия стилей окон Стилъ окна Сообщение Перекрывающиеся окна WS_OVERLAPPED Окна с владельцем WS_OVERLAPPED с hWindParent в качестве владельца Выпадающие окна WS_POPUP Дочерние окна WS_CHELD 4-857
98 Разработка программ для Windows 3.0 Таблица 4.4. Группы функций для управления окнами !Функции сообщений 2.Функции создания окна 3.Функции изображения и движения 4Функции ввода б.Аппаратные функции 6.Функции рисования 7.Функции организации диалога Б.Функции прокрутки 9.Функции меню Ю.Информационные функции Н.Системные функции 12.Функции стеллажа (clipboard) 13.Функции ошибок 14.Функции вставки 15.Функции курсора 16.Функции-крючки 17.Функции собственности 18.Функции прямоугольника ■ КОНТЕКСТ ДИСПЛЕЯ Система контекста дисплея — это средство управления дисплеем компьютера, одновременно разделяемым несколькими приложе¬ ниями Windows. Windows работает с четырьмя типами контек¬ стов: общим, классом, приватным и окном. Последний контекст поддерживает запись данных в любую точку окна. Остальные позволяют писать только в область клиента окна. Тип дисплейного контекста получает значение исходя из спецификации стиля клас¬ са окна при его создании. ■ ЭЛЕМЕНТЫ ОКОННЫХ ПОЛЬЗОВАТЕЛЬСКИХ ИНТЕРФЕЙСОВ В готовых интерфейсах пользователя Windows обычно присутст¬ вуют многочисленные средства управления, каждое из которых обладает уникальным идентификационным номером. Когда поль¬ зователь воздействует на эти средства, сообщения WM_COMMAND, циркулирующие между окнами и средствами управления, переносят эти номера. Каждый пользовательский интерфейс в системе Windows состоит из подобного цикла управ¬ ления, который по желанию пользователя может быть расширен кодом, определяющим реакцию на каждый поступивший иденти¬ фикационный номер. Например, параметры WS_TABSTOP и
Пиктограммы 99 WS_GROUP устанавливают стиль управления для групп электрон¬ ных кнопок и диалоговых полей таким образом, чтобы фокус управления перемещался с помощью соответственно клавиш табуляции или стрелок. ■ УПРАВЛЯЮЩИЕ ЭЛЕМЕНТЫ, ПЕРЕРИСОВЫВАЕМЫЕ ВЛАДЕЛЬЦЕМ Обычно система Windows сама отвечает за перерисовку различных типов управляющих элементов. Однако начиная с версии 3.0 появилась возможность приписать элементу стиль ”риеуемый вла¬ дельцем”. Это означает, что у управляющего элемента может быть тип, ответственность за перерисовку которого берет на себя владе¬ лец. Основное преимущество этого состоит в том, что адаптирован¬ ные данные могут быть запомнены в структурах, допускающих определенные вариации в стиле элемента. Вместо стандартной автоматической перерисовки элемента эта работа поручается ок- ну-владельцу, имеющему доступ к информации о способе рисова¬ ния управляющего элемента. ■ ПИКТОГРАММЫ В Windows 3.0 пиктограммы f[cons) играют более заметную роль, чем в предыдущих версиях системы. Пиктограммы создаются с помощью специальных файлов битовых карт и операторов в запи¬ сях ресурсов, которые позволяют добраться до этих файлов. Фай¬ лы пиктограмм Windows обычно содержат целый набор различных пиктограмм, настроенных на различные наборы цветов и степени разрешения. Выбор наиболее подходящей к имеющемуся дисплею оставлен системе Windows. Встроенные пиктограммы, поставляе¬ мые с системой Windows, могут быть использованы любой при¬ кладной программой. В число встроенных входят восклицательный знак, вопросительный знак, звездочка и знак ”стоп”. Обычно они используются при построении диалога. Однако пиктограммы имеют более широкое применение. Часто удобно создать пиктограмму класса. Она будет использова¬ на, когда объект этого класса сжимается, ”коллапсирует” на экране. Если для класса окон определена пиктограмма, Windows автоматически отображает ее всякий раз, когда соответствующие окна сжимаются. Программисту, создающему приложение, не нужно заботиться об этом. Для создания новых пиктограмм можно воспользоваться утилитой SDKPaint из пакета SDK, а соответст¬ вующие декларации ввести с помощью компилятора ресурсов.
100 Разработка программ для Windows 3. О Я МЕНЮ Наиболее популярны текстовые меню. Однако графические интер¬ фейсы пользователя часто содержат и графические меню, исполь¬ зующие пиктограммы, например, в программах рисования. Подобно другим средствам управления в Windows каждое меню имеет уникальный идентификационный номер, определяемый программистом. Когда пользователь выбирает пункт меню, его идентификационный номер передается вместе с сообщением Windows, уведомляющим приложение о событии. Набор инструк¬ ций, выполняемых для каждого из пунктов меню, определяется приложением. Хорошая практика программирования отключает (и делает ”серыми”) опции меню, использование которых в данный момент неуместно. Это должно быть сделано тогда, когда опцию меню невозможно или вредно активизировать. Отключение не следует использовать в ситуациях, когда имеется хотя бы одна возможность использовать опцию. Если есть сомнения, пункт меню не следует отключать. В табл. 4.5 перечислены функции меню. Таблица 4.5. Функции меню Windows SetMenu(hW nd,hMenu) AppendMeriu bisert*Menu GetSubMenu ModifyMenu(hMenu^iPosition,wFlags,wroNewItem,lpNewItem) SetMenuItemBitmaps ■ УПРАВЛЯЮЩИЕЭЛЕМЕНТЫ В Windows управляющие элементы представляют собой дочерние окна, посылающие в ответ на действия пользователя извещающие сообщения, которые, в свою очередь, могут быть проверены про¬ граммой и связаны с нужными процедурами. Следует тут же заметить, что существует исключение: статические управляющие элементы. Последние просто показывают определенный текст, не воспринимая пользовательского ввода и не генерируя сообщений. На уровне Windows управляющие элементы создаются функцией CreateWindow, которой передаются соответствующие параметры. В среде Windows для оповещения окон о состоянии управляющих элементов служит сообщение WM_COMMAND. У этого сообщения два параметра: wp и lp. Параметр wp сообщает Ш управляющего элемента, а lp содержит информацию о типе управляющего эле¬ мента: меню, ключ ускорения доступа или какой-либо другой. В следующих разделах описаны типы управляющих элементов.
Диалоги 101 ■ КНОПКИ Как и другие управляющие элементы, кнопки — это отдельные дочерние окна, с которыми работают, как с настоящими кнопками на аппаратуре. В принципе, они могут использоваться для запуска любой команды или сообщения и иметь произвольный размер. К стандартным относятся следующие типы кнопок: pushbutton, checkbox и radio button. В зависимости от стиля кнопки текстовая метка располагается либо на самой кнопке, либо справа от нее. Для того чтобы реагировать на мышь, кнопки не обязаны фоку¬ сировать ввод. Это требуется лишь при вводе с клавиатуры. Кнопки radio button используются, когда требуется выбрать один вариант из нескольких. Для этого они объединяются в группы, которые представляют собой прямоугольники, объединяющие два или более функционально связанных между собой элемента управ¬ ления. В группы могут объединяться и кнопки checkbox. Сами группы не взаимодействуют с пользователем и не генерируют никаких извещающих сообщений. ■ SCROLL BARS (ЛИНИИ ПРОКРУТКИ) Теоретически линии прокрутки могут располагаться в любой части окна. Они особенно удобны, когда пользователь должен выбирать значение из непрерывного спектра. При передвижении (с по¬ мощью мыши) индикатора вдоль линии прокрутки посылаются сообщения, которые изменяют значения в программе и внешний вид линии прокрутки. Имеются два различных типа линий про¬ крутки: линии, служащие частью границы окна, и независимые линии, которые могут быть никак не связаны с прокруткой текста. В случае независимых линий прокрутки приложение самостоя¬ тельно определяет, что должно происходить при перемещении индикатора. Windows содержит функцию SetScrollRange, задаю¬ щую диапазон значений, выбираемых при помощи линии прокрут¬ ки. За интерпретацию посылаемых значений отвечает прикладная программа. ■ ДИАЛОГИ Диалоги и меню — это наиболее важные элементы пользователь¬ ского интерфейса Windows. Типичным результатом выбора опции меню будет открытие диалогового окна. Диалоги, или диалоговые окна, — это всплывающие окна, используемые, как правило, для краткого взаимодействия с пользователем. Они обычно запраши¬ вают у пользователя дополнительную информацию или значения каких-нибудь режимов. Могут объявляться как модальные, так и немодальные диалоги. В немодальном в отличие от модального режима родительское окно не отключается. Пример такого диало¬
102 Разработка программ для Windows 3.0 га имеется в программе Write: выполнение команды Find откры¬ вает диалоговое окно, но пользователю разрешается продолжать редактирование. Подобное средство применимо для программ про¬ верки орфографии и других сходных вспомогательных инструмен¬ тов. Немного подумав, вы поймете, что между использованием клавиш в модальных и немодальных диалогах имеется существенная разница. В немодальных диалогах все определенные ранее клави¬ ши должны сохранят^ прежний смысл, в то время как в модаль¬ ных назначением этих клавиш можно управлять. Windows предопределяет назначение нескольких клавиш для диалоговых окон. Функции Windows для обеспечения диалогов приведены в табл. 4.6. Таблица 4.6. Диалоговые функции Windows DialogBox CreateDialog EndDialog IsDialogMessage GetDlgItem SendDlgMessage SetDlgItemText CreateDialogParam ■ ОКОШКИ СПИСКОВ Окошки списков делятся на две категории. Одни позволяют вы¬ брать лишь один элемент, другие — произвольное число элемен¬ тов. Имеется возможность строить списочные окошки, состоящие из нескольких колонок. В окошках, позволяющих выбрать только один элемент, выбор нового элемента автоматически подавляет выбор предшественника. Окошки списков с множественным вы¬ бором позволяют подсветить сразу несколько элементов. В окош¬ ках, состоящих из нескольких колонок, если число элементов превышает высоту окошка, при прокрутке элементы переносятся из одной колонки в другую. ■ КОМБИНИРОВАННЫЕ ОКОШКИ Комбинированные окошки представляют собой расширения око¬ шек списков. Они допускают редактирование. Имеются три основ¬ ных стиля комбинированных окошек: простые, выпадающие и выпадающие с поиском. В простом комбинированном окошке ниже поля редактирования всегда находится окошко списка.
Интерфейс с графическим устройством 103 Когда комбинированное окошко содержит фокус» в поле редакти¬ рования можно писать. При использовании стиля "выпадающий” список первоначально не виден. Визуально можно наблюдать только поле редактирования и маленькую стрелку справа от него. Если с помощью мыши выбрать строку, то ”выдадает” списковое окошко. Стиль "выпадающий с поиском” полностью совпадает с предыдущим, за исключением того, что пользователь может вы¬ бирать элементы из списка, вводя их первые буквы в поле редак¬ тирования» ■ ВСПЛЫВАЮЩИЕ МЕНЮ В новой версии Windows появилась возможность создавать меню, которые не привязаны к определенной позиции верхней строки меню, а ”всплывают” при нажатии кнопки на многокнопочной мыши. Это существенно для интерфейса с пользователем, посколь¬ ку помогает ему сберечь время. Более не требуется постоянно подтаскивать курсор мыши вверх к строке меню. Меню можно воспользоваться в том месте, где вы оказались в процессе работы. ■ ИНТЕРФЕЙС С ГРАФИЧЕСКИМ УСТРОЙСТВОМ Интерфейс с графическим устройством (GD1 — graphics device interface) предназначен для обеспечения кода, не зависящего от характеристик дисплея, предоставляя осуществлять трансляцию на каждый тип аппаратуры драйверу устройства. Независимость достигается за счет вывода на виртуальное логическое простран¬ ство, а затем отображения его на дисплеи, принтеры, плоттеры и т.п. Для этого может быть установлен один из восьми режимов отображения. Шесть из восьми режимов ограниченные". Режим Isotropic — частично ограниченный, а режим Anisotropic — неог¬ раниченный. В ограниченных режимах логические единицы ото¬ бражены на реальные единицы длияы. Например, в режиме Low English логические единицы соответствуют 0.1 дюйма. В неогра¬ ниченных режимах окно и соответствующий порт вывода самосто¬ ятельно вычисляют коэффициенты растяжения. В табл. 4.7 перечислены режимы отображения. Windows GDI поддерживает вывод графики, подготовленной при помощи битовых карт, кистей и перьев. В GDI предопределены 3 вида перьев: Black (черное), Null и White (белое). Они могут использоваться функцией GetStockObject. Предопределено также 7 кистей (Brush): Black, Dark-Gray, Gray, Hollow, Light-Gray, Null и White. Имеются шесть шаблонов закраски. GDI поддерживает 12 стилей форматирования текста.
104 Разработка программ для Windows 3. О Таблица 4.7. Режимы отображения GDI Режим отпображеяия Единица измерения Anisotropic Произвольная High English 1000 дюймов High Metric 100 миллиметров Low English 100 дюймов Low Metric 10 миллиметров Text 1 пиксел устройства Twips 1440 дюймов Контексты устройства и информационные контексты Контекст устройства Windows — это определенная связь между прикладной программой, драйвером устройства и самим устройст¬ вом вывода, скажем лазерным принтером. Его можно также рассматривать как цепочку вывода. Информационный контекст — это контекст устройства исключая возможность вывода. Имеются всего 17 различных функций Windows GDI. Они пере¬ числены в табл. 4.8. Метафайлы — это файлы, которые хранят не графические образы, а создающие их команды. Функции работы с метафайлами Windows перечислены в табл. 4.9. Они обладают значительной Таблица 4.8. Категории функций GDI 1. Функции контекста устройства 2. Функции рисования 3. Функции палитры цветов 4. Функции атрибутов рисования 5. Функции отображения 6. Функции работы с координатами 7. Функции регионов 8. Функции клиппирования 9. Функции вывода линий 10. Функции рисования эллипсов и многоугольников 11. Функции работы с битовыми картами 12. Функции работы с текстом 13. Функции работы со шрифтами 14. Функции работы с метафайлами 15. Функции управления принтером 16. Функции ESC-кодов принтера 17. Функции опроса среды
Интерфейс со многими документами (MDf — Multi-Document Interface) 105 Таблица 4.9. Функции работы с метафайлами CreateMetaFile CopyMetaFile GetMetaFile DeleteMetaFile EnumMetaFile PlayMetaFileRecord AnimatePalette SetDlBitsToDevice гибкостью и не сводятся к простой записи последовательности действий при создании изображения. Среда Windows разрабатывалась в расчете на ситуации, когда число требуемых цветов превышает возможности дисплея. Это может произойти, когда одну и ту же логическую палитру исполь¬ зуют несколько приложений. ■ ИНТЕРФЕЙС СО МНОГИМИ ДОКУМЕНТАМИ (MDI - MULTI-DOCUMENT INTERFACE) Интерфейс MDI присутствовал и в более ранних версиях Windows. Как следует из названия, это стандарт, позволяющий приложению манипулировать и управлять более чем одним документом одно¬ временно. Это сделано так: каждый документ располагается в отдельном дочернем окне, а управление по-прежнему сосредотачи¬ вается в единственном родительском окне. В многодокументных приложениях основное окно служит в каче¬ стве рамки. Использование этого интерфейса позволяет ограни¬ читься весьма небольшим количеством специализированного кода и данных. Среда Windows предоставляет стандартные средства управления. Первый шаг в создании прикладной программы с использованием MDI — описание фазы инициализации. Вы должны зарегистриро¬ вать два класса окон: один для рамки, а другой для окна докумен¬ та. По сравнению с обычным дочерним окном у регистрационной структуры для класса окна документа имеется ряд отличий. Для него обязательно нужна пиктограмма, чтобы дать пользователю возможность минимизировать дочернее окно тем же способом, что и основное. У этого окна должно быть пустое имя, поскольку такие окна не могут иметь собственных меню. Кроме этого должно резервироваться дополнительное место в памяти для запоминания имени файла, ассоциированного с каждым документом.
106 Разработка программ дпя Windows 3. 0 ■ ДИНАМИЧЕСКИ ПОДКЛЮЧАЕМЫЕ БИБЛИОТЕКИ (DLL - DYNAMIC LINK LIBRARIES) При переходе на версию 3.0 необходимо обработать все ранее созданные динамически подключаемые библиотеки с помощью компилятора ресурсов. Это нужно хотя бы для того, чтобы поста¬ вить штамп: ”версия 3.0”. Необходимо предотвратить появление при начале работы сообщения, предупреждающего пользователя о возможной несовместимости. Наличие штампа версии означает, что разработчики учли условия нового окружения Windows. Ди¬ намически подключаемые библиотеки, начиная с версии 3.0, должны вместо расширения .EXE, характерного для более ранних версий, иметь расширение .DLL. Так сделано, чтобы пользователи не путали библиотеки с непосредственно выполнимыми приложе¬ ниями Windows. Для программистов работа с новыми библиотека¬ ми проще, поскольку вместо указания входных точек вручную на машинном языке можно воспользоваться стандартной функцией установки точек входа. DLL требуют процедур инициализации и завершения Версия 3.0 динамически подгружаемых библиотек требует выпол¬ нения процедуры выхода Windows с именем WEP. Это сообщение посылается^, чтобы уведомить библиотеки, что они выгружаются. Можйо проектировать приватные DLL, позволяющие перемещать себя в область верхних адресов памяти, оставляя пространство основной памяти для других нужд. Для совместимости с Windows 3.0 требуется обработка DLL ком¬ пилятором ресурсов. Одной из неудач следует признать опцию -p, определяющую DDL как приватную. Она противоречит самой идее библиотек DLL. Каждый, кто помечает свою библиотеку как приватную, получает избыточный код и теряет память. Кроме этого те, кто не "приватизирует” свои DLL, имеют существенные преимущества на рынке. ■ ДИНАМИЧЕСКИЙ ОБМЕН ДАННЫМИ (DDE - DYNAMIC DATA EXCHANGE) DDE — один из нескольких способов обмена данными между приложениями Windows. Основным здесь является протокол со¬ общений, позволяющий пересылать данные, как непосредственно используя для этого сообщения DDE, так и передавая дескрипторы блоков разделяемой глобальной памяти. Приведем типичные при- ложения, в которых может быть использован динамический обмен данными.
Компилятор ресурсов 107 1. Связь с работающими в реальном режиме времени источ¬ никами данных, такими, как датчики управления процес¬ сами, биржевые сводки и т.д. 2. Использование составных документов, т.е. документов, со¬ стоящих из нескольких файлов. 3. Связь по данным между базой данных и электронной таб¬ лицей. 4. Информационный обмен между разными компьютерами, работающими под управлением Windows. ■ МАШИННО-НЕЗАВИСИМАЯ ЦВЕТНАЯ ГРАФИКА В отличие от монохромных дисплеев в цветных битовых картах отсутствует взаимноюднозначное соответствие между битами па¬ мяти и пикселами. Способ представления цвета в памяти сущест¬ венно зависит от адаптера дисплея и его возможностей. Начиная с версии Windows 3.0, функции, управляющие побитовыми изо¬ бражениями, делают это способом, не зависящим от используемой аппаратуры. Новые форматы пиктограмм и курсоров выполнены в виде наборов аппаратно-независимых битовых карт. Подробнее об этом будет рассказано при описании инструмента SDKPaint. ■ СИСТЕМА ПОСТРОЕНИЯ СПРАВОЧНОЙ ИНФОРМАЦИИ (HELP) Одним из наиболее существенных дополнений к Windows стала система построения справочной информации. Соответствующий инструмент SDK состоит из двух основных частей: Help engine и компилятор Help. Help engine входит в состав поставки Windows и доступен всем пользователям. Он используется для визуализа¬ ции файлов Help в процессе работы. Изготовление файлов Help происходит в несколько этапов. Сначала необходимо написать текст, который должен появиться в системе Help. Для этого можно воспользоваться любым редактором, который готовит файлы в формате ASCDL Затем в текст вставляются управляющие символы, используемые компилятором Help. ■ КОМПИЛЯТОР РЕСУРСОВ Этот инструмент используется для создания ресурсов Windows, т.е всевозможных меню, курсоров, пиктограмм и диалоговых окон, используемых приложением. Компилятор использует описашш ресурсов из файлов с расширением .RC, которые содержат имена и описания всех требуемых приложением ресурсов. Он компили¬ рует их в файл .RES, склеивает их с файлом .EXE приложения и ставит затем штамп версии Windows. Перед тем как воспользо¬
108 Разработка программ для Windows 3.0 ваться компилятором ресурсов, необходимо создать все нужные для данного приложения файлы пиктограмм, курсоров, шрифтов, битовых карт и диалогов. После чего они описываются в файле ресурсов. В описаниях таких ресурсов, как шрифты, курсоры и диалоги, которые определяются в своих собственных файлах, содержатся только имена ресурсов и ссылки на содержащие их файлы. Однако в случае меню скрипт должен содержать полное описание. Как только описание составлено, вы компилируете его при помощи компилятора ресурсов (RC.EXE). Эта программа может быть вы¬ звана несколькими разными способами. В последующих главах будут даны примеры различных способов использования этого инструмента. Вариант Windows для разработчиков Вариант окружения Windows, предназначенный для отладки про¬ грамм, проверяет корректность дескрипторов окон, передаваемых функциям Windows. В отладочном режиме доступна символьная информация, содержащаяся в составляющих Windows динамиче¬ ски подключаемых библиотеках. Это облегчает диагностику оши¬ бок с помощью отладчика CodeView for Windows. Отладочный вариант Windows позволяет наилучшим образом использовать программу HeapW alker. Установка отладочного варианта Отладочный вариант Windows состоит из альтернативных версий основных DLL системы Windows: KRNL286.EXE, KRNL386.EXE, GDLEXE, и USER.EXE, которые продаются вместе с SDK и обычно располагаются в директории WTNDEV\DEBUG. Чтобы запустить Windows в отладочном режиме, эти библиотеки и соответствующие им символьные файлы должны быть скопированы в директорий WDTOOWS\SYSTEM. Прежде чем сделать это следует переимено¬ вать рабочие версии этих DLL или скопировать их для безопасно¬ сти в другой директорий. ■ РЕДАКТОРЫ РЕСУРСОВ И ИНСТРУМЕНТЫ При создании программ для Windows должен гарантироваться контроль за разделяемыми ресурсами со стороны окружения. Одним из таких ресурсов является память. Дисплей также явля¬ ется разделяемым ресурсом. Не следует путать ресурсы компью¬ тера и ресурсы Windows. Ресурсы Windows — это реальные программы, видимые на экране. В следующих разделах я рассмот¬ рю различные инструменты, предусмотренные в SDK для помощи в создании ресурсов Windows.
Утилита SDKPaint 109 ■ РЕДАКТОР ДИАЛОГОВ Этот инструмент позволяет быстро создавать диалоги. Вместо определения операторов диалога в описаниях ресурсов редактор диалогов дает возможность проектировать и проверять элементы управления на экране в интерактивном режиме. Спецификации диалогов выводятся в виде файлов .DLG и .RES, используемых компилятором ресурсов. Редактор диалогов создает также файлы заголовков с операторами #define, определяющими константы, которые идентифицируют управляющие элементы. Последняя ре¬ ализация редактора позволяет строить и поддерживать каталоги специализированных управляющих элементов для диалогов. Редактор диалогов содержит альтернативный интерфейс пользо¬ вателя, называемый Toolbox, работающий по принципу графиче¬ ского редактора и предоставляющий те же возможности, что и система меню. Над управляющим элементом можно выполнять следующие действия: движение, изменение размеров, удаление, редактирование текста, изменение стиля управляющего элемента и его идентификатора. Вы можете рассматривать группы управля¬ ющих элементов и определять порядок перемещения фокуса ввода. Порядок,используемый по умолчанию,совпадает с последовательно¬ стью создания элементов и не зависит от их расположения. ■ УТИЛИТА SDKPAINT Появившаяся в версии 3.0 утилита SDKPaint является очень гибкой графической программой, способной создавать и модифи¬ цировать объекты трех типов: битовые карты, пиктограммы и курсоры. Им соответствуют три типа файлов: .BMP, .ICO и .CUR, которые могут включаться в описание ресурсов и компилироваться в соответствующий файл .RES. Могут создаваться файлы как цветных, так и монохромных аппаратно-независимых битовых карт. Каждая битовая карта содержит уникальный графический образ. Пиктограммы и курсоры представляют собой наборы кар¬ тинок, каждый из которых разработан для визуализации опреде¬ ленного объекта Windows. При открытии файлы битовых карт, созданные для версий 2.X, автоматически преобразуются к формату Windows 3.0. Из-за того, что файлы пиктограмм и курсоров содержат несколько изображе¬ ний, вы вначале открываете файл, а затем загружаете интересую¬ щее вас изображение. Для этого служит отдельное меню. В открытые файлы пиктограмм и курсоров можно добавлять новые изображения. Для их создания служат многочисленные инстру¬ менты, изображенные в нижней части окна SDKPaint.
110 Разработка программ для Windows 3. О ■ РЕДАКТОР ШРИФТОВ FONTEDIT Программа FontEdit помогает создавать новые шрифты и модифи¬ цировать имеющиеся. Она работает только с растровыми экранны¬ ми шрифтами. Окно редактирования представляет собой увеличенную сетку, содержащую букву загруженного в редактор шрифта. Редактирование состоит в закраске и удалении отдельных пикселов. Единственным способом создания нового шрифта явля¬ ется модификация существующего. С этой целью в SDK имеются образцы шрифтов. Когда шрифт загружается, вы видите букву А в двух видах: обычном и разбитом на клетки. При этом каждый пиксел представлен небольшим прямоугольником. Можно перемещаться вдоль всего множества символов данной гарнитуры, содержащего весь алфавит, включая заглавные и строчные буквы. Кроме ввода и удаления отдельных пикселов редактор шрифтов позволяет работать с целыми рядами и колон¬ ками, а также выполнять мощные операции для разных видов штриховки. Есть команды, изменяющие ширину букв. Изменение высоты букв возможно только при выборе общей высоты всего шрифта. ■ ЛУПА Zoomin — это специальное окно, в котором отображается прямо¬ угольный фрагмент экрана ^?uc. 4.1). Масштаб изображения может изменяться вслед за перемещением индикатора линии прокрутки. Если вы нажмете кнопку мыши внутри окна и подвинете ее, держа кнопку нажатой, то сможете переместить рамку прямоугольника лупы по экрану. Внутри окна будет видна захватываемая лупой часть экрана. Если в рамку лупы попадет часть границы окна Zoomin, вы увидите картину бесконечного отражения в зеркалах. ■ ПРОФАЙЛЕР Профайлер (утилита построения профиля программы) состоит из трех частей: 1) утилиты случайной выборки, 2) утилиты построе¬ ния отчета и 3) набора вызываемых из приложений функций. При работе программы утилита случайной выборки запоминает инфор¬ мацию о времени, затраченном между двумя соседними метками, и адресах кода. Предусмотрены два варианта утилиты случайной выборки: для реального и для защищенного режимов. В реальном режиме используется PROF.COM, а в защищенном эта утилита требует дополнительного драйвера VPROD. 386. В результате работы этой утилиты создаются два отдельных файла: один содер¬ жит выборку значений регистра DP, а другой содержит информа¬ цию о перемещениях сегментов кода. После того как сбор
Spy (мониторинг сообщений) 111 информации будет завершен, можно для отображения результатов запустить программу SHOWHrTS.EXE. Вы можете указать профайлеру, где начинать и где заканчивать сбор информации, включив в код обращения к функциям ProfStart и ProfStop. Функцию ProfStop не следует путать с функцией ProfFinish, которая необходима для сбросов результатов профайлера на диск без выхода из Windows. Как только информа¬ ция сброшена, может запускаться программа SHOWHTTS для подготовки отчета. ■ SPY (МОНИТОРИНГ СООБЩЕНИЙ) Программа Spy (шпион) — удобное средство отладки, позволяющее разобраться, как работает система сообщений Windows (puc.4.2). Эта очень простая в работе программа осуществляет мониторинг сообщений, направленных определенному окну. Она вызывается, как обычная программа Windows. Войдя в нее, следует выбрать команду Set Window и указать интересующее окно, нажав клави¬ шу мыши. Начиная с этого момента поток сообщений этому окну начнет отображаться в окне программы Spy. Даже простое пере¬ движение мыши в выбранном окне сопровождается большим числом сообщений WM_SETCURSOR и WM_MOUSEMOVE. В ме- Рис. 4.1. Утилита Zoomin пакета SDK
112 Разработка программ для Windows 3. 0 Рис. 4.2. Показан диалог Options утилиты Spy пакета SDK ню Options с помощью крестиков в квадратиках cheehbox можно задать фильтр, выделяющий только интересующие нас сообщения. Вы можете отобрать тип отслеживаемых сообщений. Кроме этого можно задать, интересуетесь ли вы только входящими сообщени¬ ями, только исходящими или и теми и другими вместе. ■ HEAPWALKER (МОНИТОРИНГ КУЧИ) Это очень важное средство (puc. 4.3), поскольку позволяет отсле¬ живать состояние системной памяти Windows — кучи. Она рабо¬ тает как в реальном, так и в защищенном режиме. Если вы работаете в защищенном режиме, то HeapWalker обходит области памяти, расположенные выше DOS, программ TSR и загруженных драйверов. HeapWalker имеет несколько режимов обхода. При обходе осматривается содержимое определенных мест в памяти и в окне HeapWalker строится список. Например, обход может включать список LRU. При этом обходе вся допускающая сброс память упорядочивается в список, в котором последний элемент будет использован первым. В этом списке можно выбрать произ¬ вольное число элементов, и с помощью команды ADD меню узнать в диалоговом окне суммарное число байтов в этих элементах.
Shaker (миксер для кучи) 113 Рис. 4.3. Меню Walk утилиты HeapWalker пакета SDK ■ SWAP (ОБМЕН) Swap яэляется инструментом, позволяющим обнаружить межсег¬ ментные вызовы процедур. Это нужно для повышения производи¬ тельности программ, поскольку в быстрых программах число, таких переходов должно быть сведено к минимуму. Использование программы Swap требует встраивания в приложение функции SwapRecording. Не обойтись без файлов .SYM, которые создаются, если при запуске линкера указана опция /map. Кроме этого Swap требует программы SKKERNAL.EXE, которая в существующей версии Windows работает только в реальном режиме. Если исполь¬ зовать режим, заданный по умолчанию, то Swap нужно много времени для обработки всех модулей и имен. Обычно имеет смысл выделять те модули, которые следует изучать с помощью програм¬ мы Swap. Для этого служит опция -М. ■ SHAKER (МИКСЕР ДЛЯ КУЧИ) Эта простая программа захватывает псевдослучайным образом в глобальной куче участки памяти, что приводит к перемещениям сегментов кода. Таким способом можно убедиться в том, что не возникает проблем, связанных с изменением расположения сег-
114 Разработка программ для Windows 3. 0 ментов кода. Программа Shaker работает автономно. После того как ваша программа запущена, вы можете проверить, не прояви¬ лись ли какие-либо ошибки. Перед началом работы Shaker позво¬ ляет установить несколько параметров, включая Ganularity, Time biterval и Max Objects. Granularity задает минимальный размер размещаемого объекта. Time Interval устанавливает временные интервалы, измеряемые в квантах системного времени. Мах Objects устанавливает максимальное число объектов, размещае¬ мых в памяти. Лучший способ понять работу этой программы — запустить ее. Она проста и вполне очевидна. ■ CODEVIEW FOR WINDOWS Как уже говорилось ранее, CodeView for Windows — это символь¬ ный отладчик для Windows. Он предназначен для работы со вторым экраном и функционирует только в защищенном режиме. Он также требует не менее двух мегабайтов расширенной памяти. По умолчанию CodeView отображается на втором экране, а отла¬ живаемая программа — на первом. Эта версия CodeView похожа на версию той же программы для DOS, но она содержит дополни¬ тельные функции, специально разработанные для отладки прило¬ жений Windows. Например, CodeView for Windows может отслеживать сегменты кода и данных приложения, перенастраи¬ вая свою таблицу символов, при изменении места расположения программы в памяти. Отладчик отображает также сообщения Windows, связанные с перемещением сегментов и созданием новых объектов. Ниже приведены пять новых команд, специально пред¬ назначенных для программирования в среде Windows: wdl wdg wdm wwm wbm ■ ОТЛАДЧИК SYMDEB ДЛЯ РЕАЛЬНОГО РЕЖИМА Symbolic Debug Utility, или кратко Symdeb, — инструмент, исполь¬ зуемый для отладки программ в реальном режиме. Как и CodeView for Windows, Symdeb также требует второго экрана, однако в этом случае имеется возможность использовать один терминал. Про¬ грамма Mapsyn работает совместно с Symdeb, преобразуя содержи¬ мое файлов .MAP к формату .SYM, который использует отладчик.
Структура прикладных программ для Windows 115 ■ ОБЕСПЕЧЕНИЕ СОВМЕСТИМОСТИ МЕЖДУ ВЕРСИЯМИ 2.0 И 3.0 Для того чтобы программой могло воспользоваться максимальное число пользователей, она должна быть совместимой со всеми версиями Windows, начиная со второй. При создании такой про¬ граммы нужно следовать специальным правилам. Одна из причин этого состоит в новой структуре памяти. Кроме ограничений, связанных с управлением памятью, имеются еще два типа согла¬ шений, касающихся флага разрешения прерываний и пропорци¬ ональных системных шрифтов. Все приложения, разработанные для Windows 2.0, находясь в среде версии 3.0, получают для меню системные пропорциональ¬ ные шрифты. Чтобы пользоваться ими в диалогах, управляющих элементах и области пользователя, приложения должны быть промаркированы специальным образом. Один из побочных эффек¬ тов появления пропорциональных шрифтов состоит в том, что программы, предполагающие, что все буквы имеют одинаковую ширину, перестают правильно работать. Для выравнивания таких элементов, как коды клавиш ускоренного ввода команд в меню, вместо последовательности пробелов следует использовать клави¬ шу TAB. Ограничения на работу с флагом разрешения прерываний относятся только к программам на языке ассемблера, использую¬ щим инструкции CLI и STL Поскольку в этой книге не ведется речь о языке ассемблера, мы не будем останавливаться на этом ограничении. ■ СТРУКТУРА ПРИКЛАДНЫХ ПРОГРАММ ДЛЯ WINDOWS Как правило, функция WinMain приложения выполняет следую¬ щие действия: 1. Регистрирует классы окон, которые используются прило¬ жением, и выполняет другие действия по инициализации. 2. Создает основное окно приложения и, если требуется, дру¬ гие окна. 3. Запускает цикл обработки сообщений из очереди приложе¬ ния. 4. Ожидает сообщения WM_QUIT, сообщающего о заверше¬ нии программы. Для каждого приложения Windows передает функции WinMain четыре параметра. Один (hbistance) служит для дескриптора эк¬ земпляра, второй (hPrevInstance) — для предыдущего экземпляра приложения, еще один содержит длинный указатель на заканчи¬
116 Разработка программ для Windows 3.0 вающуюся нулем командную строку, и последний (nCmdShow), который может быть передан функции Show Window, отображаю¬ щей основное окно приложения. Приложение включает также набор функций СаИЬаск, которые вызываются сообщениями Windows. ■ СТИЛЬ ПОЛЬЗОВАТЕЛЬСКОГО ИНТЕРФЕЙСА WINDOWS Фирма Microsoft составила некоторые рекомендации по стилю использования средств разработки Windows для построения при- лрограмм. Приведем два из них. 1. Не включайте в диалоги меню. 2. Используйте рамочную границу для модальных диалого¬ вых окон. ■ ОПИСАНИЯ РЕСУРСОВ Язык Resource Script фирмы Microsoft дает способ определения ресурсов, которые должны быть скомпилированы, а затем добав¬ лены к выполняемому файлу прикладной программы. Этот язык содержит восемь различных типов операторов: 1)одиночные стро¬ ки; 2)ресурсы, определяемые пользователем; З)данные; 4)таблицы строк; 5)клавиши ускорения; 6)меню; 7)диалоги; 8) директивы. Для определения таких ресурсов, как пиктограммы, шрифты и битовые карты, которые содержатся в отдельных файлах, исполь¬ зуются одиночные строки, указывающие идентификатор ресурса и соответствующее имя файла. Формат позволяет задать одну из опций, определяющую ресурс как FIXED (фиксируемый),. MOVEABLE (перемещаемый) или DE3CARDABLE (сбрасываемый). Ресурсы могут объявляться как заранее загружаемые или как загружаемые по запросу. Допускаются необрабатываемые и опре¬ деляемые пользователем форматы данных. Могут быть определены таблицы, состоящие из пронумерованных строк ASCH. Их можно извлечь по номеру, используя функцию LoadString. Вслед за метками меню идут номер Ш или имя константы с заранее определенным целым значением. В табл. 4.10 перечислен список ключевых слов, используемых в скриптах ресурсов. ■ ЗАКЛЮЧЕНИЕ SDK представляет собой внушительный набор средств разработки. Он весьма сложен. По этой причине в настоящее время проявилось множество средств создания программ для Windows, изготовлен-
Заключение 117 Таблица 4.10. Список ключевых слов RCDATA RTEXT STRINGTABLE CTEXT ACCELERATORS PUSHBUTTON MENU LE3TBOX MENUITEM GROUPBOX POPUP DEFPUSHBUTTON DIALOG RADIOBUTTON STYLE EDITTEXT CAPTION COMBOBOX MENU ICON CLASS SCROLLBAR FONT CONTROL LTEXT CHECKBUTTON ных другими фирмами, стремящимися облегчить жизнь програм¬ мисту. Они, безусловно, заслуживают внимания. Хотя в настоящее время существует пара продуктов, которые позволяют обходитьс§ при написании программ вовсе без SDK, для серьезных разработчи¬ ков средства, подобные Spy,HeapWalker и Coceview for Windows, по-прежнему делают использование SDK весьма привлекательным вне зависимости от выбранного языка программирования. У архитектуры Windows имеются некоторые преимущества объ- ектно-ориентированных систем. Программные системы, которые можно построить под Windows, будут напоминать их. Это проис¬ ходит благодаря обмену сообщениями между независимыми моду¬ лями, которые функционируют независимо и которые не требуется кодировать от нуля каждый раз, когда они нужны. Имеются классы объектов-шаблонов, которые могут быть использованы для штамповки готовых частей программ. Однако сходство кончается в этом месте. Наиболее привлекательные возможности ООП отсут¬ ствуют. Когда дело доходит до кодирования, обычное программи¬ рование на Си имеет мало общего с объектно-ориентированным стилем, свойственным архитектуре Windows. Легко заметить, на¬ пример, что,для того чтобы воспользоваться преимуществами, ко¬ торые дают ресурсы Windows, требуется написать много кода. Этот стандарт требует избыточности кода. С позиции ООП чувствуется определенная незавершенность Windows SDK, используемого в паре с ”плоским” Си. В принципе, вместе с Windows может быть использован любой язык. Однако использование любого другого языка, отличного от Си, рекомен¬ дованного фирмой Microsoft, сопряжено с определенными трудно¬ стями. На практике этот язык должен выдавать код весьма
118 Разработка программ для Windows 3. 0 специфического вида и работать со всеми соглашениями о моделях памяти, существующими в Windows. Это, впрочем, касается ско¬ рее не языка, а компилятора. По этой причине вы обнаружите серьезные достоинства у объектно-ориентированных языков, спе¬ циально перенесенных для использования в среде Windows. Ос¬ новная идея этой книги состоит в том, что наиболее естественный подход к программированию для Windows состоит в использова¬ нии объектно-ориентированной технологии. Поскольку даже относительно простая программа для Windows требует большого объема кода, естественным образом возникает практика создания родовых приложений, которые обслуживают множество областей и способны принять код, специфичный для конкретного приложения. В Windows SDK имеется пример такой родовой программы. ■ УПРАВЛЯЮЩИЕ СООБЩЕНИЯ РЕДАКТИРОВАНИЯ EM_CANUNDO EM_EMPTYUNDOBUFFER EM_FMTLINES ■ НОВЫЕ ФУНКЦИИ ВЕРСИИ 3.0 AdjustWindowRectEx Вычисляет размер прямоугольника AppendMenu AnsiLowerBuff AnsiToOemBuf AllocSelector AnimatePalette AllocCStoDSAlias AllocDStoCSAlias для окна расширенного стиля По селектору сегмента кода возвращает селектор сегмента данных для чтения и записи команд По селектору сегмента данных возвращает селектор сегмента кода, который может быть использован для выполнения команд, находящихся в сегменте данных Размещает новую копию селектора Заменяет записи в логической палитре Преобразует символы, находящиеся в буфере, в строчные символы Преобразует строку символов ANSI в набор символов, определенный разработчиком аппаратуры Добавляет новый элемент в конец меню
Новые функции версии 3. 0 119 ArrangeIconicW indows BeginDeferWindowPos ChangeSelector CreateCursor GreateDialogIndirectParam CreateDialogParam CreateDIBitmap CreateDffiPatternBrush CreateIcon CreatePalette CreatePolyPoligonRgn CreatePopupMenu CreateRoundRectRegn CreateWindowEx DebugBreak DefDlgProc DeferWindowPos DefFrameProc DefineHandleTable Располагает подокна, представленные в виде пиктограмм Инициализирует память для многооконной структуры Генерирует временный селектор кода, соответствующий селектору данных, или, наоборот, сегмент данных, соответствующий сегменту кода Создает курсор указанной высоты, ширины и конфигурации битов Создает немодальный диалог по шаблону и передает ему данные Создает немодальный диалог и передает ему данные Создает зависящую от устройства битовую карту по аппаратно-независимой карте Создает логическую кисть, имеющую шаблон, определяемый аппаратно¬ независимой битовой картой Создает пиктограмму с указанными высотой, шириной, набором цветов и конфигурацией битов Создает логическую палитру Создает область, представляющую собой объединение замкнутых многоугольников Создает пустое подменю Создает прямоугольную область с закругленными углами Создает окно расширенного типа Вызывает переход к отладчику Обеспечивает обработку по умолчанию любого сообщения Windows, которое не обрабатывает сам диалог Модифицирует структуру, содержащую положения и размеры одновременно нескольких окон Обеспечивает обработку по умолчанию сообщений, не обрабатываемых функцией основного окна MDI Создает индивидуальную таблицу дескрипторов
120 Разработка программ для Windows 3.0 DefMDIChildProc DeleteMenu DestroyCursor DestroyIcon DeviceCapabilities DialogBoxbidirectParam DialogBoxParam DlgDirListComboBox DlgDirSelectComboBox DOS3Call DrawFocusRect EndDeferWindowPos ExitWindows ExtDeviceMode ExtFloodFill FatalAppExit FreeModule FreeSelector GetClassInfo Обеспечивает обработку по умолчанию сообщений, не обрабатываемых функцией дочернего окна MDI Удаляет подменю из указанного меню и освобождает занимаемую им память Уничтожает созданный функцией CreateCursor курсор и освобождает занимаемую им память Уничтожает созданную функцией CreateIcon пиктограмму и освобождает занимаемую ею память Выдает характеристики принтера Создает модальный диалог по шаблону и передает ему данные для инициализации Создает модальный диалог и передает ему данные Заполняет комбинированный список с именами файлов по заданному пути и расширению Копирует текущий пункт из комбинированного списка в строку Позволяет программам Windows выполнять прерывание 21h Рисует прямоугольник, указывающий текущий фокус ввода Изменяет расположение и размер окна при перерисовке экрана Инициирует стандартную последовательность выключения Windows Получает или модифицирует информацию для данного драйвера Заполняет область на экране, используя текущую кисть Показывает сообщение о фатальной ошибке и покидает Windows Уменьшает на единицу список ссылок на модуль Освобождает ранее захваченные селекторы Дает информацию о классе окна
Новые функции версии 3.0 121 GetCodebifo GetCurrentPDB GetDesktopW indow GetDialogBaseUnits GetDlBits GetDlgCntlID GetDOSEnvironment GetDriveType GetFreeSpace GetKBCodePage GetKeyboardType GetKeyNameText GetLastActivePopup GetMenuCheckMarkDimensions GetNearestPaletteIndex GetPaletteEntries GetPriorityClipboardFormat GetPrivateProfileInt Дает информацию о сегменте. Возвращает массив из 4 16-битных слов Возвращает сегмент PSP Возвращает дескриптор фона экрана Windows Возвращает базовые единицы диалога для определения фактических размеров управляющих элементов Копирует биты из битовой карты в заданный буфер Возвращает ID указанного окна Возвращает длинный указатель на строку среды выполнения текущей задачи Определяет, является ли диск переносным, стационарным или удаленным Возвращает число свободных байтов в глобальной куче Определяет, какая из кодовых страниц OEM/ANSI загружена системой Windows Возвращает тип клавиатуры Возвращает строку с именем нажатой клавиши Определяет, какое из подменю данного окна было активным последним Возвращает размерности предопределенной битовой карты, используемой для изображения отметки элементов меню Возвращает индекс записи палитры, наиболее близко соответствующей цвету Возвращает диапазон записей логической палитры Возвращает первый формат из списка форматов, для которого имеются соответствующие ему данные в clipboard Возвращает значение целого ключа из указанного файла
122 Разработка программ для Windows 3.0 GetPrivateProfileString GetRngBox GetSystemDirectory GetSystemPaletteEntries GetSystemPaletteUse GetTabbedTextExtent GetWindowsDirectory WindowsGetWinFlags GlobalDosAlloc GlobalDosFree GlobalFix GlobalPageLock GlobalPageUnlock GlobalUnfix bisertMenu bCharAlpha IsCharAl phaN um eric bCharLower feCharUpper Копирует строку символов из файла в указанный буфер Возвращает координаты прямоугольника, ограничивающего указанную область Возвращает полное имя системного директория Windows Позволяет получить записи системной палитры Определяет, доступна ли программе полная системная палитра Вычисляет высоту и ширину указанной строки текста Возвращает полное имя директория Windows Возвращает 32-разрядное поле флагов, описывающих процессор и конфигурацию памяти Распределяет глобальную память, к которой может обращаться DOS, работающая в реальном режиме Освобождает блок памяти, захваченный при помощи функции GlobalDosAlloc Предотвращает перемещение или сброс блока глобальной памяти Увеличивает счетчик постраничной блокировки памяти Уменьшает счетчик постраничной блокировки памяти Разблокирует блок глобальной памяти Вставляет новый элемент в меню на определенное место Проверяет, является ли символ буквой Проверяет, является ли символ буквой или цифрой Проверяет, является ли буква строчной Проверяет, является ли буква прописной
Новые функции версии 3.0 123 lstrcmp lstrcmpi MapVirtualKey ModifyMenu MuLDiv NetBIOSCall OemKeyScan OutputDebugString PALETTEINDEX PALETTERGB PolyPolygon ProfClear ProfFinish ProfFlush ProflnsChk ProfSampRate ProfSetup ProfStart ProfStop RealizePalette Лексикографически сравнивает две строки. Сравнение чувствительно к регистру символов Лексикографически сравнивает две строки. Сравнение не чувствительно к регистру символов Осуществляет преобразование между ASCn-кодами, скан-кодами и кодами виртуальных клавиш Изменяет существующий пункт меню Умножает два словных числа и делит их на третье Позволяет приложениям Windows использовать функции прерывания BIOS 5Ch Отображает OEM ASCE коды на OEM скан-коды Посылает сообщение отладчику Макро, возвращающее индекс записи палитры Макро, возвращающее значения интенсивности RGB Создает набор многоугольников Стирает накопленную профайлером информацию Заканчивает сбор профайлером информации и сбрасывает накопленные данные на диск Сбрасывает накопленные профайлером данные на диск для предотвращения переполнения буфера Проверяет, установлен ли профайлер Устанавливает интервалы сбора информации профайлером Задает параметры при работе профайлера в усовершенствованном 386 режиме Стартует сбор информации профайлером Останавливает сбор информации Отображает элементы логической палитры на системную палитру
124 Разработка программ для Windows 3.0 RectInRegion RemoveMenu ResizePalette SelectPalette SetDIBits SetDIBitsToDevice SetHandleCount SetMenuItemBitmaps SetPaletteEntries SetSystemPaletteU se StretchDIBits SwapRecording SwithStackBack SwitchStackTo TabbedTextOut ToAscii TrackPopupMenu TranslateMDESysAccel UnregisterClass UpdateCoIors Проверяет, находится ли прямоугольник целиком в заданной области Удаляет из меню элемент вместе с его подменю Изменяет размер логической палитры Выбирает логическую палитру Устанавливает значения определенных битов битовой карты Отображает аппаратно-независимую битовую карту на экране дисплея Изменяет число дескрипторов файлов Ассоциирует битовую карту с элементом меню Устанавливает флаги и значения цвета для ряда записей логической палитры Дает возможность программе использовать полную системную палитру Копирует прямоугольник аппаратно¬ независимой битовой карты Включает и выключает анализ, выполняемый программой Swap Возвращает стек текущей программы в сегмент данных Переносит стек текущей программы в указанный сегмент Пишет строку в определенные колонки Транслирует код клавиши в соответствующий символ или символы ANSI Показывает подменю в определенном месте и отслеживает выбор элементов в нем Обрабатывает акселераторные комбинации клавиш для команд системного меню в подокне MDI Удаляет определенный класс Windows из таблицы классов Обновляет рабочую область указанного контекста устройства
Новые сообщения Windows 3.0 125 ValidateCodeSegments WinExec WinPrivateProfileString Wvsprintf Выдает отладочную информацию на терминал Выполняет приложение с заданной командной строкой Копирует символьную строку в заданный инициализационный файл sprintf. Форматирует и запоминает последовательность символов в буфере. Значения передаются как параметры функции Форматирует и запоминает последовательность символов в буфере. Значения передаются через массив НОВЫЕ СООБЩЕНИЯ WINDOWS 3.0 CB_ADDSTRING CB_DELETESTRDsTG CB_Dni CB_FINDSTRING CB_GETCOUNT CB_GETCURSEL CB_GETEDITSEL CB_GETITEMDATA CB_GETLBTEXT CB_GETLBTEXTLEN CBJNSERTSTRING CB_LIMITTEXT СВ RESETCONTENT Добавить строку в комбинированное окошко Удалить строку из комбинированного окошка Занести список файлов текущего директория в комбинированное окошко Найти первую подходящую строку в комбинированном окошке Выдать число элементов в комбинированном окошке Выдать индекс выделенного элемента Выдать начало и конец выделенного текста в редактирующем элементе комбинированного окошка Выдать 32-разрядное значение, ассоциированное с элементом в комбинированном окошке Скопировать строку из комбинированного окошка в буфер Выдать длину строки в комбинированном окошке Вставить строку в комбинированное окошко Ограничить длину вводимого текста Удалить все строки комбинированного окошка
126 Разработка программ для Windows 3.0 CB_SETCURSEL CB_SETEDITSEL СВ SETITEMDATA СВ SHOWDROPDOWN Выбрать строку и сделать ее текущей Выделить все символы между первым и последним отмеченными Ассоциировать 32-разрядное значение с элементом комбинированного окошка Показать или спрятать выпадающий список Оповещающие сообщения для комбинированного окошка CBN_DBLCLK CBN_DROPDOWN CBN_EDITCHAN GE CBN_EDITUPDATE CBN_ERRSPACE CBN_KILLFOCUS CBN_SELCHAN GE CBN SETFOCUS Пользователь дважды нажал кнопку мыши на строке комбинированного окошка Сообщить дочернему окну, что должен выпасть список его комбинированного подокна Пользователь изменил текст в редактирующем элементе Необходимо отобразить модифицированный текст Нехватка памяти Комбинированное окошко теряет фокус Изменился выбранный элемент Комбинированное окошко получает фокус Сообщения, управляющие редактированием EM_EMPTYUNDOBUFFER EM_SETPASSWORDCHAR EM_SETTABSTOPS ЕМ MAXTEXT Выключает возможность возврата к предыдущему состоянию Изменяет символ пароля Устанавливает позиции табуляции при многострочном редактировании Объявляет, что больше нельзя вводить символы Сообщения окошку списка LB FINDSTRING mcffimoemsTEALEXEENT LB GETITEMDATA Найти первую подходящую строку в списке Выдать ширину горизонтального скроллирования списка Выдать 32-разрядное значение, ассоциированное с элементом списка
Новые сообщения Windows 3.0 127 LB_GETITEMRECT LB_GETSELCOUNT LB_GETSELITEMS LB_GETOPENDSTDEX LB_SETCOLUMNWmTH LB_SEraOREZONTALEXTENT LB_SETITEMDATA LB_SETTABSTOPS LB_SETOPENDSTDEX LB_KILLFOCU S LB_SETFOCUS WM_CHARTOITEM WM_COMPACTIN G WM_COMPAREITEM WM_DELETEITEM WM_DRAWrTEM WM GETFONT Выдать координаты прямоугольника, содержащего заданный элемент окна списка Выдать число выделенных элементов списка Выдать номера выделенных элементов Выдать номер первого видимого элемента списка В окошке, состоящем из нескольких колонок, задать ширину колонок в пикселах Установить ширину горизонтального скроллирования списка Ассоциировать с элементом списка 32-разрядное значение Установить позиции табуляции в списке Установить номер первого видимого элемента Посылается, когда окно теряет фокус Посылается, когда окно получает фокус Посылается окошком списка дочернему окну при получении сообщения WM_CHAR Посылается всем окнам первого уровня, когда Windows обнаруживает, что тратит более 12.5% процессорного времени на упаковку памяти Определить относительную позицию элемента в упорядоченном списке Информирует окно, владеющее окошком списка или комбинированным окошком, что уничтожен элемент Сообщает окну о необходимости перерисовать управляющий элемент Получить от управляющего элемента шрифт, чтобы использовать его далее для изображения текста
128 Разработка программ для Windows 3. 0 WM_ICONERASEBKGD WM_MDIACTIV АТЕ WM_MDICASCADE WM_MDICREATE WM_MDIDESTROY WM_MDIGETACTrVE WM_MDHCONARRANGE WM_MDIMAXIMIZE WM_MDE4EXT WM_MDIRESTORE WM_MDISETMENU WM_MDITILE WM_MEASUREITEM WM_PAINTICON WM_PALETTECHANGED WM_PARENTNOTEFY WM_QUERYDRAGICON WM_QUERYNEWPALETTE WM_SETFONT WM SPOOLERSTATUS Посылается ужатому окну, когда требуется заполнение фона перед перерисовкой пиктограммы Посылается клиенту MDI, чтобы он активировал новое дочернее окно Расположить дочерние окна фрейма MDI в форме ”каскада” Посылается основному окну MDI для создания нового дочернего окна Посылается основному окну MDI для закрытия дочернего окна Выдать активное в данный момент дочернее окно Посылается клиенту MDt чтобы он расставил дочерние окна Посылается клиенту MDI для максимизации дочернего окна Активирует следующее подокно MDI Восстанавливает размер максимизированного или минимизированного подокна Заменяет меню в основном окне MDI Располагает подокна MDI в виде паркета Требует заполнить структуру данных MEASUREITEM Требует перерисовать минимизированное окно Сообщает всем окнам, что текущее окно изменило палитру Сообщает родительскому окну о существенных изменениях статуса Сообщает минимизированному окну, не имеющему пиктограммы класса, что *ero передвигают Сообщает окну, что оно скоро получит фокус ввода Определяет шрифт, который следует использовать в окнах диалога Посылается менеджером печати при добавлении или удалении элементов из очереди
Новые структуры данных Windows 3.0 129 НОВЫЕ СТРУКТУРЫ ДАННЫХ WINDOWS 3.0 BrTMAPCOREHEADER BrTMAPCORENFO BrTMAPFBLEHEADER BrrMAPDNTFO BrTMAPE4FOHEADER CLIENTCREATESTRU CT OMPAREITEMSTRUCT DELETETTEMSTRUCT DEVMODE LOGPALETTE MDICREATESTRUCT MEASUREITEMSTRU CT PALETTEENTRY RGBQUAD RGBTRIPLE Содержит сведения о размерах и цветах аппаратно-независимой битовой карты Полное определение размеров и цветов аппаратно-независимой битовой карты Содержит тип, размер и расположение аппаратно¬ независимой битовой карты Полностью определяет размеры и цвет аппаратно-независимой битовой карты в Windows 3.0 Содержит информацию о размерах и формате аппаратно-независимой битовой карты в Windows 3.0 Содержит информацию о меню и первом подокне ШЛчжна Содержит информацию о сравнении двух записей в нестандартном списке Описывает удаляемый элемент нестандартного списка Содержит информацию об инициализации устройства и конфигурации драйвера принтера Определяет логическую палитру Содержит класс, титул, хозяина, расположение и размер подокна MDI Содержит размеры нестандартного управляющего подокна Задает цвет и способ использования элемента логической цветовой палитры 4-байтовая структура, описывающая интенсивность цветов RGB 3-байтовая структура, описывающая интенсивность цветов RGB £—857
Глава 5 ■ ВВЕДЕНИЕ В СИСТЕМУ ПРОГРАММИРОВАНИЯ АКТОР ■ ЯЗЫК АКТОР, ВЕРСИЯ 3.0 В качестве введения в объектноюриентированное программирова¬ ние (ООП) для Windows хорошо познакомиться с языком Актор группы Whitewater. Актор не только работает под управлением MS-Windows, он был первым объектно-ориентированным инстру¬ ментом, разработанным специально для написания приложений, работающих в среде Windows. Использование языка Актор — самый простой путь к изучению ООП в Windows, в этом случае вам не потребуется собственного SDK. В этой главе вначале опи¬ сывается среда программирования Актор, затем его синтаксис и, наконец, после этого даются примеры программирования на Ак¬ торе в Windows и использования различных инструментов, помо¬ гающих в этом. ■ СРЕДА ПРОГРАММИРОВАНИЯ АКТОР Среда программирования Актор состоит из экрана дисплея, рабо¬ чего окна и некоторых факультативных возможностей, использу¬ емых в той или иной ситуации. Важнейшими среди них являются Броузер (визуализатор), инспектор, отладчик и редактор файлов. Актор использует возможности Windows для того, чтобы реализо¬ вывать объекты, обеспечивающие интерактивную оконную среду, похожую на среду Смолтока. Как и во многих программах MS- Windows, при использовании Актора вам понадобится "мышь”. Главными составляющими ’’рабочего стола” Актора являются рабочие окна, визуализаторы и инспекторы, смоделированные в духе Смолтока. Когда система начинает первый сеанс, она выдает рабочее окно с двумя рядами опций команд, расположенных вдоль верхней полоски рабочего окна Q>uc. 5.1). В число их входят
Броузер Актора 131 Рис. 5.1. Рабочее окно Актора следующие команды: File, Edit!, Doit!, Browse!, bispect!, Show, Room! и Templates. Область редактирования в рабочем окне ведет себя как интерпре¬ татор. Если введете некоторое выражение и нажмете Enter, Актор попытается скомпилировать и выполнить его. Если некоторый код (программный текст) уже имеется в окне редактирования, то его фрагмент можно подсветить, после чего нажать Doit! (Выполни!), в результате отмеченный фрагмент будет скомпилирован и выпол¬ нен. Опции Browse! и bispect! создают новый экземпляр этих инструментов и открывают на ’’рабочем столе” Актора окно в стиле рорир-окон (окон, появляющихся на месте уже имеющихся). ■ БРОУЗЕР АКТОРА Броузер ipuc. 5.2) выполняет ту же функцию, что и инспектор, но открывает интерактивное окно не на каждый единичный объект, а сразу на всю систему классов, загруженных в данный момент. Окно прокручивания списка (окно скроллинга) содержит список всех классов иерархии классов Актора. Правое списковое окно содержит методы текущего класса. Выбрав класс, а потом какой-
132 Введение в систему программирования Актор Рис. 5.2. Окно Броузера классов в Акторе нибудь из его методов, вы можете получить доступ к коду в нижнем окне и редактировать его. Выбор нужной опции в строке-меню Броузера позволит вам ука¬ зать, как просматривать классы — в соответствии с иерархией или с алфавитным порядком. Еще более простой способ найти класс — использовать опцию Goto Class в меню Options. Кроме того, в этом меню располагаются средства для определения переменных класса и программ его инициализации. К вопросу о переменных класса мы вернемся несколько позже. Отдельное меню Броузера предназначено для поиска и замены операций в текстовом окошке (pane). Для удобства при помощи опции Templates можно выбрать различные шаблоны, которые вставляются в окошко редактирования — это ускоряет процесс разработки и ограничивает возможность появления опечатки или синтаксической ошибки. Меню Utility Броузера содержит несколько мощных возможно¬ стей: bnplementors, Senders, Window Routine Senders, Global References и References.
Инспекторы 133 Опция Implementors (реализаторы) находит целиком среду разра¬ ботки для всех классов, которые реализуют методы с данным именем. Опция Senders (отправители) делает примерно то же самое, но она строит список методов, которые используют данный метод в посылаемых сообщениях. Опция Window Routine Senders (отправители оконной программы) особенно интересна. С ее по¬ мощью можно найти все методы в среде разработки, которые вызывают некоторую функцию Windows. Global References (гло¬ бальные ссылки) строит список всех методов, которые ссылаются на выбранную глобальную переменную. И наконец, References возвращает список всех предварительно связанных ссылок на некоторый метод. Предварительное связывание — это специаль¬ ный механизм Актора, его можно использовать для написания более эффективного кода, когда вы уверены в том, каков будет тип аргументов при обращении к данному методу. Для любого класса Броузер показывает только те методы, которые реализованы именно для этого класса. Наследуемые методы не попадают в список, который просматривается через окошко G?ane), однако наследуемые переменные попадают в список окошка пере¬ менных. Броузер — один из важнейших инструментов ООП. Однажды попробовав поработать с ним, вы никогда не станете заниматься ООП без аналогичного средства. Отметим, что Броузер Актора в отличие от других, где можно работать как с мышью, так и без нее, ориентирован исключительно на использование мыши. ■ ИНСПЕКТОРЫ Инспектор — это рорир-окно, которое можно использовать в любой момент времени для просмотра содержимого каждого из объектов как экземпляров, так и классов. Пользоваться им очень просто. Если вы хотите просмотреть некоторый экземпляр объекта, сна¬ чала подсветите его имя в рабочем окне и затем обратитесь к опции bispect! из окна Актора в среде разработки. Если объект является экземпляром одного из классов CoUection, то в верхнем правом окошке вы найдете список номеров (индексов), соответствующих объектам в коллекции. Указав на любой из них, вы выберите объект, и его имя будет высвечено в главном окне. Инспекторы используются тогда, когда требуется сконцентриро¬ ваться на одном частном объекте. Вы обращаетесь к ним тогда, когда нужно изучать содержимое объектов в деталях или модифи¬ цировать их. Верхнее левое окошко окна инспектора содержит список окон, в которых выдаются все экземплярные переменные некоторого объекта. Если указать мышью на какую-либо перемен¬ ную в скроллируемом списке, то в нижнем окошке инспектора,
134 Вввдвнив в систему программирования Актор его окне редактирования, выдается ее значение. При использова¬ нии инспектора доступны переменные как класса, так и экземп¬ ляров. ■ КЛАССЫ АКТОРА Актор оснащен удивительно богатой библиотекой классов, гото¬ вых программ, которые можно использовать при разработке соб¬ ственных приложений. Эти библиотеки являются мостиком, связывающим изучение и использование системы. Здесь приво¬ дится неполный список классов, которые используются для струк¬ тур данных и графических возможностей, связанных с ними. Object Collection IndexedCollection Аггау Function Ordered-Collection Sorted-Collection Text-Collection Byte-Collection String Symbol Struct DosStruct Graphics-Object WinPolygon Rect WinEllipse RndRect Proc Interval CharInterval KeyedCollection Dictionary Method-Dictionary Identity-Dictionary System-классы — это особые конструкции Актора. Только для методов этих классов разрешается посылать сообщения, не имея ”приемников” этих сообщений. ■ КЛАССЫ MS-WINDOWS Одной из наиболее привлекательных особенностей системы Актор является то, что она предоставляет встроенные классы для облег¬
Классы MS-Windows 135 чения использования пользовательского интерфейса MS-Windows. Таких классов три: Window, Control и Dialog. Сначала рассмотрим ветвь иерархии, которая содержит все классы Windows. Приведем схему этой ветви. Object Windows-Object Control Button Edit ListBox ClassList MethodList VarList ScrollBar Dialog ClVarDialog ClassDialog DebugDialog DialogDesign DirtyCLD FileDialog InputDialog ReplaceDialog PrintDialog SealDialog Window AboutWindow TextWindow EditWindow WorkEdit BrowEdit DBBrowEdit DebugEdit FileWindow PrintFileWindow Workspace ToolWindow Browser DebugWindow Inspector Для упрощения мы выделим три главных ветви: Window, Control и Dialog. Может показаться, что класс Window гораздо больше других, но это лишь формальный или абстрактный класс. Это означает, что он реализует методы, которые будут использоваться
136 Введение в систему программирования Актор подклассами, каждый из которых реализует специальные окна, в действительности существующие и используемые. В частности, Window реализует программы для взаимодействия с MS-Windows. Для того чтобы понимать, как работает Актор, надо помнить о разнице между сообщениями Windows и Актора. Windows не понимает никаких сообщений уровня Актора, просто игнорирует их существование. Актор не понимает большую часть сообщений Windows, поскольку для него нет необходимости отвечать на них. Однако, если необходимо, можно сделать так, что Актор будет отвечать на любое сообщение Windows (любым желаемым спосо¬ бом). Все, что необходимо сделать для этого, это определить метод некоторого класса в Акторе с таким же именем, как и сообщение Windows, и в определении метода описать, как объект Актора, принимающий сообщение, будет отвечать на него. Всегда, когда в Акторе создается оконный объект, в Windows посылается сообщение СаИ CreateWindow с общим числом пара¬ метров не менее одиннадцати. Когда в такой ситуации создается оконный объект, Windows посылает дескриптор (handle) или имя созданного окна и Актор должен заполнить этот дескриптор. В терминах ООП на Акторе можно сказать, в классе Window и у его потомков имеется метод create, результатом работы которого яв¬ ляется посылка MS-Windows сообщения CreateWindow. Когда окно Актора закрывается, оконный объект в действительности по-прежнему будет существовать до тех пор, пока каким-либо явным dnoco6oM не будет удален из системы. Дескриптор оконного меню запоминается в экземплярной переменной объекта hMenu. Метод loadMenu занимается загрузкой ресурсов меню и задает значение hMenu. Один из простейших потомков Window — класс TextWindow. Он предоставляет вам некоторые реальные услуги. Этот класс позво¬ ляет создавать tiled (паркетные) окна. Они могут печатать текст и делают это при помощи методов printString и printChar, которые вызывают в MS-Windows функцию Textout GDI (Graphics Display biterface). Часто вам требуется окно, которое может не только показывать текст, но и передвигаться по нему и редактировать его. Такие средства редактирования предоставляет программа класса EditWindow, являющегося подклассом TextWindow. Класс WorkEdit — еще один шаг вперед: он позволяет создавать окна, в которых можно не только редактировать, но и вводить и выпол¬ нять операторы языка Актор. Три подкласса WorkEdit представ¬ ляют окна традиционного назначения: броузеры, броузеры файлов и рабочие окна общего назначения. Их главное отличие от тради¬ ционных в том, что они, так же как и их предок TextWindow, являются паркетными окнами. Чаще всего в Акторе используются окна из другой ветви дерева.
Элементы управлений 137 Окна, которые вы получаете из PopupWindow, напоминают слои, накладываемые друг на друга. Но в отличие от паркетных окон их нельзя распахнуть или сжать в пиктограмму. Это обусловлено MS-Windows, рорир-окна требуют наличия родительского тексто¬ вого окна. Когда родительское окно сжимается в пиктограмму, рорир-окна, ассоциированные с ним, временно становятся невиди¬ мыми. ■ ЭЛЕМЕНТЫ УПРАВЛЕНИЯ Как и в MS-Windows, в Акторе элементы управления (Controls) — это специальный вид окон, который используется для программ ввода и вывода в пользовательском интерфейсе. В качестве при¬ меров элементов управления можно привести кнопку, список окошек, полосу скроллирования. Ветвь дерева классов, связанная с элементами управления, выглядит следующим образом: Object Control Button Edit ListBox ClassList MethodList VarList Scrollbar Подобно классу Window класс Актора Control тоже формальный, его подклассы — это подклассы, которые реализуются в приложе¬ ниях. С элементами управления в Акторе работают практически так же, как и в Windows. Новые экземпляры создаются при помощи посылки сообщения new, а показываются они при посыл¬ ке сообщения show. Класс ListBox является подклассом Control, который создает ма¬ ленькое рорирчжно, ассоциированное с родительским окном, и выдает список айтемов (item — элементов некоторого перечисле¬ ния). Обычно списковое окно используется, чтобы представить айтемы, которые пользователь может выбирать при помощи мы¬ ши. Класс Scrollbar используется не для всех полосок скроллиро¬ вания, как можно было бы предположить. Стиль определений для окон обычно обеспечивает это автоматически. Этот класс исполь¬ зуется только тогда, когда расположение полосок скроллирования должно отличаться от стандартного, которое обычно обеспечива¬ ется автоматически. Таким образом, используя этот класс, вы сможете создать набор горизонтальных и вертикальных полосок скроллирования, которые находятся в части экрана, отдельной от
138 Введение в систему программирования Актор родительского окна, которым они управляют. Аналогично, класс Button предназначен для создания ”кнопок” в манере, отличной от обьгчной. Кнопками оснащают встроенные диалоговые окошки (boxes), но, возможно, вы хотите, чтобы при помощи кнопок выполнялось и еще кое-что. Вы можете, например, пожелать разместить их в обычных окнах. Теперь переходим к классу Dialog и его подклассам: Object Dialog ClVarDialog ClassDialog DebugDialog DialogDesign DirtyCLD FileDialog biputDialog ReplaceDialog PrintDialog SealDialog Диалоговые окна похожи на рорир-окна тем, что они накладыва¬ ются одно поверх другого, и тем, что им необходимо иметь родительское окно, с которым они связываются. Так же как Window и Control, Dialog, почзуществу, это абстрактный класс, который предоставляет программы, предназначенные для исполь¬ зования в его потомках. Класс FileDialog в Акторе используется для создания диалоговых окон, которые обычно появляются в MS-Windows, когда вы загружаете файл, используя pull-down-ме- ню (ниспадающее меню). Класс для диалоговых объектов ClassDialog используется тогда, когда при помощи броузера редак¬ тируется или создается какой-нибудь класс. ■ ДИНАМИЧЕСКИЕ МЕНЮ И ДИАЛОГИ Еще одно преимущество Актора проявляется в том, что он помимо варианта со статщческой компиляцией ресурсов предоставляет некоторый альтернативный вариант создания меню и диалогов. Хотя статические меню и диалоги более эффективны, динамиче¬ ские ресурсы в силу того, что создаются динамически, и проще в использовании. Для создания динамических меню используется класс Menu, который не был представлен в иерархии классов, но который легко загрузить при помощи оператора: load ("classes\menu.cls");
Списковые структуры 139 С другой стороны, если вы не хотите набирать такой длинный текст, вы можете перейти в меню File окна Актора Display и выполнить Load, а затем выбрать menu.cls из директория классов. ■ CALLBACKS Нередко вам требуется повторить некоторое действие над объекта¬ ми определенного ряда: над всеми окнами определенного типа, всеми экземплярами определенного класса и т.п. В Акторе для этой цели небходимо послать в Windows адрес функции, при помощи которой вы собираетесь выполнять итерации с таким расчетом, что Windows сможет, обращаясь к ней последовательно, получить все оконные объекты. Для упрощения этого процесса предоставляется класс CaUback, который дает все виды конструк¬ ций, опирающихся на са11ЬасЫ£ункции, — функции, последова¬ тельно реализующие серии вызовов. В табл. 5.1 приведен список перечисляющих функций Windows, которые можно использовать таким образом. Таблица 5.1. Перечисляющие функции Windows EnumChilWindows EnumClipboardFormats EnumFonts EnmnMetaFile EnumObjects EnumProps EnumTaskWindows EnumWindows Для того чтобы использовать в Акторе класс Callback, предвари¬ тельно надо написать: Def enumWindows(self cb, set) { set := new(Set, 4); сЬ := create(callback, #(0, 1), {using(hnd,IVal); add(set,hnd); 1}); Call EnumWindows(lock(cd) , lL); free(cd); ^set; ■ СПИСКОВЫЕ СТРУКТУРЫ Для задач искусственного интеллекта и во многих других прило¬ жениях важны механизмы для обработки связанных списков. Как сделать это в Акторе? В соответствии с одним вариантом можно было бы работать с классом OrderedCollection, дополнив его под¬ классы необходимыми методами для обработки списков. Однако листинг одной из демонстрационных программ, которая поставле¬ на вместе с предыдущей версией Актора, предлагает другой под¬ ход. Новый класс ListNode определяется как подкласс класса Collection. Для этого нового класса определяются методы append,
140 Введение в систему программирования Актор do, isAtom, printOn и rPrintOn. Для Object как корневого класса также определяются новые методы. isAtom, rPrintOn и cons. Если вы посмотрите в текст метода cons, то увидите, что это программа для посылки сообщения new классу ListNode для создания его нового экземпляра. Очевидно, это всего лишь элементарный пере¬ ходник, обеспечивающий сервис для начинающих пользоваться классом для обработки списков. Структуры, используемые классом OrderedCollection, существенно отличаются от динамически модифицируемых списков. В терми¬ нологии ООП упорядоченная коллекция — это коллекция фикси¬ рованной длины, она никогда не увеличивается. Это означает, что, когда вы создаете некоторую упорядоченную коллекцию (OrderedCollection), то должны ориентироваться на некоторое мак¬ симальное число элементов. Если в частично заполненной коллек¬ ции максимум еще не достигнут, то можно добавлять элементы как в начало, так и в конец списка. Когда максимум достигнут, а вам требуется еще, необходимо послать в коллекцию сообщение grow, при этом будет создан новый массив необходимого размера, а элементы старого — скопированы в него. ■ ПРОГРАММИРОВАНИЕ НА АКТОРЕ Из сказанного можно заключить, что язык Актор — это некоторый гибрид. Он очень похож на Смолток, но его синтаксис сильно тяготеет к Си. С точки зрения программистов, Актор является хорошим воссозданием среды типа Смолтока, но без его специфи¬ ческого синтаксиса. Программисты с опытом работы на Си навер¬ няка одобрят синтаксис Актора, и я думаю, большинство согласится, что этот синтаксис делает объектно-ориентированную программу более понятной. Каковы же его основные черты? Я предполагаю, что вы зададите такой вопрос, и поэтому перехожу к обсуждению именно этой темы. ■ ОСНОВЫ СИНТАКСИСА АКТОРА Название ”Актор” начинается со слова ”акт” — действие. Имена метода или процедуры, ”глагол” в программном тексте в сообще¬ нии Актора стоят на первом месте. Сначала имя действия, затем скобки и список элементов внутри них. Это общий формат для всех операторов Актора. Самый первый элемент списка после открыва¬ ющей скобки — это всегда имя объекта, которому посылается сообщение. Так, в некотором гипотетическом сообщении verify(program77, rapidModel); сообщение veryfi посылается некоторому объекту, называемому program77. ’’rapidModel” — это аргумент, который уточняет на¬ значение сообщения. В конце любого оператора Актора, передаю¬
Основы синтаксиса Актора 141 щего сообщение, если хотите ’’терминатор” или ’’завершитель”, точка с запятой, так же как в Си. Кроме того, с Си сближает и использование фигурных скобок ”{}”, которые заключают в себе блок программы. Важно иметь в виду, что Актор чувствителен к тому, какими буквами — прописными или строчными — вводится текст. Он не распознает термин, если все прописные и строчные буквы не будут написаны так, как надо. Одна маленькая ошибка, и Актор начи¬ нает жаловаться на то, что не понимает вас. Так же как и в языках Си и Паскаль, в Акторе аргументы, заключенные в скобки следуют сразу за именем метода или селектором, и подобно Паскалю и Смолтоку присваивание пере¬ менной задается операцией ”?=”. Аналогично, Смолтоку, для того чтобы создать новый экземпляр, вы обычно посылаете сообщение ’’new” в некоторый класс и присваиваете этот экземпляр некото¬ рому имени. Так, например, вы можете создать экземпляр из класса Turtle (’’черепашка” в стиле Лого), указав: Barney:=new(Turtle) ; В общем случае посылка сообщения в Акторе подобна передаче аргументов. Теперь черепашка будет разбирать посланные нами сообщения. Как и любая нормальная черепашка, Barney знает, что сообщение ”г” означает ’’повернись направо”, ”1” — ’’налево, ”f” — двигайся вперед, ”b” — назад. Она также знает, что ”down” — означает ’’опусти хвост” ОДО? того, чтобы рисовать), а ”up” — ’’подними хвост”. Так, если бы мы хотели, чтобы черепашка Barney совершила прогулку и нарисовала квадрат, мы должны были бы сообщить ей следующее: down(Barney); f(Barney, 10); r(Barney, 90); f(Barney, 10); l(Barney, 90); b(Barney, 10); r (Barney, 90); b(Barney, 10); up (Barney); Для определения методов в Акторе используется ключевое слово Def. Так, если мы хотим обучить не только Barney, но и всех других черепашек новому сообщению walkSquare (прогуляйся по квадрату), мы можем воспользоваться таким методом: Def walkSquare(self, size) { down(self); f (self, size); r(self, 90); f (self, size); 1 (self, 90); b(self, size);
142 Введение в систему программирования Актор r(self, 90); f(self, size); up(self); }; Теперь, для того чтобы попросить Barney или другую черепашку произвести подобный маневр, все,что от нас требуется, это указать: walkSquare(Barney, 10); Возможно, вы заметили, что движение этой черепашки по квадрату происходит в одном направлении — по часовой стрелке. Иногда, правда довольно редко, требуется другой вид черепашек, который двигается по квадрату против часовой стрелки. К счастью, объек¬ тно-ориентированные системы типа Актора позволяют отразить и такую причуду естественной истории. Как мы можем наделить черепашку этой новой особенностью? Нам следует определить класс CounterTurtle (контр-черепашка) и в нем метод walkSquare для черепашек этого вида, метод, который задает вариант стан¬ дартной прогулки по квадрату против часовой стрелки. Это заодно дает нам возможность проиллюстрировать определение новых классов в Акторе. Обычно в Акторе новый класс создается на основе какого-нибудь уже имеющегося класса, доступного вам в броузере, поэтому я с этого и начну. Прежде всего вы находите в списке классов в верхнем левом окне класс Turtle. Затем переходите в ниспадающее меню Options и выбираете в нем опцию ’’MakeDescendant” (создать потомка). В результате откроется рорир-окно, которое будет ис¬ пользоваться как шаблон для создания нового класса. Теперь вводите имя нового класса — CounterTurtle, нажимаете на клави¬ шу Accept, и система создает новый класс, а его имя добавляется к списку классов и становится частью иерархии классов Актора. Второй способ создать новый класс — это написать его программу непосредственно, заново, броузер при этом отодвигается на второй план. Для этих целей используется инструкция ’*inherit”. Так, мы можем написать: inherit(Turtle, #CounterTurtle) Все версии Актора, начиная с версии 2.0 и далее, поддерживают ведение переменных класса. Переменные класса — это такие переменные, которые определяются для класса и могут хранить некоторое значение даже тогда, когда нет ни одного экземпляра класса. В действительности переменная класса принадлежит клас¬ су и, в общем, не зависит от его экземпляров. Один из возможных способов использования переменной класса — это запоминание списка всех его экземпляров. Другой важный вариант использо¬ вания — хранение любой информации, запоминание которой требуется для экземпляров данного класса.
Тренировки в языке Актор 143 ■ НЕМНОГО МУЗЫКИ? Если вы не прочь немного послушать Баха, то выполнение сооб¬ щений включит воспроизведение: А := defaultNew(AboutWindow, "New"); bach(А); ■ ПРОГРАММНЫЕ БЛОКИ Использование блоков в Акторе — это некоторая вариация блоч¬ ной структуры Смолтока. Блок — это ’’кусочек” программы, которому не присвоено никакого имени. Это способ, при помощи которого программа становится чуть более модульной. Каждый блок сам по себе — объект, являющийся экземпляром класса BlockContext. Единственный метод этого класса, который вам в действительности нужен, это метод eval (выполнить). Что он делает? Вы угадали. Он выполняет блоки. Каждый раз, когда какой-либо блок получает сообщение eval, он выполняется. Блоки существенно упрощают специализацию алгоритмов. Всегда, когда имеется набор алгоритмов, решающих примерно одну и ту же задачу, вы можете выделить различия в них и определить их как блоки. Пользуясь этим способом, единственное, что надо сде¬ лать,— это выполнить переключение с одного блока на другой. ■ ФОРМАТЫ ФАЙЛОВ Исходная программа на Акторе может храниться в файлах одного из двух типов. Бывают файлы с расширениями .ACT и .CLS. Последний из них предназначен для определений классов и имеет более сложную структуру. Файлы ACT в основном используются для хранения инструкций Актора и программ, базирующихся на существующих классах. Актор похож на Смолток в отношении использования файлов-образов, сохраняющих полное состоянйё среды разработки, так, что при последующем входе в систему восстанавливается в точности то состояние среды, которое было на момент последнего запоминания образа. Запоминание образа — это ’’мгновенная фотография”, которую можно сделать в любой момент в течение сеанса с Актором. ■ ТРЕНИРОВКИ В ЯЗЫКЕ АКТОР Специально для тех, кто не знаком с Актором, я разработал серию упражнений, которые быстро, в интерактивном сеансе познакомят вас с наиболее важными аспектами языка. Те, кто знаком с Си и Смолтоком, легко увидят, что находятся в привычном окружении. Прежде всего, есть ряд моментов, которые вам следует знать о функционировании интерактивной среды Актора. Вы можете на¬
144 Введение в систему программирования Актор печатать (ввести) имя любого объекта Актора и точку с запятой после него, в ответ на это Актор выдаст общее описание объекта. Для получения более детальной информации надо обратиться к инспектору. Так, например: ThePort; а WorkSpace OutPorts; Set( а WorkWindow ) ■ СЕАНС С КЛАССОМ NUMBER Как и в большинстве объектно-ориентированных языков, выпол¬ нение арифметических действий связано с посылкой сообщений к объектам класса Number, в которых указывается, какие операции следует над ними произвести. Актор использует инфиксную форму записи, примеры которой приводятся ниже: 2 + 2; 4 4 * 5; 20 7 * (4 + (3 * 2) ) ; 70 sqrt(144); 12 exp(2); 7.389056099 4**3; 64 random(666); 47 Следующее определение метода для возведения числа в квадрат для класса Number некорректно. Почему? Def square(self) { self * self; } А вот корректное определение: Def square(self | ans) { ans := self * self; ^ans } Теперь метод square можно использовать для определения других новых методов, например для вычисления суммы квадратов: Def sumsquare(self, other ans) { ans := square(self) + square(other); ^ans }
Циклы 145 sumsquare(3, 4) 25 ■ СТРОКИ Bs := fillWith("b", 22); "bbbbbbbbbbbbbbbbbbbbbb” insetr(Bs, "x", 10); "bbbbbbbbbbxbbbbbbbbbbbb" hash (Bs); 3004 ■ СТРУКТУРЫ УПРАВЛЕНИЯ Актор предоставляет полный набор структур управления, тради¬ ционный для современных языков программирования. Имеются условные и итеративные циклы, перечисление, конструкции вы¬ бора (case) и рекурсия. Шаблоны, которые выдаются в меню Templates, помогут вам, если вы забыли синтаксис одной из управляющих структур. Большинство программистов отлично знакомы с конструкцией итеративного цикла. Следующий раздел продемонстрирует их использование в Акторе. ■ ЦИКЛЫ i : = 0 ; loop while i 10 print (i); i := i + 1; endLoop; As := fillWith("a", 22); "aaaaaaaaaaaaaaaaaaaaaa" i := 0; loop while i 10 As := insert(As, "x", i); i := i + 1; endLoop; ^As; "xxxxxxxxxxaaaaaaaaaaaaaaaaaaaaaa" delete(As, 0, 10); "aaaaaaaaaaaaa" i : = 0 ; loop while i 22 As := insert(As, " ", i); i := i + 1; endLoop; ^As; " aaaaaaaaaaaaaaaaaaaaaa" leadingBlanks(As); 22 leftJustify(As); "aaaaaaaaaaaaaaaaaaaaaa"
146 Введение в систему программирования Актор print(As); aaaaaaaaaaaaaaaaaaaaaa" delete(As, 0, 10); "aaaaaaaaaaaaa" i : = 1; loop while i 45 As := insert(As, "x", i); i : = i + 2 ; endLoop; ^As; "axaxaxaxaxaxaxaxaxaxaxaxaxaxaxaxaxaxaxaxaxax" i := 2; loop while i 67 As := insert(As, "e", i); i : = i + 3 ; endLoop; ^As; "axeaxeaxeaxeaxeaxeaxeaxeaxeaxeaxeaxeaxeaxeaxeaxeaxeaxeaxe axeaxeaxe" i := 3; loop while i 89 As := insert(As, " ", i); i := i + 4; endLoop; ^As; "axe axe axe axe axe axe axe axe axe axe axe axe axe axe axe axe axe axe axe axe axe axe " words(As); OrderedCollection("axe" "axe" "axe" "axe" "axe" "axe" "axe" "axe" "axe" "axe" "axe" "axe" "axe" "axe" "axe" "axe" "axe" "axe" "axe" "axe" "axe" "axe") ■ КОЛЛЕКЦИИ В большинстве объектно-ориентированных систем классы Collection и Container представляют собой ядро набора встроенных, готовых структур данных — ’’снимай с полки и пользуйся”. Актор не является исключением. Ниже приводится ветвь иерархии клас¬ сов Актора, связанная с объектами класса CoUection. Object CoUection Bag bidexedCollection Array Function OrderedCoUection CircularQueue TextCoUection SortedCollection SortedSet
Массивы 147 ByteCollection String Symbol Struct Interval Charbiterval KeyedCollection Dictionary Frame FrameList Slot Сам по себе класс Collection — это абстрактный или формальный класс. Вы ничего не сможете сделать с его экземплярами. Он служит базой для многих классов-наследников коллекций. Когда при помсщи ссобщения new создается член одного нз класса-кол- лекции, вы должны указать аргумент сообщающий максимальное количество элементов в данной коллекции. Однако метод size вернет вам только то количество элементов коллекции, которое в действительности включено в нее, а совсем не максимальный размер, фигурировавший в сообщении new. ■ МАССИВЫ Для работы с массивами в Акторе используют класс Array. Для их создания используют две основные конструкции: A4 := new(Array, 4); AR4 := #(1, 2, 3, 4); Вот так строятся двумерные массивы: AA :=#(#(l,0), #(1,1), #(0,1), #(0,0)); Теперь построим трехмерны:: массив: AAA := #(#(#(1,1,1), #(1,1,0), #(l,0,0)), #(#(0,1,1), #(0,1,0), #(0,0,0)), #(#(1,0,1), #(0,0,1), #(0,0,0))); Массив в Акторе может быть образован из смеси различных типов данных: Mix := #("String", 77, #(а, b, c)); AA2 := copyFrom(AA, 0, 3); Кроме того, вы можете собрать массив из массивов, предваритель¬ но создав массив и затем отправляя сообщение put для вставки одного массива в другой, так как это делается в следующей программе: A1 := #(1, 2, 3, 4); A2 := #(5, б, 7, 8); АЗ := #(9, 10, 11, 12); A4 := #(13, 14, 15, 16);
148 Введение в систему программирования Актор AAAA := new(Array, 4); put(AAAA, A1, 0); put(AAAA, A2, 1); put(AAAA, АЗ, 2); put(AAAA, A4, 3); Для проверки, содержит ли массив в действительности все, что туда было помещено, вы можете использовать сообщение print, чтобы выдать содержимое этого массива. Вот как отправляется это сообщение и вот что возвращается в ответ: print(AAAA); Array (Array ( 1 2 3 4 ) Array(5 6 7 8) Array(9 10 11 12) ■ ДРУГИЕКОЛЛЕКЦИИ У потомков класса Array добавляются свойства, которые не при¬ сущи простым массивам. Так, объекты OrderedCollection (упоря¬ доченная коллекция) имеют две экземплярные переменные firstElement и lastElement (первый и последний элемент). Объекты класса SortedCoUection (отсортированная коллекция) наследуют две эти переменные и имеют еще одну compareBlock. Значение compareBlock определяет то, как будут упорядочены объекты в SortedCollection. Здесь приводится простой интерактивный сеанс работы с SortedCollection: S := new(SortedCollection, 7); add(S, "f"); add(S, ''d") ; add(S, "y"); add(S, "a"); print(S); SortedCollection("а", "d", "f", "у". add(S, "k"); add(S, "c"); add(S, "x"); print(S); SortedCollection("а", "с", "d", "f", "k", ”х", "у") size (S)*; 7 add(S, "q"); size (S); 8 "а" in S; 1 "r" in S; nil findItemIndex(S, "X"); Array(l 6)
Словари 149 remove(S, "x"); "x" size (S); 7 Отметим один важный момент. Coo6njeHnesetCompareBlock позво¬ ляет нам переупорядочить SortedCollection, используя новый кри¬ терий для сравнения двух элементов этой коллекции. Я забежал немного вперед для того, чтобы подчеркнуть, какими мощными возможностями уже обладают эти классы. Теперь вернемся назад и рассмотрим, как отсортированные коллекции (SortedCollection) наследуют некоторые из своих возможностей. ■ УПОРЯДОЧЕННЫЕ КОЛЛЕКЦИИ Класс SortedCollection, который я только что представил в преды¬ дущем разделе, является прямым подклассом OrderedCollection. Это не абстрактные классы. Вы можете использовать их для создания работающих объектов. Рассмотрим в качестве примера какой-нибудь объект, который можно построить из класса OrderedCollection. Ниже следуют две вариации на одну и ту же тему. В каждом из примеров сначала объект инициализируется. Затем к нему добавляются члены. И наконец, используется пере¬ числительный цикл, который обрабатывает все члены и выводит их на экран. Для того чтобы посмотреть эти и другие, созданные вами объекты коллекции, вы можете использовать Инспектор. 0C := new(OrderedCollection, 9); add(OC, "One"); add(OC, "Two"); add(OC, "Three"); do (ОС, { using(i) printLine(i) }); Assassins := new(OrderedCollection, 3); add(Assassins, "Ruby"); add(Assassins, "Ray"); add(Assassins, "Bremer"); do(Assassins, { using(a) printLine(a) });' ■ СЛОВАРИ Здесь приводится простой пример того, как вы можете использо¬ вать объекты Dictionary: Capitals := new(Dictionary, 10); add(Capitals, "USA", "Washington"); add(Capitals, "USSR", "Moscow"); add(Capitals, "Italy", "Rome"); add(Capitals, "France", "Paris"); add(Capitals, "Germany", "Berlin"); add(Capitals, "Japan", "Tokyo"); add(Capitals, "China", "Peking"); add(Capitals, "Sweden", "Stockholm"); add(Capitals, "England", "London");
150 Введение в систему программирования Актор add(Capitals, "Greece", "Athens"); at (Capitals, "USA"); "Washington" MethodDictionary хранит пары ’’ключ/значение”, поиск которых основывается на принципе эквивалентности, а не равенства. Этот подход по сравнению с Dictionary более эффективный как в смысле времени, так и пространства, хотя и является менее общим. Поскольку поиск ключей ведется с учетом эквивалентности, а не равенства, это ограничивает применимость MethodDictionary кру¬ гом объектов, для которых эквивалентность имеет смысл, для таких, как Char, bit и Symbol. ■ ТЕКСТОВЫЕ КОЛЛЕКЦИИ Экземпляры класса TextCollection являются отсортированными коллекциями строк. Ниже приведен пример, в котором показано, как можно использовать объекты TextCollection для того, чтобы собирать и разбирать различные слова и предложения из коллек¬ ции строк. Тс := new(TextCollection, 12); add(Tc, "What"); add(Tc, "goes"); add(Tc, "on"); add(Tc, "in"); add(Tc, "your"); add(Tc, "heart"); makeString(Tc); "What goes on in your heart" add(Tc, "mind"); makeString(copyFrom(Tc, 0, 6)); "What goes on in your heart" ■ ОЧЕРЕДИ В комплекте поставки Актора распространяется класс CircularQueue. Объекты CircularQueue можно использовать там, где обычно применяются структуры данных с круговыми (цикли¬ ческими) очередями. Следующий пример показывает, как можно использовать объект CircularQueue для представления повторяю¬ щегося недельного цикла: WeekDays := new(CircularQueue, 7); add(WeekDays, "Monday"); add(WeekDays, "Tuesday"); add(WeekDays, "Wednesday"); add(WeekDays, "Thursday"); add(WeekDays, "Friday"); add(WeekDays, "Saturday"); add(WeekDays, "Sunday"); do(WeekDays, {using(i) printLine(i)});
Переменные класса 151 Последнее сообщение — это перечислительный цикл, который охватывает все сущности, входящие в CircularQueue, и выдает их на экран. Ниже приводится интерактивный сеанс, в котором используется объект WeekDays для демонстрации некоторых фун¬ кций и свойств объектов класса CircularQueue: size(WeekDays) ; 7 next (WeekDays) ; "Monday" next (WeekDays) ; "Tuesday" next (WeekDays) ; "Wednesday" next(WeekDays) ; "Thursday" next(WeekDays) ; "Friday" next (WeekDays) ; "Saturday" next(WeekDays) ; "Sunday" WeekDays[0]; "Monday" WeekDays[6]; "Sunday" ■ ПЕРЕМЕННЫЕ КЛАССА В системе Актор переменные класса используются весьма редко. К небольшому числу использующих их классов принадлежат: ErrorHandler, File и его потомки, ActorAnalyzer, System, VarList, Dialog и его потомки, AboutWindow, ClockWindow и ToolWindow и его потомки. Для чего обычно используются переменные класса? Во-первых, для хранения того, что требуется для всех экземпляров класса и никогда не изменяется. Во-вторых, для тех значений, которые могут изменяться, но изменяются одновременно для всех экземпляров класса. Таким образом, переменные класса — это совсем не те величины, которые пользователь или программист может менять, не особенно задумываясь, следуя своей прихоти. По этой причине в Акторе используют соглашение о том, что их имена начинаются с символа $, как, например, переменная класса $Dialogs в классе Dialog. Один из интересных вариантов использования переменной класса переменная $Clocks в классе ClockWindow. Это переменная хранит множество всех экземпляров данного класса. Несколько позже мы рассмотрим механизм, который используется ClockWindow.
152 Введение в систему программирования Актор ■ ВЫЗОВ БИБЛИОТЕК С ДИНАМИЧЕСКИМ СВЯЗЫВАНИЕМ Для доступа к библиотекам с динамическим вызовом системы Windows — DLL fDynamic Link Libraries) в Акторе имеется класс Library, который в свою очередь для доступа к возможностям DLL использует класс Proc. Proc является потомком класса Struct. При использовании Library для доступа к DLL-библиотекам вам при¬ дется создать отдельный экземпляр класса для каждого библио¬ течного файла, который вам нужен. Экземпляры Library связывают некоторое имя с файлами DDL и определяют отдельные СЛовари (каталоги) процедур. Для записи имени в экземплярную переменную используется метод setName класса Library. Например, для того чтобы инициализировать экземпляр Library для использования PBRUSH.DDL, модуля, который поставляется вместе с Windows, вы должны были бы сделать что-нибудь подо- бйое тому, что написано ниже, при этом, вероятно, изменив н&звание драйвера и маршрут (pathname): pbLib := new(Library); setName(pbLib, "e:\windows\pbrush.dll"); После этого надо создать экземпляры Proc для каждой функции, которую вы собираетесь использовать. Это делается при помощи сообщения add, которое создает новый экземпляр Proc и вводит имя процедуры и дескриптор аргументов в объект Library. Library имеет свое собственное сообщение load, которое используется для загрузки модулей из DLL файлов. В действительности вызов процедур DLL состоит в посылке сообщений pcall к нужным экземплярам Proc. Перед выходом из Актора или из приложения, построенного при помощи Актора, необходимо явно закрыть DLL- модуль при помощи сообщения free. Используя DLL-библиотеки, вы должны каждый раз загружать и освобождать нужные вам модули, что несколько неудобно, однако это не слишком большая цена за то разнообразие программ, которое становится вам доступно. ■ ПРЕДСТАВЛЕНИЕ ЗНАНИЙ Актор включает в себя набор классов, которые определяют язык представления фреймов (Frame Representation Language — FRL), весьма полезный для представления знаний. Эта возможность обеспечивается классами Frame, FrameList и Slot. Frame, FrameList и Slot являются потомками класса Dictionary. Их на¬ значение — это представление структур знаний, организованных в форме фреймов, слотов и граней. Экземпляры класса FrameList предоставляют словари всех фреймов, которые будут использо¬ ваться в данном приложении. Объекты FrameList — это словари
Представление знаний 153 фреймов, объекты Frame — это словари слотов и объекты Slot — это словари граней. Грани не представлены в Акторе отдельным классом, они могут быть любым классом или объектом. Первое, что нужно сделать для создания базы знаний на FRL, — создать новый экземпляр класса FrameList. В примере, который мы будем рассматривать, используется FRL для хранения цепочки фактов истории. Инструкция для создания экземпляра класса FrameList выглядит так: History := new(FrameList, 12); set(History, #event, #date, #location, #principal,); set(History, #event, #time, #value, nil); setValue(History, #event, #value, #ElectionofChairman); ^Z setValue (History, #ElectionofChairman, #date, date(new(Date), 11, 7, 1992)); В последней инструкции значение слота #date задается путем создания экземпляра класса Актора Date, который предоставляет свой собственный формат для хранения дат и программы для манипулирования ими. getValue(History, #ElectionofChairman, #date); 11-7-1992 setValue (History, #ElectionofChairman, #location, "San Francisco); setValue(History, #ElectionofChairman, #time, time(new(Time), 13, 00, 0)) ; 13:00:00 setValue(History, #event, #value, #SigningofContract); setValue(History, #SigningofContract, #date, date(new(Date), 1, 1, 1993)); dayOfWeek(getValue(History, #ElectionofChairman, #date)); "Tuesday" dayOfWeek(getValue(History, #SigningofContract, #date)); "Wednesday" asDayString (getValue(History, #SigningofContract, #date)); "January 1, 1993" asLongString(getValue(History, #SigningofContract, #date)); "January 1, 1993" asDayString(getValue(History, #SigningofContract, #date)); "Friday, January 1, 1993"
154 Введение в систему программирования Актор Я ОТЛАДКА В результате ошибки в программе, выполняющейся в системе Актор, открывается диалоговое окошко, содержащее состояние стека истории (вызовов) на момент обнаружения ошибки. Кроме того, диалоговое окошко обычно будет содержать сообщение, ди¬ агностирующее тип ошибки. Если хотите, вы можете при появле¬ нии диалогового окошка, вызванного ошибкой, нажать на клавишу ’’debug” и тем самым открыть окно Debug. Это весьма мощный инструмент отладки, сочетающий в себе некоторые воз¬ можности броузера, инспектора, а также средств для изменения любых значений, связанных с некоторым методом. При помощи этого инструмента вы можете продолжать работу, на ходу попра¬ вив обнаруженную ошибку. ■ ПРОФАЙЛЕР Работая в Акторе, вы в любое время можете провести измерения вашего приложения, построить его ’’профиль”, выполнив сообщение: profile(System); Начиная с этого момента Актор будет собирать информацию о том, сколько раз вызывался каждый из методов. Такое же сооб¬ щение profile(System) используется и для того, чтобы остановить сбор информации о профиле. Когда сообщение будет послано во второй раз, собранные данные будут автоматически записаны в файл PROFTLE.DAT. ■ ВЫПОЛНЕНИЕ ВНЕШНИХ ПРОГРАММ Внешние программы можно выполнять при помощи метода exec класса String, например: exec ("D:\execl\execl\.exe"); Метод exec получает аргументы командной строки, так что можно выполнить и такую команду: exec("D:\execl\execl d:\execl\library\goalseek.xlm"); Результат метода exec состоит в том, что управление передается указанному приложению. Оно сначала загружается, а потом вы¬ полняется. Для того чтобы вернуться назад, пользователь должен выйти из внешнего приложения. Класс Behavior в Акторе является корневым классом для мета¬ классов, так же как Object — для обычных классов.
Класс Application 155 ■ "РАСПЕЧАТЫВАНИЕ” ПРИЛОЖЕНИЙ Постольку поскольку Актор компилируется инкрементально, тре¬ буется, чтобы программы Актора были обеспечены соответствую¬ щим окружением для своего выполнения. Вместо "линкования” федактирования связей) скомпилированных программ с библио¬ теками поддержки времени исполнения предоставляется другой метод, обеспечивающий предварительную обработку нужных при¬ ложений. В Акторе он называется ’’распечатыванием” (seaUng- off). Для облегчения этого процесса в Акторе имеется меню Seal-off. Выбранное при помощи мыши, это меню открывает диалоговое окно, которое содержит шаблон для ввода информа¬ ции, необходимой для процесса распечатывания. Вы должны ввести имя класса приложений, имя образа, который вы собирае¬ тесь использовать, и параметры для его статической и динамиче¬ ской памяти. ■ КЛАСС APPLICATION В предыдущем параграфе я упоминал, что средство распечатыва¬ ния требует введения имени класса приложения. Нужно сделать некоторые пояснения, поскольку отнюдь не каждый класс позво¬ ляет использовать эту возможность корректно. Обычно приложе¬ ние включает в себя несколько классов. Но для того чтобы приложение можно было распечатать, оно должно быть подклас¬ сом специального класса, называемого AppHcation. Рассмотрим некоторые из возможностей этого класса. Класс Application имеет две экземплярные переменные ’mainWindow’ и ’conunandLine’. Значения этих экземплярных переменных сообщают средствам распечатывания имя главного окна приложения и аргумент ко¬ мандной строки, которая будет посылаться в метод init данного класса. Этот и другие методы класса не касаются того, что делает приложение, однако они связаны с процессом распечатывания и обеспечением функционирования в среде выполнения. Вызывающие функции Windows Ниже приводится пример вызова функции Windows из метода createMenu класса Windows: Def createMenu(self) { if setMenu(self, Call CreateMenu()) then Call DrawMenuBar(getHWnd(self)) else ^nil endif; } Еще более простой пример — широко используемый метод класса Window Object:
156 Введение в систему программирования Актор Def show(self, val) { Call ShowWindow(getHWnd(self), val); update(self); } В методе appendItem класса Menu имеется еще один вариант использования вызовов Windows-функций: Def appendItem(self, menuItem, hM pC hPopup) { if pC := popupCall(menuItem) then hPopup := Call CreateMenu(); do(pC, {using(mi) appendItem(self, mi, hPopup); }); Call AppendMenu(hM, MF_POPUP, hPopup, asciiz(text(menuItem))); else addToMenu(menuItem, hM); setAction(self, id(menuItem), actionSym(menuItem)); endif; } Сначала при помощи вызова функции CreateMenu создается пози¬ ция меню, ей дается название hPopup. Затем, используя hPopup как аргумент, вызывается функция AppendMenu. Позиция меню, связанная с временной переменной hPopup, создается и подклю¬ чается к меню, которое посылало сообщение. ■ ДОБАВЛЕНИЕ ПРИМИТИВОВ В АКТОР Primitive — это встроенный метод, эффективность которого объ¬ ясняется тем, что он написан непосредственно в машинных кодах, а не компилируется из программ Актора. Актор допускает добав¬ ление примитивов, написанных на языке ассемблера или на Си. Группа Whitewater предоставляет также следующие пакеты под¬ держки: The Actor Extension — расширение Актора; The Resource TooMt — пакет работы с ресурсами; Object Graphics — объектная графика; The Wintrieve Database Toolkit — база данных Wintrieve; Actor vs SmalltaDc — Актор против Смолтока. Между Смолтоком и Актором так много общего , что у каждого может возникнуть вопрос, зачем тратить так много усилий на создание нового языка, когда реализации Смолтока уже сущест¬ вуют. Имеются две основные: причины. Во-первых, Актор проек¬ тировался совместимым с коммерческой операционной средой на ШМ PC, следовательно, как внутренне совместимый с Microsoft Windows. Во-вторых, синтаксис Актора разработан с таким расче¬ том, чтобы он был ближе тем программистам, кто работал на Си и на Паскале. Короче говоря, Актор предназначен для того, чтобы
Добавление примитивов в Актор 157 дать систему, включающую в себя все хорошее, что есть в Смол- токе, переоформленное таким образом, чтобы отвечать специфи¬ ческим потребностям сегодняшних пользователей IBM PC. И последнее, но отнюдь не маловажное обстоятельство состоит в том, что Актор работает под управлением MS-Windows. Смолток же обычно пользуется своим собственным пользовательским интер¬ фейсом и своей оконной системой. До сих пор я избегал обсуждения одного из аспектов языка Актор, а именно темы множественного наследования. В системах, рабо¬ тающих с реальными проблемами, множественное наследование во все большей степени будет становиться стандартной возможно¬ стью. Причина здесь проста. В реальном мире многие предметы наделены множеством ролей, выполняют несколько функций. Множественное наследование указывает прямой путь для реали¬ зации этого свойства. Поставщики объектноюриентированных инструментов, которые отказываются от включения средств мно¬ жественного наследования, обычно ссылаются на то, что не хотят делать систему слишком сложной для пользователей, или на то, что никто из потребителей не заказывал таких возможностей. Я не нахожу эти ответы особенно убедительными. Существует еще одна точка зрения: я не встречал особых трудностей в использова¬ нии множественного наследования в тех системах, где оно есть, и с трудом представляю себе серьезную объектно-ориентированную программу без него. В ответ на утверждения такого рода можно сказать, что в системе без множественного наследования по-преж¬ нему есть возможность создавать классы с одинаковыми опреде¬ лениями; это тяжелый путь, но он позволяет выразить то, что вы хотите. Мое отношение к этому таково: что возможно в теории, никогда не делается на практике. За 5 лет работы с объектноюри- ентированными системами мне пришлось лишь раз или два рабо¬ тать в системах без множественного наследования, в то время как работа с системами, имеющими его, — самое обычное дело. Причина этому та же, что делает важными интерактивные систе¬ мы типа Актор, а не только простое удобство. Чем проще можно сделать базовые вещи, тем больше возникает стремление творче¬ ски подойти к более сложным областям, пытаясь сделать то, за что в другом случае вы, возможно, и не взялись бы. Если вы пользователь объектно-ориентированных средств, которые лише¬ ны множественного наследования, я настоятельно рекомендую попытаться уговорить продавца включить его первым номером в список новых качеств системы. Я уверен, что вы не пожалеете о том, что воспользуетесь здесь своим правом покупателя.
158 Введение в систему программирования Актор Л ИЗМЕРЕНИЕ ВРЕМЕНИ Важность возможностей работы со временем особенно проявляется в операционной среде типа Windows. Кроме того, это область, где преимущества объектно-ориентированкого программирования прямо бросаются в глаза. Во многих операциях системного уровня объектно-ориентированный подход применим лишь отчасти. В качестве иллюстрации можно привести класс Актора System, в котором имеется целый ряд системных функций, которые плохо выразимы в терминах посылки сообщений объектам. По этой причине все методы из System являются методами класса, а не методами экземпляров класса. С другой стороны, оформление временных записей в виде объектов, которые заключают в себе все необходимое для их работы с ними, в действительности представ¬ ляет собой классический пример, демонстрирующий преимущест¬ ва объектно-ориентированного подхода. Пакет Windows API предоставляет в качестве ограниченного ре¬ сурса Timer. Timer используют несколько инструментов Windows для обеспечения "рабочего стола”, средл них такие, как Clock, Calendar, Terminal и Control Panel. Эти средства всегда доступны для непосредственного вызова из функций Windows. Вызов функ¬ ции GetCurrentTime() не требует аргументов и возвращает время Windows — число миллисекунд, прошедших с момента загрузки системы. GetTickCount () Функция Windows SetTimer создает события системного таймера. Windows использует сообщение WM_TMER для ответа на вызов функции SetTimer, сделанный любым приложением. Формат ар¬ гументов для SetTimer таков: SetTimer(hWnd, nIDEvent, wElapse, lpTimerFunc) Аргумент hWnd может опускаться. Он связывает таймер со спе¬ циальным окном путем указания его дескриптора. Если hWnd не NULI то для указания временного интервала модификации тай¬ мера используется nffiEvent. Аргумент lpTimerFunc служит для передачи в Windows имени callback функции, которой будут посылаться сообщения WM_TMER. Функция SetTimer возвраща¬ ет значение, показывающее, создавался ли ранее таймерный ре¬ сурс. Если возвращается ноль, то ресурс не создавался. Когда таймерный ресурс больше не нужен, для его завершения можно использовать функцию KillTimer. Формат KillTimer таков: KiIlTimer(hWnd, nIDEvent) Когда параметр hWnd, используемый при вызове SetTimer, равен Null, значением аргумента nK)Event, используемого для вызова KiUTimer, служит величина, возвращаемая из SetTimer.
Работа с датами и временем 159 ■ РАБОТА С ДАТАМИ И ВРЕМЕНЕМ Классы Time и Date предоставляют множество услуг для работы с форматами, которые используются в записях, хранящих время. Новый объект Date можно создать вручную следующим образом: dayl := date(new(Date), 7, 27, 90); 7-27-90 Класс Date содержит программы, которые позволяют узнать, на какой день недели приходилась или будет приходиться некоторая дата в прошлом, настоящем или будущем. Для того чтобы это сделать, надо послать сообщение dayOfWeek любому объекту-дате примерно таким образом: dayOfWeek(dayl); "Wednesday" Сообщение dayOfWeekNumber можно послать для того, чтобы получить число, порядковый номер дня в неделе вместо строки с текстом. В данном примере вместо "Wednesday” (среда) возвраща¬ ется число 3. dayOfWeekNumber(dayl); 3 Аналогичным образом, в той же манере, в какой класс Date производит вычисления, необходимые для корректной работы с датами, класс Timer предоставляет средства для работы с форма¬ тами данных, используемых для записи времени дня и ночи. Вручную создать объекты Time можно при помощи посылки сообщения такого типа: Т1 := time(new(Time), 12, 30, 0); 12:30:00 Обычный временной формат можно преобразовать в формат вре¬ менного массива, использовав сообщение asTimeArray: asTimeArray(T1); Array(12 30 0 nil) Как видно из ветви иерархии классов, приведенной ниже, Date и Time являются классами одного уровня, и оба происходят из класса Long. Object Magnitude Number Long Date Time
160 Введение в систему программирования Актор Внутреннее представление дат — это ’’длинное число”, которое соответствует числу дней, прошедших со времени введения Гри¬ горианского календаря (вероятно, имеется в виду число дней, прошедших с начала нашего летоисчисления. — Прим. nepee.). Интересцо сравнить реализацию классов Time и Date, поскольку они похожи друг на друга, хотя используются иногда совершенно по-разному. Наибольшее различие, конечно, состоит в том, что классу Time приходится иметь дело с временными интервалами вплоть до секунды, а классу Date — только до дня. Эти два класса так близки по замыслу и реализации, что набор их методов практически идентичен. Методами, которые в точности совпада¬ ют, являются: asLong, set, printOn и методы объекта sysPrintOn и метод класса new. Даже текущие методы класса так схожи, что различаются лишь значениями данных. Альтернативным вариан¬ том, способным извлечь преимущества такого близкого сходства этих классов, могло бы стать создание формального класса TemporalObject (временной объект), включающего в себя все мето¬ ды, идентичные в обоих классах. Тогда Date и Time были бы потомками TemporalObject и дополнительно включали бы только специфические для каждого из них методы. Имеющийся метод класса можно было бы реализовать в формальном классе, посколь¬ ку изменяемое значение могло бы реализовываться как перемен¬ ная класса. Но особого практического интереса в данном частном случае эта возможность не представляет, так как все равно потре¬ бовались бы различные дополнительные методы для инициализа¬ ции переменных класса. Класс объектов Temporal (временной) inherit(Long, #TemporalObject, nil, 1, nil)!! now(class(TemporalObject))!! /* Создать новый Data4>6beKT и инициализировать*/ Def new(self) { ^init(new(self:Behavior)); }!! now(TemporalObject)!! /* Отправить (положить) данные в указанный поток */ Def sysPrintOn(self, aStrm) { printOn(asString(self), aStrm) }!! /* Присваивает seLf полученные значения в форме индексированной величины */ Def set(self, value temp) { temp := new(Struct, 4); putLong(temp, value, 0); setClass(temp, class(self)); swapProperties(self, temp); swap(self, temp); }!!
Работа с датами и временем 161 /* Отправляет данные в указанный поток */ Def printOn(self, aStm) { printOn(asString(self), aStrm) } ! ! /* Возвращает seLf как kmg-число для использования в арифметических операциях */ Def asLong(self temp) { ^setClass(temp := copy(self), Long) } ! ! Класс таймер inherit(TemporalObject, #Timer, nil, 1, nil)!! now(class(Timer))!! /* Возвращает текущее время */ Def current(self ds) { ds := new(Struct, 18); putMSB(ds, 0x2c, 4); call(ds); ^time(new(self), atMSB(ds, 8), /* Часы */ atLSB(ds, 8), /* Минуты */ atMSB(ds, 10) /* Секунды */ }!! now(Timer)!! /* Нормализовать время, свести его к моменту времени в единичном 24-х часовом периоде */ Def- normalize(self) { set(self, asLong(self) mod 86400) } ! ! /* Определить временной объект на основании переданного параметра */ Def time(self, hr, min, sec) { set(self, (hr*3600)+(min*60)+sec) }N /* Инициализировать время (момент 0:00:00) */ Def init (self) { set (self, 0L); }!! /* Возвратить массив из 4-х элементов — часы, минуты, секунды, превышение. Превышение указывает, сколько в указанном времени содержится полных 24 часа. Время, выходящее за границы полных суток, должно быть нормализовано */ Def asTimeArray(self hour, min, sec, over, time) { if (time := (asLong(self) mod 86400) ) self /* больше, чем 24 часа */ then over := asLong(self) / 86400; endif; hour := time / 3600; min := time " (hour * 3600); sec := min mod 60; min := min / 60; ^tuple(asInt(hour), asInt(min), asInt(sec), over) } ! ! 6-857
162 Введение в систему программирования Актор /* Преобразует self во временной объект */ Def asTime(self) { ^self } ! ! /* Преобразует Timer-объект в строку */ Def asString(self ta) { ta := asTimeArray(self); ^asString(ta[0]) + ":" + asPaddedString(ta[l], 2) + ":" + asPaddedString(ta[2], 2) } ! ! Класс Dating (для представления дат) inherit(TemporalObject, #Dating, nil, 1, nil)!! now(class(Dating))!! /* Возвращает текущую дату */ Def current(self ds) { ds := new(Struct, 18); putMSD(ds, 0x2a, 4); call(ds); ^date(new(self), atMSB(ds, 10), /* Месяц */ atLSB(ds, 10), /* День */ wordAt(ds, 8) /* Год */; } ! ! now (Dating) ! ! /* Для данной даты возвращает предыдущую дату, не изменяя seLf */ Def Previous(self) { ^asDate(self " 1) }!! /* Для данной даты возвращает предыдущую дату, не изменяя seLf */ Def Previous(self) { ^asDate(self + 1) } ! ! /* Пределом объекта-дата является 2 */ Def limit(self) { ^2 }!! /* Инициализирует все новые даты как l/l/l980 */ Def init(self) { ^date(self, 1, 1, 1980); }!! /* Возвращает номер дня в неделе (0 — воскресенье, ...6 — суббота) */ Def dayOfWeekNumber(self) { ^asInt(asLong(self) + 4L) mod 7) } ! ! /* Возвращает день недели */ Def dayOfWeek(self) { ^#("Sunday" "Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday")[dayOfWeekNurnber(self)3
Работа с датами и временем 163 /* Возвращает новый объект-дату с переданными значениями месяца, дня и года. Дата вычисляется, путем прибавления к первому дню года month месяцев и day дней, т.е. date(newflDate), 11, 31, 1990) возвращает 12-01-1990. За основу здесь взят алгоритм 199 из фонда алгоритмов ACM */ Def date(self^ month, day, year centure) { if month 2 then month := month ^ 3; else month := rtionth + 9; year := year " 1; endif; centure := year / 100; year := year ^ (centure * 100); ^set(self, ((asLong(centure) * 146097L) / 4L) + ((asLong(year) * 1461L) / 4L) + ((asLong(month) * 153L) + 2L) / 5L) ' + (asLong(day) ^ 578041L)); }! ! /* Возвращает дату в сокращенном строковом формате */ Def asString(self da) { da := asDateArray(self); ^asString(do[0^) + "-" + asString(do[l]) + "-" + asString(do[2]) } ! ! /* Возвращает дату в длинном строковом формате */ Def asLongString(self da) { da := asDateArray(self); ^#("January" "February" "March" "April" "May" "June" "July" "August" "September" "October" "November" "December")[da[0]-l]+ " " + asString(da[l]) + "," + asString(da[2]) }!! /* Возвращает дату в формате ”день недели” */ Def asDayString(self) { ^dayOfWeek(self) + "," + asLongString(self) } ! ! /* Возвращает массив с тремя элементами — месяц, день, год [алгоритм 199 из фонда алгоритмов ACM] */ def asDateArray(self gregorian, month, day, year) { gregorian := asLong(self) + 578041L; year := ((gregorian * 4) ^ 1) / 146097L; gregorian := ((gregorian * 4) ~ 1) / 146097L; day := (gregorian / 4); gregorian := ((day * 4) + 3 ) / 1461; day := ((day * 4) + 3) ^ (gregorian * 1461); day := (day + 4) / 4; month := ((day * 5) ^ 3) / 153; day := ((day * 5) ^ 3) ^ (month * 153); day := (day + 5) / 5; year := (year * 100) + gregorian; if month 10 then month := month + 3; else month := month ^ 9;
164 Введение в систему программирования Актор year := year + 1; endif; ^tuple(asInt(month), asInt(day), asInt(year)) }!! /* Преобразует полученное число в дату */ Def asDate(self) { ^self } ! ! dayl := date(new(Dating), 3, 15, 90); 8-15-90 T1 := time(new(Timer), 12, 30, 0); 12:30:00 ■ ЧТО ТАКОЕ ВРЕМЯ? В последующих разделах главы вы познакомитесь с примерами, которые демонстрируют, как легко иметь дело со временем в объектно-ориентированных системах типа Актор и насколько удо¬ бен объектно-ориентированный подход к такого рода сущностям. Прежде всего — маленькие цифровые часы, которые вы можете включить в свою среду разработки, если уверены, что класс ClockWindow загружен, выполнив сообщение: CW :=.defaultNew(ClockWindow, "Current Time"); show(CW, 1); Пример класса ClockWindow показывает, как легко реализовать цифровые часы в Windows при использовании Актора. Как потом¬ ки класса TextWindow объекты класса ClockWindow предоставля¬ ют выдачу текущего времени в символьном виде. Одной из специфических особенностей ClockWindow является возможность синхронизации нескольких различных экземпляров класса при помощи отслеживания интервалов изменения времени на некото¬ рой регулярной основе. Для того чтобы обеспечить это, ClockWindow использует переменные класса. Всю необходимую для простой модифицирующей процедуры информацию дают пе¬ ременные класса $biterval и $Clocks. В переменной $biterval хранится интервал времени между модификациями объектов ClockWindow. Переменная $Clocks хранит имена всех имеющихся в данный момент экземпляров ClockWindow. ■ ЦИФРОВЫЕ ЧАСЫ У класса ClockWindow есть несколько интересных возможностей для выдачи правильного времени. Инициализация класса состоит из следующих строк: $Interval := 5; $Clocks := new(Set, 4);
Цифровые часы 165 Это означает, что показания часов будут обновляться каждые пять секунд и что переменная класса $Clocks инициализируется как пустой экземпляр класса Set и имеет размер, позволяющий запо¬ минать до четырех объектов. Каждые пять секунд — это не достаточно часто для многих применений, в частности, если имеются несколько часов. CW1 := new(ClockWindow, nil, n.il, "First Clock", &(50,50,175,125)); show(CW1,1); CW2 := new(ClockWindow, nil, nil, "Second Clock", & (5 0, 110, 17 5,17 5) ) ; show(CW2,1); CW3 := new(ClockWindow, nil, nil, &(50, 170, 175, 125)) ; show(CW3,1); "Third Clock" CW4 := new(ClockWindow, nil, nil, "Fourth Clock", & (50, 230,175,2 95));' show(CW4,1) ; Если вас беспокоит, что не всегда на часах одно и то же время, выполните сообщение: setInterval(ClockWindow, 0) ; Далее показывается, как описан метод init для ClockWindow: Def init (self) { init(self:ancestor); add($Clocks, self); tick(self); setTimer(self, $Interval); } Def close(self) { killTimer(self); remove($Clocks, self); close (self:ancestor); } Как видите, этот метод задает значения для обеих переменных класса ClockWindow. Можно сделать простое расширение ClockWindow, если добавить метод класса new, который позволяет описывать часовой пояс. Таким образом можно будет обеспечить синхронизацию для нескольких часов в различных часовых поя¬ сах. Новый метод класса можно описать так: Def new(self, parent, menu, windowName, rect, timeZone aClock) { aClock := new(self:WindowClass, parent, menu, windowName, rect); aClock[timeZone] := ((timeZone mod 24) + 24) mod 24; ^aClock; }
166 Введение в систему программирования Актор Функция .SetTimer MS Windows реализует таймер в среде Windows, являющийся глобальным ограниченным ресурсом. Функции Windows для доступа к системному времени и дате очень просты, они совсем не требуют никаких аргументов. Call GetCurrentTime(); Call GetTickCount(); ОПИСАНИЯ КЛАССОВ ДЛЯ РАБОТЫ СО ВРЕМЕНЕМ ClockWindow Исходный файл: Наследует из: Переменные класса: $biterval $Clocks Методы класса: setbiterval sizeRect Методы объектов: close drawTime init killTimer setTimer tic CLOCKWIN. CLS TextWindow Устанавливает интервал часов (число секунд). Интервал указывает, как часто следует обновлять выдачу для каждого из объектов, реализующих окно-часы. Когда интервал завершается, каждый экземпляр часов информируется, с тем, чтобы он мог модифицировать свой временной интервал Возвращает калиброванный прямоугольник. Изменяет размеры калиброванного окна так, что оно становится просто четвертью от окна с размерами, задаваемыми по умолчанию Выключает таймер и затем закрывает окно Рисует время в окне TextWindow, перезаписывая строку, нарисованную ранее Уничтожает таймер Запускает таймер. Он будет посылать сообщения WM_TMER каждые n секунд, n — аргумент метода. Если нужны миллисекунды, то проявите изобретательность Обрабатывает такт (tick) таймера. Берет текущее время и выдает его
Описания классов для работы со временем 167 timeStr Возвращает текущую строку со временем. Вызывает функцию DOS GetTime. Прерывание 21H функция 2CH update Модифицирует интервал таймера. Переустанавливает таймер получателя на такт каждые n секунд в соответствии со значением переменной класса $brterval STOPWATC. CLS Object экземпляров: Время начала (старта) выполнения блока Время конца выполнения блока StopW а^Ь(секундомер) Исходный файл: Наследует из: Переменные startTime stopTime Методы объектов: lap Возвращает время между временем старта и текущим моментом start Запускает секундомер и записывает время старта stop Останавливает секундомер и возвращает общее прошедшее время С1оск(часы) Примечание. Этот материал заимствован из приложений, написанных Джимом Кауторном. Описание класса приводится здесь с целью обучения. Исходный файл: CLOCK.CLS Наследует из: Window Переменные экземпляра: brForeground penBackground penForeground bIconic aspectD aspectN clockRect clockRadius clockCenter VertRes HorzRes MapSize TimerID oTime n Time ’’Кисть” для переднего плана ”Перо” для заднего плана ”Перо” для переднего плана Не равно nil, если приложение свернуто в пиктограмму Масштабный множитель Номер Timer-ID Последнее считанное время Самое новое считанное время
168 Вввдвнив в систему программирования Актор timeZone IconDrawMode dayligth Методы объектов: clockPaint clockSize clockTimer compClockDim create createTools deleteTools drawFace drawFatHand drawHand drawHandsIcon drawHandsWnd killTimer paint setTimer show Число часов, которое надо прибавить к времени (для получения поясного времени — Прим. nepee.) Рисовать изображение часов непосредственно на экран или сначала в bitmap-буфер, а потом на экран? Не равно nil, если дневное время, и nil, если ночное Главный метод для рисования и перерисовки часов Дает информацию, в каком состоянии находятся часы, в виде пиктограммы и в виде обычного окна Управляет сообщениями таймера, посланными к команде часов Вычисляет размерности часов Решает создавать POPUP или TILED (паркетное) окно Строит перья и кисти для рисования часов Уничтожает перья и кисти Рисует циферблат в форме окружности из черточек (рисок) и точек Рисует часовую и минутную стрелки, когда часы представлены в виде окна (не пиктограммы) Рисует секундную стрелку или минутную и часовую стрелки, когда часы представлены пиктограммой Рисует минутную и часовую стрелки в пиктограмме Рисует минутную и часовую стрелки в окне Уничтожает системный таймер Перекрашивает экран Устанавливает системный таймер Выдает показание часов в соответствии со значением val. Важен порядок событий. Часы должны иметь свое уже "показанное” окно до того, как к нему будет присоединяться окно ошибок. Перед исполнением ’show’ инструменты должны быть уже созданы
Дополнительный пример программ 169 ■ ДОПОЛНИТЕЛЬНЫЙ ПРИМЕР ПРОГРАММ В листинги 1 — 4 включены дополнительные примеры программ, три из которых по типу близки к эталонным программам для проведения измерений производительности: простые числа, числа Фиббоначчи и Ханойские башни; последняя программа содержит некоторые расширения для обработки списков. Поскольку эти алгоритмы реализовывались так много раз и на таком множестве широко распространенных языков программирования, то для опытного программиста будет нетрудно быстро разобраться, как же использовать Актор там, где он пользуется своим любимым языком. Когда вы изучите Актор достаточно основательно, то придете к заключению, что он поддерживает все известные вам конструкции, а также и некоторые другие. Листинг 1. Эталонная программа ”Решето Эратосфена” inherit(Object, #Sieve, nil, nil, nil)); now(Sieve); /* Возвращает число простых чисел между 0 и cnt, включительно */ Def sieve(self, cnt flags, count, с) { с := cnt + 1; flags := new(Array, с); fill(flags, true); count := 1; do( over(2, с), { using(i triple) if flags[i] then triple := i*3; if triple cnt then do( overBy(triple-1, с, i+i-1), { using(j) flags[j] := nil }); endif; count := count + 1 ; endif; }) ; Acount; } Acto.r[#Sam] := new(Sieve) /* Для запуска надо напечатать: sieve(Sam, 100) */ Листинг 2. Программа Фиббоначчи now(Int) /* Рекурсивный метод поиска n-ro числа Фиббоначчи. Преднамеренно выбран крайне неэффективный способ поиска чисел Фиббоначчи, каждое сообщение "размножается”, порождает рекурсивно два других */ Def fib(self) { if self 3 then ^1 endif; Afib(self " 1) + fib(self ^ 2); }
170 Введение в систему программирования Актор /* Итеративный метод поиска n-го числа Фиббоначчи */ Def fib2(self term termlBefore, term2Before) { if self 3 then ^1 else term := 2; termlBefore := 1; trem2Before := 1; do(new(Interval, 3, self + 1, 1), {using(i) term:= termlBefore + term2Before; term2Before := termlBefore; termlBefore := term; }); endif; } Листинг 3. Ханойские башни /* См. Byte, август 1986, с. 146, cbd 13.8.86 */ inherit(Object, #TowerOfHanoi, nil, nil, nil); now(NowerOfHanoi); Def moveTower(self, height, from, to, use) { if height 0 then moveTower(self, height ^ 1, from, use, to); moveTower(self, height ^ 1, use, to, from); endif; } Def moveTower2(self, height, from, to, use) { if ’height 0 then moveTower(self : TowerOfHanoi, height ^ 1, from, use, to); moveTower(self : TowerOfHanoi, height ^ 1, use, to, from); endif; } Actor[#Hanoi] := new(TowerOfHanoi); /* пример запуска программы для решения задачи о Ханойских башнях */ moveTower(Hanoi, 3, 1, 3, 2); Листинг 4. Поддержка обработки списков в Акторе /* С.В. Duff 7.13.86 (с) Copyright, 1986 */ now(NilClass); /* подсоединить niI к узлу */ Def append(self, aNode) { ^aNode } Def cons(self, aNode) { ^aNode } Def rPrintOn(self., aStrm) { printOn('[', aStrm); }
Дополнительный пример программ 171 inherit(Collection, #ListNode, #(left, right), nil, nil) ; now(ListNode); Def append(self, aNode) { ^cons( left, append(right, aNode)); } Def do(self, aBlock) { if isAtom(left) then eval(aBlock, left); else do(left, aBlock); endif; if right then do(right, aBlock); endif; } Def isAtom(self) { ^nil } Def printOn(self, aStrm) { printOn('[', aStrm); printOn(left, aStrm); rPrintOn(right, aStrm); } Def rPrintOn(self, aStrm) { printOn(' ', aStrm); printOn(left, aStrm); rPrintOn(right, aStrm); printOn(']', aStrm); } now(Object) ; Def isAtom(self) {}!! Def rPrintOn(self, aStrm) { printOn('.', aStrmf); printOn(' ', aStrm)); printOn(self, aStrm)); printOn(']', aStrm)); } Def cons (self, aNode newNode) { if isAtom(aNode) then newNode := new(listNode)/ else newNode := copy(aNode); endif; newNode.left := self; newNode.right := aNode ^newNode;
Глава 6 Я ОБЪЕКТНО-ОРИЕНТИРОВАННЫЙ ПОЛЬЗОВАТЕЛЬСКИЙ ИНТЕРФЕЙС ДЛЯ WINDOWS Центральным моментом во всей разработке MS-Windows является использование его возможностей для построения пользовательско¬ го интерфейса для различных приложений. Вопросы пользова¬ тельских интерфейсов привлекают к себе так много внимания практически во всех применениях Windows, что даже требуются определенные усилия для того, чтобы отделить их от внутренних аспектов работы прикладных программ. К счастью, объектно-ори¬ ентированные системы кое в чем выгодно отличаются от традици¬ онных, они несут в себе именно тот тип модульности, который здесь и требуется. Мы, по-прежнему, будем использовать язык Актор, описанный в предыдущей главе. Однако основное внима¬ ние будем уделять вопросам, которые касаются всех объектно-ори¬ ентированных систем в Windows. ■ СОЗДАНИЕОКОН Вначале процесс создания окна в Акторе может показаться не¬ сколько сложным. Это потому, что необходимо запомнить несколь¬ ко важных правил. Для создания окон часто используются методы классов new и defaultNew. Метод defaultNew — это вариант new, аргументы которого определяются по умолчанию, поэтому его проще и легче использовать. Метод класса new обращается к методу объекта create, который в свою очередь вызывает функцию CreateWindow в среде MS-Windows. Элементарное окно можно создать и открыть, послав сообщение: W := defaultNew(Window, "Basic Window”); show(W,1);
Создание окон 173 Таким образом создается и выводится базисное пустое окно, кото¬ рое само по себе делать что-либо существенное не может. Этим должны заниматься подклассы Windows, использующиеся для тех или иных целей. Другим общецелевым методом для создания окон практически любого типа служит метод newStyle. Цена, которую приходится платить за общность, — это необходимость полностью задавать большой набор аргументов. Фактически их семь. Первый определяет имя класса типа окна, которое вы создаете. Следующий дает имя родительского окна, если вы хотите создать дочернее окно. В большинстве случаев создаваемые окна являются дочер¬ ними того или иного вида. Для этих целей обычно используется метод newChild. Ниже приводится ветвь иерархии классов Актора, в которой собраны различные специализированные классы окон. Object W indowsObject Window TextWindow EditWindow WorkEdit BrowEdit FileWindow WorkSpace ToolWindow Browser bispector Класс WindowsObject — это формальный корневой класс, который дает основу для создания любого из основных объектов, использу¬ емых при создании окна, таких как элементы управления, диалоги и окна сами по себе. Впервые на уровне класса Window обеспечи¬ вается поддержка меню. По умолчанию выбирается стиль popup окон, а в качестве указателя — обобщенный курсор мыши, Хотя Window в большинстве случаев это лишь формальный класс, его экземпляры можно использовать для выдачи графики. FW := newMain(FileWindow, "editmenu","Editor", null); show(FW,1); Каждый раз, когда в Акторе создается оконный объект, в Windows посылается сообщение CallCreateWindow, содержащее не менее чем одиннадцать параметров. Когда в такой ситуации создается оконный объект, Windows посылает дескриптор или имя создан¬ ного окна и Актор должен запомнить этот дескриптор. В терминах ООП на Акторе можно сказать, что в классе Window и его потомков имеется метод create, результатом работы которого является по¬
Объектно-оривнтированный пользовательский интерфейс для Windows сылка MS-Windows сообщения CreateWindow. Дескриптор окон¬ ного меню запоминается в экземплярной переменной hMenu. Ме¬ тод IoadMenu занимается загрузкой ресурсов меню и задает значение hMenu. Вызов метода defaultNew равносилен вызову new с некоторым набором параметров, задаваемых по умолчанию. Простейшее оп¬ ределение метода выглядит так: Def defaultNew(self, паше) { ^new(self, mainWindow(TheApp:late), nil, паше, nil); } Аналогичным образом метод new легко определяется в терминах метода newStyle: Def new(self, par, menuName,wName, rect) { ^newStyle(self, par, menuName,wName, rect, nil, nil) } В методе newStyle, который уже выполняет некоторую реальную работу, главными являются обращения к методам create и init. Вот как определяется метод newStyle: Def newStyle(self,par,menuName,wName,rect,id,style | theWnd) { theWnd := new(self:Behavior); if menuName then loadMenu(theWnd, menuName); else setHMenu(theWnd, setContl'D(theWnd, id)); endif; setPaintStruct(theWnd, new(Struct, 32)); create(theWnd, par, wName car lllf, rect car sizeRect(self), style cor style(self)); setLocRect- (theWnd) ; ^init(theWnd); } И наконец, когда мы доберемся до настоящего работающего мето¬ да, увидим, где происходят обращения к функциям базисного уровня Windows типа CreateWindow. Например, в определении метода create, которое приводится ниже, функция CreateWindow вызывается одним из двух способов в зависимости от того, предо¬ ставлено ли реальное значение для аргумента rect. Когда оно равно nil, размер задается по умолчанию: Def create(self, par, wName, rect, style | wndCls, parHWnd) { parHWnd := if parent := par then handle(par) else 0 endif; wndCls := wndClass(class(self)); if Call GetClassInfo(HInstance, asciiz(wndCls), new(Struct, 26)) = 0
Текстовые окна 175 then register(class(self)); endif; caption := wName; wStyle := style; setCurWindow(System, self); hWnd := if rect then Call CreateWindow(asciiz(wndCls,asciiz(caption),style, left(rect),top(rect),width(rect),height(rect), parHWnd, hMenu cor 0, HInstance, 0L) ; else Call CreateWindow(asciiz(wndCls), asciiz(caption),style, 0x80 * 0x100, 0, 0x80 * 0x100, 0, parHWnd, hMenu cor 0, HInstance, 0L) ; endif; if hWnd = 0 then alert(System, sell, #windCreateError); endif; add(Windows, self); if not(parent) then add(OpenWindows, self); endif; } Важным видом сообщений являются сообщения newMain. Они используются для того, чтобы создать и вернуть новое окно, пригодное как главное окно некоторого приложения. Определение newMain таково: Def newMain(self, menu, caption, rect) { ^newStyle(self, nil, menu, caption, rect, nil, WS_OVERLAPPEDWINDOW) } Для аргумента menu требуется строка, соответствующая имени меню, определенному в ресурсном сценарии (script), или nil, если такого меню нет. Аргумент caption — другая строка, если это не nil, то это будет заголовок окна. И наконец, для аргумента rect ожидается либо структура, задающая координаты окна на экране, либо nil, тогда координаты задаются по умолчанию. ■ ТЕКСТОВЫЕ ОКНА Класс TextWindows — это один из простейших потомков Window, который в действительности может предоставить что-то полезное. Этот класс позволяет создавать паркетные окна, которые могут распечатывать текст. Он делает это при помощи методов printString и printChar, которые вызывают функцию MS-Windows Textout GDI (Graphics Display Interface). Часто хочется иметь текстовое окно, которое позволяет не только выводить текст, но и передвигаться по нему и редактировать его. Подкласс TextWindow, вызывающий EditWindow, предоставляет программу для обеспечения таких возможностей. Класс WorkEdit делает еще
176 Объектно-ориентированный пользовательский интерфейс для Windows один шаг вперед, он позволяет создавать окна, в которых можно не только редактировать, но и вводить и выполнять операторы языка Актор. Три подкласса WorkEdit предоставляют окна тради¬ ционного назначения: броузеры, броузеры файлов и рабочие окна общего назначения. Их главное отличие от традиционных в том, что они так же,как и их предок TextWindow, являются паркетны¬ ми. Чаще всего в Акторе используются окна из другой ветви дерева, они реализуются на основе класса PopupWindow. Объект TextWindow можно создать и открыть при помощи сооб¬ щения: TW := defaultNew(TextWindow, "Characters, Strings, & Things”); Show(TW, 1 ); Окно, созданное таким образом, не позволяет вводить текст непос¬ редственно. Однако вы можете переслать в него текст. Например, следующие сообщения дают окну команду выдать различные текстовые строки: eol(TW); printString(TW,"There must be an easier way.”); eol(TW); eol(TW); printString(TW,"Oh, come on. You're enjoyirig this and you know it") ; Другой неплохой возможностью TextWindow является то, что он может сохранять отметку о месте вставки, положение этой пози¬ ции запоминается в экземплярных переменных xPos и yPos. Позицию вставки можно изменить при помощи сообщения moveCaret, например: setXPos(TW, 50); moveCaret(TW); Кроме того, объекты TextWindow восстанавливают свое изображе¬ ние после того, как их на некоторое время заслоняет другое окно. У TextWindow имеется метод setXPos, который позволяет устанав¬ ливать новое значение переменной xPos, и, хотя нет метода setYPos, его легко определить: Def setYPos(self, yp) { ^yPos := yp; } Окна, которые мы получаем в popup-стиле, напоминают слои, накладываемые друг на друга. В отличие от паркетных окон их нельзя распахнуть или сжать в пиктограмму. Это обусловлено MS-Windows, рорир-окна требуют наличия ’’родительского” тек¬ стового окна. Когда родительское окно сжимается в пиктограмму, рорир-окна, ассоциированные с ним,временно становятся невиди¬ мыми. Окнами стиля popup обычно бывают экземпляры классов
EditWindows 177 Control, Dialog или их потомков. Позже я уделю внимание окон¬ ным объектам этого типа. ■ EDITWINDOWS В качестве примера полноценного окна для редактирования можно использовать окно, которое получится., если в рабочем окне вы¬ полнить сообщение: E := new(EditWindow, ThePort, "editmenu","Bas.ic Editor",nil); show(E,1); Созданный редактор имеет два меню команд, размещенных на главной полоске. Первое меню содержит операции редактирова¬ ния для Бейсика, второе служит для поиска и замены текстов. Нам предоставлен элементарный редактор, правда в нем нет файлов ввода-вывода и других удобств, необходимых для функци¬ онально полной программы подобного назначения. А как можно было бы превратить базисный объект редактирования во что-ни¬ будь более функциональное и полезное? Для того, чтобы ответить на этот вопрос, вам придется узнать кое-что о том, как работают объекты EditWindow. Нам также будут полезны два других фай¬ ловых класса FiIeWindow (табл. 6.1) и FileEditor. Методы для управления меню, используемые в EditWindow, в действительности наследуются из классов Window и WindowObject. В классе Window методы объектов createMenu, drawMenu, loadMenu, setHMenu и setMenu определяются для управления меню, представленных в форме статических ресурсов. Методы классов new, newMain и newStyle получают параметры меню или имена как аргументьь Экземплярные переменные hMenu и menu также наследуются из класса Window. Экземпляр- ная переменная menu предназначена для использования в случае динамических меню. В отличие от о5ъектоп EditWindow у WorkSpaces имеются опции для работы с файловыми операциями. Для того чтобы создать новое функционально полное рабочее окно, используя уже скомпилированные ресурсы меню Actor.EXE, выполним: WS := new(Classes[#WorkSpace], mainWindow, loadString(32), loadString{321), nil); show(WS, 1); Таблица 6.1. Экземплярные переменные FileWindow fileDlg Диалог для загрузки файла file Имя редактируемого файла inputDelimiter Разделитель во входном файле outputDelimiter Разделитель в выходном файле
178 Объектно-ориентированный пользовательский интерфейс для Windows Сообщение loadstring используется для того, чтобы активировать строки, которые уже были определены как статические ресурсы и скомпилированы как часть файла ACTOR.EXE. Сообщение getClipText из EditWindow возвращает текстовую стро¬ ку из Clipboard. Def getClipText(self | hStr, aStr) { if Call OpenClipboard(hWnd) 0 then hStr := Call GetClipboardData(CF_TEXT); aStr := getText(hStr); Call CloseClipboard(); AremoveNulls(aStr); endif; Call CloseClipboard(); Anil ■ FILEEDITOR Другим важным классом для редактирования окон является FileEditor. Как подсказывает само название, этот класс предостав¬ ляет окно редактирования с меню для выполнения различных операций над файлами. Сообщение open реализовано как метод класса таким образом, что одного сообщения достаточно для создания и открытия объекта FileEditor, например: FE := ppen(FileEditor, "New"); ■ ОПИСАНИЯ КЛАССА FileEdit Файл с исходным текстом: Наследует из: Переменные экземпляра: fileDlg inputDelimiter outputDelimiter Методы класса: open style wndClass wndIcon FILEEDIT.CLS EditWindow Диалог для загрузки файла Разделитель во входном файле Разделитель в выходном файле Открывает новый редактор файла для указанного файла Возвращает стиль окна по умолчанию Возвращает имя оконного класса MS-Windows для объектов этого класса Возвращает имя оконного класса MS-Window указанного класса для регистрации или для создания нового окна
Описания класса 179 Методы объектов: charIn CheekDirty command create init maxFileSize openFile openSaveAs readText recreate save setFileDlg shoudClose showTitle writeText ActorApp Файл с исходным текстом: Управляет автоидентификацией Спрашивает у пользователя, в действительностили он хочет покинуть редактор (закончить редактирование). Возвращает true (self), если это так. Текст в окне диагностики Определялся как ресурсные строки Управляет событиями меню. Создает Filewindow, используя все параметры по умолчанию. Подключает FileDialog Инициализирует переменные для хранения разделителей Возвращает максимальный размер считываемого файла Открывает новый файл и заполняет workText его содержимым Запрашивает у пользователя имя файла и пытается открыть его. Возвращает имя файла в DOS или nil Заменяет диапазон выборки на содержимое файла. fName — строка, в которой находится имя считываемого файла DOS, например, ”new.doc” Выполняет "пересоздание” в соответствии со значением экземплярных переменных Сохраняет текст в текущем файле Устанавливает и возвращает файловый диалог Подтверждает уверенность, что пользователь в действительности хочет закрыть окно Выдает текущий заголовок редактируемого файла или "Untitled” если его имя еще не определено Записывает содержимое workText в файл. fName — строка, в которой находится имя файла в DOS, например, "new.doc” ACTORAPP.CLS
180 Объектно-ориентированный пользовательский ингерфейсдля Windows Наследует из: Application Экземплярные переменные: workspace imageName dynamic static Методы класса: Методы объектов: abort Очищает mainWindow и перерисовывает, если необходимо, текущее окно. Этот метод возвращает true (self) для того,чтобы показать, что отправитель может продолжать выброшенный процесс ■ КЛАСС ACTORAPP Разобраться, как строятся пользовательские интерфейсы в Акторе, можно, тщательно изучив класс ActorApp. Этот класс предостав¬ ляет большую часть того, что имеется в пользовательском интер¬ фейсе в среде разработки Актора. Единственный экземпляр ActorApp хранится в переменной с именем TheApp, этому экзем¬ пляру посылаются сообщения init в начале работы. Для любой корректной прикладной программы необходимо, чтобы был неко¬ торый, хранящийся в TheApp объект, который мог бы отвечать на сообщения init, abort и shouldClass. Метод init в ActorApp начинает запуск Актора с выдачи паркет¬ ного окна, которое необходимо создать первым. Затем он создает рабочее окно, если это возможно, и пересоздает все окна, которые отвечают на сообщение recreate. Метод ActorApp всегда выполня¬ ется в начале работы для того, чтобы инициализировать среду разработки Актора. Def init(self, str) { init(self:ancestor, str); DeadWindows := new(Set, 4); if mainWindow then recreate (mamWindow) ; forceOnScreen(mainWindow); else startMainWindow(self); add(OutPorts:late,- mainWindow); endif; addAbout(mainWindow) ; removeUsing(OpenWindows, mainWindow, {}); if workspace then recreate(workspace); else if Classes[#WorkSpace]; then startWorkSpace(self);
Класс ActorApp 181 endif; endif; removeUsing(OpenWindows, workspace,{}); do(OpenWindows:late, {using(window) recreate(window);}); if size(DeadWindows:late) 0 then do(DeadWindows:late, { using(window) remove(OpenWindows:late, window); }) ; beep(); printLine(loadString(382)); beep(); endif; initDirty(self); if workspace then forceOnScreen(workspace); changeLog(workspace), "/*Started image: " + imageInfo(self) + "*/"); setFocus(workspace); Call PostMessage(handle(workspace), messageID(#WM_SYSCOMMAND), IDSABOUT,0)? endif; initMemory(); AmessageLoop(self); } В первую очередь сообщение startMainWindow инициализирует окно. Когда такое окно создано, метод теоретически можно было бы удалять из системы, он требуется лишь один раз. Def startMainWindow(self) { display:=mainWindow:=newMain(WorkWindow,nil,loadString(1 6 0), nil); setBuffer(mainWindow, new(String, 120)); fill(buffer(mainWindow), ' '); home(mainWindow); show(mainWindow, CmdShow); } Метод startWorkSpace класса ActorApp точно так же запускает в первый раз рабочую область workspace. Поскольку объект workspace принадлежит OpenWindows, так же как и startMain, теоретически этот метод можно удалить из системы. def startWorkSpace(self) { workspace := new(Classes[#WorkSpace], mainWindow, loadString(320), loadString(321), nil)? show(workSpace, 1); } Сообщение reset класса ActorApp вновь вызывает целиком всю среду Actor Application. Выполняя это, он очищает mainWindow и окно workspace.
182 Объектно-ориентированный пользовательский интерфейс для Windows Def reset(self) { mainWindow cand cls(initWorkText(resetLocRect(mainWindow) ) ) ; workspace cand cls(initWorkText(resetLocRect(workspace))); } ■ КЛАВИШИ, ДИАЛОГИ И ДРУГИЕ ЭЛЕМЕНТЫ УПРАВЛЕНИЯ Ветвь Актора, которая связана с элементами управления, имеет следующую структуру: Object DlgItem WindowsObject Control Button Edit ScrollBar ListBox ComboDox Dialog DialogDesign FileDialog biputDialog ■ СОЗДАНИЕ КНОПОК Класс Button (кнопка) происходит из класса Control, который в свою очередь является потомком класса WindowsObject. Кноп¬ ки — это в действительности маленькие дочерние окна со специ¬ фическими свойствами, связанными с управлением и с их визуализацией Q)uc. 6.1). Один из примеров различных типов кнопок можно увидеть, выполнив в workspace окне следующую программу: WB := new(Window,ThePort,nil,"Window For Buttons",&(215,60,500,200)); show(WB,1); B1 := newPush(Button, 202, WB, "Go"); setCRect(B1,&(10,20, 70, 40) ) ; moveWindow(Bl) ; show(Bl,T); B2 := newDefPush(Button, 203, WB, "Stop"); setCRect(B2, &(90,20, 150, 40) ) ; moveWindow(B2) ; show(B2,1); B3 := newDefRadio(Button, 204, WB, "Radio"); setCRect(B3,&(10, 60, 70, 80)) ; moveWindow(B3);
Создание кнопок 183 Рис. 6.1. Четыре базисных стиля кнопок show (B3,1); B4 := newCheck(But'ton, 205, WB, "Check"); setCRect(B4,&(90,60,150,80)); moveWindow(B4) ; show(B4,1); Если у вас имеются некоторые сомнения или колебания в отноше¬ нии свободы выбора размеров кнопок, следующий пример должен их рассеять: GBW := new(Window, ThePort, nil, "Giant Button Window", &(275, 60, 500, 300) ) ; show(GBW, 1); Huge := newPush(Button, 202, GBW, "Anything to Prave а Point") ; setCRect(Huge, &(15, 10, 205, 200)); moveWindow(Huge) ; show(Huge,1); Windows обеспечивает поддержку до 255 кнопок для одного окна или диалога. Здесь приводится пример окна со множеством кнопок: WB := new(Window,ThePort,nil,"Say it with Buttons",&(175,90,600,300)); show(WB,1); at := newPush(Button, 202, WB, "At"); setCRect(at,&(10, 60, 70, 80) ) ; moveWindow(at) ;
Объектно-ориентированный пользовательский интерфейс для Windows show(at,1); this := newPush(Button, 203, WB, "this"); setCRect(this,&(90,60,150,80) ); moveWindow(this); show(this,1); rate := newPush(Button, 204, WB, "rate"); setCRect(rate,&(1 70,60,230,80) ) ; moveWindow(rate); show(rate,1); comma := newPush(Button, 205, WB, ","); setCRect(comma,&(250,60,265,80)); moveWindow(comma); show(comma,1); well := newPush(Button, 205, WB, "we'll"); setCRect(well,&(275,60,320,80) ) ; moveWindow(well); show(well,1); surely := newPush(Button, 205, WB, "surely"); setCRect(surely,&(340,60,400,80)); moveWindow(surely); show(surely,1); run := newPush(Button, 202, WB, "run"); setCRect(run,&(10,120,70,140)); moveWindow(run); show(run,1); out := newPush(Button, 203, WB, "out"); setCRect(out,&(90,120,150,140)); moveWindow(out); show(out,1); of := newPush(Button, 204, WB, "of"); setCRect(of,&(170,120,230,1 40) ) ; moveWindow(of); show(of,1); buttons := newPush(Button, 205, WB, "buttons"); setCRect(buttons,&(250,120,320,140)); moveWindow(buttons); show(buttons, 1) ; soon := newPush(Button, 205, WB, "soon"); setCRect(soon,&(340,120,400,1 40) ) ; moveWindow(soon); show(soon,1); ■ СОЗДАНИЕ ФАЙЛОВЫХ ДИАЛОГОВ Файловые диалоги отличаются от обычных большим числом со¬ глашений, встроенных (подразумевающихся) в них. Обычно они автоматически предоставляют возможности расширения файлов, компиляции и выдачи списка файлов с окне для списков, в котором можно скроллировать и выполнять селекцию. Кроме того, традиционно обеспечивается дополнительное окно, где можно выполнять селекцию для другого директория или другого диска. Для создания базисного файлового диалога достаточно следующих сообщений:
Класс TextFile 185 F := new(FileDialog, "*.*") ; runModal(F, FILE_BOX, ThePort); Как вы можете увидеть, этот тип файлового диалога устанавлива¬ ется для загрузки файлов из спискового окна. ■ РАБОЧИЕОБЛАСТИ Отличный способ увидеть, как в Акторе строится полный пользо¬ вательский интерфейс, состоит в изучении класса WorkSpace (рабочая область). Его экземпляры используются для общения с рабочими окнами, опыт использования которых у вас уже имеется. Рабочие области задуманы как самые первые дочерние окна глав¬ ного окна. Другие дочерние окна главного окна используют метод sizeRect класса WorkSpace. Вот как определяется метод loadFile класса WorkSpace: def loadFile(self) { if not(fileDlg) then fileDlg := new(FileDialog, "*.*"); endif; if runModal(fileDlg, FILE_BOX, self) == IDOK then load(loadFile(fileDlg)); endif; repaint(self); setFocus(self); } Стратегия этой программы состоит в использовании одного и того же файлового диалога каждый раз, когда она запоминает строку с самой последней path-спецификацией. WorkSpace имеет те же тридцать девять экземплярных перемен¬ ных, как и его предок EditWindow. WorkSpace использует экземплярную переменную fileDlg для за¬ поминания оконного объекта файлового диалога. ■ КЛАСС TEXTFILE TextFile — это подкласс класса File, который имеет средства для чтения и записи текстовых буферов. Для того чтобы инициали¬ зировать новый TextFile, вам обычно требуется что-нибудь в этом роде: TF := new(TextFile); setName(TF, "newfile.txt"); create(TF); open(TF, 1); Не забывайте, что объекты TextFile — это не окна. Для того чтобы визуализировать объект TextFile, необходимо использовать окно какого-нибудь класса, оснащенного средствами для загрузки ви¬ зуализируемых текстовых файлов типа FileEdit. Объекты TextFile
186 Объектно-ориентированный пользовательский интерфейс для Windows имеют буфер на 512K и могут распознавать симвожл-разделители. Это позволяет объектам TextFile отслеживать конец текстовой строки. Все объекты данного вида для этой цели имеют экземп- лярную переменную, называемую delimiter, в которой хранится символ-разделитель. Этим обстоятельством пользуется и метод readLine для того, чтобы определять, как разделяются строки в тексте. ■ СОЗДАНИЕ ДИНАМИЧЕСКИХ МЕНЮ Как упоминалось ранее, динамические меню-это такие меню, которые можно создавать без предварительной компиляции файла ресурсов. С ними связаны два главных класса Menu и MenuItem. В меню могут появиться два типа айтемов, те, которые выполняют некоторые действия, и те, которые открывают popup-меню с новым списком выбора. Динамические меню запоминаются в экземпляр- ной переменной menu класса Window. Класс MenuWindow подходит к созданию приложения с каскад¬ ным меню как к созданию некоторого класса, который будет заниматься только этой задачей. Это, конечно, не идеальный способ решения вопроса. Вы можете захотеть использовать эти специальные типы меню в разнообразных ситуациях. Таким обра¬ зом, придется разбить задачу на несколько функций общего на¬ значения и либо поместить их в отдельный подкласс, либо вставить их прямо в класс Window. Здесь мы будем использовать подход с созданием нового класса. Таким образом описание класса Window остается в целости, и все новые описания будут сосредо¬ точены в документации нового класса. Рассмотрим простое динамическое меню файлов, которое активи¬ зирует файловый диалог. Построение такого меню состоит из двух основных этапов. Сначала вы создаете командную строку для меню, а затем должны создать действие-событие для того, чтобы активизировать ваш файловый диалог. Для этого вы можете создать новый класс, называемый FileMenuWindow, который яв¬ ляется подклассом Window. Для нового класса необходимо опре¬ делить новый метод command. Этот метод просто интерпретирует команды меню, которые должны возбуждать события, и выполня¬ ет действия, ассоциированные с ними. Программа будет очень простой, если ваш подход будет заключаться только в передаче сообщения perform для обработки некоторому методу, который специфицирован, например, так: Def command(self, wp, lp msg) { if msg := action(menu, wp) then ^perform(self, wp, msg) endif; }
Создание динамическихдиалогов 187 Это базис, позволяющий при появлении событий, которые возбуж¬ даются пользователем, работающим с меню, пересылать сообще¬ ния объектам, выполняющим указанные действия. Помимо метода command для построения динамических меню требуется метод init. Ниже описан типовой метод init для динамических меню, который можно использовать как образец, для вашего приложения его можно скорректировать: Def init(self pMenu) { menu := create(new(Menu), self); addItem(menu, appPopup(self)); pMenu := new(Popup(MenuItem, "&Menu1"); addItem(pMenu, new(MenuItem, "&0ption1"+asString(Tab)+"Ctrl+W", C0MMAND1, #message1)) ; addItem(pMenu, new(MenuItem, "&0ption2"+asString(Tab)+"Ctrl+S", C0MMAND2, #message2) ) ; addItem(menu, pMenu); pMenu := new(Popup(MenuItem, "&Menu2"); addItem(pMenu, new(MenuItem, "&0ption1", C0MMAND1, #message1)); addItem(pMenu, new(MenuItem, "&0ption2", C0MMAND2, #message2)); addItem(menu, pMenu); addItem(menu, new(MenuItem, "&Help1", HELP, #help)); do(over(C0MMAND1, C0MMAND2 + 1), { using(item) grayMenuItem(menu, item); }) ; ' drawMenu(self); } Естественно, что количество меню и подменю, описанных в данном образце, может быть произвольным. Строки этого текста можно удалять или копировать так, как вам требуется. ■ СОЗДАНИЕ ДИНАМИЧЕСКИХ ДИАЛОГОВ В той же манере, как и динамические меню, можно создавать динамические диалоги. Для этого используются два главных клас¬ са DlgItem и DialogDesign. Если в вашей системе нет таких классов, следует их загрузить. Динамические диалоги полезны не только для интерактивной разработки, но и тогда, когда вы заранее не можете скомпилировать ресурсы статического диалога. Для того чтобы создать динамический диалог, выполните данные сообщения в рабочем окне: D := new(DialogDesign); setText(D, "Hard Dialog"); addItem(D, newButton(DlgItem, "Shut", IDOK, 40@40, 32016, 0)); addItem(D, newStatic(DlgItem,"Make Your Move",100,25020,60@16,0)) ? setSize(D, 0@0, 110@70);
188 Объектно-ориентированный пользовательский интерфейс для Windows runModal(D, nil, ThePort); Теперь попробуем разобраться, что же реадьно можно делать с динамическими диалогами. Главная экземплярная переменная класса DialogDesign - это itemColl, которая является коллекцией, содержащей все дочерние элементы управления в некотором диа¬ логе. Метод-приглашение (prompter) класса DialogDesign исполь¬ зуется для создания динамических диалогов, которые дают возможность пользователю интерактивно вводить текст в соответ¬ ствующие поля. Элементы управления добавляются к диалогу при помощи метода addItem, способов добавления к меню много. Метод addItem обычно используется в сочетании с различными методами инициализации класса Design. У всех этих методов одинаковый базисный формат, они запрашивают шесть аргументов: 1) назначение сообщения; 2) текст заголовка, который выдается для данного диалога; 3) целое число Ш; 4) координаты элементов управления в диалоге; 5) размеры элементов управления; 6) типы элементов управления. Создание очень простого диалога из предыдущего примера можно усовершенствовать, если использовать класс ErrorBox Q?uc. 6.2). Загрузите этот класс, если вы еще не сделали этого, и выполните: E := new(ErrorBox, ThePort, "Make Your Move", "HardDialog", MB_ICONHAND); ■ ВНЕРЕЖИМНЫЕ ДИАЛОГИ Внережимный диалог, не зависящий от текущего контекста, — это диалог, в котором пользователя не заставляют отвечать за действия других задач, выполнявшихся перед этим. Такой диалог возможен всегда, когда нужно. Все, что для этого необходимо, — это использовать runModeless вместо runModeL Для того, чтобы запустить ’’Hard Dialog” внережимно, выполните: D := new(DialogDesign); setText(D, "Hard Dialog"); addItem(D, newButton(DlgItem, "Shut", IDOK, 4040, 3216, 0)) ; addItem(D, newStatic(DlgItem,"Make Your Move",100,2520,6016,0)); setSize(D, 00, 11070); unModeless(D, nil, ThePort); Внережимные диалоги важны в части приложений, где вы хотели бы дать пользователю возможность перехватить инициативу и выполнить какую-либо подзадачу, не принуждая его следовать жесткой схеме. Они также важны для того, чтобы предохранить
Создание списковьь окон 189 Рис. 6.2. Динамический диалог с пиктограммой, основанный на классе ErrorBox пользователя от попадания в режим, когда возникает ситуация, в которой ничего нельзя ответить на запрос и приходится перезаг¬ ружать компьютер. ■ ОБЫЧНЫЙ ДИНАМИЧЕСКИЙ ДИАЛОГ Способов использовать диалоги бесконечное множество, и поэтому нет конца многообразию типов диалогов, пригодньгх для той или иной цели. Например, одно общепринятое использование диалогов с приглашениями и другими элементами управления должно позволить пользователю ввести аргументы сообщений. Один из неплохих вариантов решения этой задачи — определить специаль¬ ный командный метод, который опишет, что нужно сделать с текстом, введенным пользователем» ■ СОЗДАНИЕ СПИСКОВЫХ ОКОН Списковые окна — важный тип элементов управления в среде Windows. Он содержит бесконечное многообразие видов использо¬ вания и может принести важные преимущества, когда проектиро¬ вание выполнено продуманно и качественно. В Акторе класс ListBox является потомком класса Control, который служит одно¬
190 Объектно-ориентированный пользовательский интерфейс для Windows временно и образцом для инициализации работающих объектов и классом, чьи свойства наследуются усовершенствованными эле¬ ментами управления, такими, как комбинированные окошки. Простое списковое окно можно создать и визуализировать при помощи сообщения: WW := new(Window, ThePort, nil, "Window For List Box", &(275, 60, 500, 200)); show(WW, 1); L := new(ListBox, 200, WW); setCRect(L, &(100, 0,200, 80)); moveWindow(L); addString(L, "Alpha"); show(L, 1); addString(L, "Beta"); addString(L, "Gamma"); ■ СОЗДАНИЕ КОМБИНИРОВАННЫХ ОКОШЕК Специальный класс ComboBox Q?uc. 6.3) предназначен для создания комбинированных окошек Актора. Он является подклассом ListBox, поскольку комбинированные окошки в действительности являются особым типом списковых окон. Комбинированное окош¬ ко — это особый диалог, который может включать различные списки и опции выбора. Имеются три различных вида: простой, ниспадающий и списковый ниспадающий. WC := (Window, ThwPort, nil, "Window For Combo Box", &(275, 60, 500, 200)); show(WC, 1); С := newSimple(ComboBox, IDOK, WC, clientRect(WC)); addString(C, "command.com"); addString(C, "config.sys"); addString(C, "autoexec.bat"); Show (С, 1); WC := (Window, ThwPort, nil, "Window For Combo Box", &(275, 60, 500, 200)) ; show(WC, 1); С := newDropDownList(ComboBox, IDOK, WC, clientRect(WC)); addString(C, "happiness"); addString(C, "freedom"); addString(C, "prosperity"); Show(С, 1); Нажав на стрелку, вы увидите список отсортированных ниспада¬ ющих текстовых фрагментов-айтемов. Хотя этот пример, по-види¬ мому, отработает нормально, для комбинированных окошек, созданных данным способом, имеется ряд проблем. Вы это заме¬ тите, если будете передвигать окно по экрану. Окно двигается, а комбинированное окошко стоит на месте. В приложениях, где у пользователя нет возможности передвигать окно, проблем не воз¬ никает. В случае же приложений, где пользователь может переме-
Создание полосок меню главного окна 191 Рис. 6.3. Простое комбинированное окошко щать списковые окна, необходимо предусмотреть простой меха¬ низм, который будет автоматически и корректным образом пере¬ мещать списковое окно всегда, когда перемещается его владелец. ■ СОЗДАНИЕ ПОЛОСОК МЕНЮ ГЛАВНОГО ОКНА В технологии программирования уже давно практикуется метод прототипирования, когда вместо программы, которую можно до¬ бавить позднее, используется заглушка или пустуя функция. Эта техника подходит и для разработки главной полоски в пользова¬ тельском интерфейсе Windows. В следующих двух разделах я приведу два примера, где все меню и их айтемы создаются, но действия айтемов представляются лишь заглушками, которые не делают ничего, кроме открытия диалога для того, чтобы показать, куда будут направлены следующие сообщения. Ниже приводится пример главного инициирующего сообщения для некоторого ги¬ потетического приложения, связанного с деловой графикой. Def init(self pMenu) { menu := create(new(Menu(, self); pMenu := newPopup(MenuItem, "&File"); addItem(pMenu, new(MenuItem, "&Load" + asString(Tab) + "Ctrl+W", 100, #load) ) ;
192 Объектно-ориентированный пользовательский интерфейс для Windows additem(pMenu, new(MenuItem, "&Save" + asString(Tab) + "Ctrl+S",100, #save)); addItem(menu, pMenu); pMenu := newPopup(MenuItem, &Charts"); addItem(pMenu, new(MenuItem, "Vertical", 102, #drawChart)); addItem(pMenu, new(MenuItem, "Horizontal", 103, #drawChart)); addItem(pMenu, new(MenuItem, "Pie Chart", 104, #drawChart)); addltem(menu, pMenu); addItem(menu, chartsPopup(self)); addltem(menu, new(MenuItem, "&Help", HELP, #help)); } Def chartsPopup(self popup) { popup := new(Popup(menuItem, "&Options"); addItem(popup, stylePopup(self)); addItem(popup, new(MenuItem, "Brushes", 206, #brushes)); addItem(popup, new(MenuItem, "Colors", 207, #palette)); Apopup } Def stylePopup(self popup) { popup := newPopup(MenuItem, "&Style"); addItem(popup, new(MenuItem, "Dashed", 208, #style)); addItem(popup, new(MenuItem, "Datted", 209, #style)); addItem(popup, new(MenuItem, "Spray", 210, #style)); ^popup } Def command(self, wp, lp msg) { if msg := action(menu, wp) then Aperform(self, wp, msg) endif; } Методы Stub для этого примера используют несложные диалоги с окнами для ошибок для того, чтобы произвести некоторое дейст¬ вие, когда выбирается какой-либо пункт меню, и информировать пользователя о том, что должно произойти, когда программа закончится. Эти метод приводятся ниже: Def brushes(self, wp) { errorBox(getString(menu, wp), "Brushes dialog goes here")? } Def drawChart(self, wp) { errorBox(getString(menu, wp), "Chart will go here"); } Def help(self, wp) { errorBox(getString(menu, wp), "Help dialog goes here"); }
Простой контроллер исполнения 193 Def load(self, wp) { errorBox(getString(menu, wp), "Load routine goes here"); } Def palette(self, wp) { errorBox(getString(menu, wp), "Palette dialog goes here"); } Def save(self, wp) { errorBox(getString(menu, wp), "Save routine goes here"); } Def style(self, wp) { errorBox(getString(menu, wp), "Style dialog goes here"); } После того, как вы введете эти методы в класс ClassWindow, выполните их в рабочей области. W := defaultNew(ChartWindow, "Chart Sampler"); show(W, 1); ■ ПРОСТОЙ КОНТРОЛЛЕР ИСПОЛНЕНИЯ Следующий пример (листинг 1) подобен предыдущему, но предо¬ ставляет начальный прототип для более развитого приложения, которое претерпит еще несколько изменений, пока не приобретет окончательный вид в гл. 9. Здесь нас интересует лишь дизайн главной полоски меню и, как и в предыдущем примере, для фрагментов, описывающих собственно действия, пока они еще не реализованы, используются заглушки. Здесь приводится листинг файла класса, потому что этот класс реально не содержит никакого нового материала. Методы, исполь¬ зующиеся здесь, уже знакомы нам, и теперь они применяются для того, чтобы построить прототип приложения, которое сможет принести определенную пользу. Листинг 1. Пользовательский интерфейс версии ExecWindow (заглушка для меню) Класс ExecWindow (только пользовательский интерфейс)
194 Объектно-ориентированный пользовательский интерфейс для Windows inherit(Window, #ExecWindow, #(actions fileDlg), 2, nil))!! now(class(ExecWindow))!! /* Метод для иерархического меню для вызова графических программ */ Def graphPopup(self popup) { popup := new(Popup(MenuItem, "&Graphics"); addItem(popup, new(MenuItem, "Zing”, 208, #type)); addItem(popup, new(MenuItem, "Windows CAD", 209, #type)); addItem(popup, new(MenuItem, "Designer", 210, #type)); addItem(popup, new(MenuItem, "Object Draw", 211, #type)); Apopup }!! /* Метод для иерархического меню для вызова утилит */ Def utilityPopup(self popup) { popup := new(Popup(MenuItem, "&Utilities"); addItem(popup, new(MenuItem, "Norton", 217, #type)); addItem(popup, new(MenuItem, "Xtree Gold", 218, #type)); addItem(popup, new(MenuItem, "Archive", 219, #type)); addItem(popup, new(MenuItem, "Other", 220, #type)); Apopup }!! /* Метод для иерархического меню для работы с системами обработки текстов */ Def wpPopup(self popup) { popup := new(Popup(MenuItem, "&Word Processors"); addItem(popup, new(MenuItem, "Word for Windows", 208, #type)); ' addItem(popup, new(MenuItem, "Ami", 209, #type)); Apopup }!! /* Метод-заглушка для незавершенных команд меню */ Def stub(self, wp) { errorBox(getString(menu, wp), ”Не теряй времени и замени это на что-нибудь стоящееП; } » i /* Метод-заглушка для незавершенных команд меню */ Def type(self, wp) { errorBox(getString(menu, wp), ”А теперь, представьте себе, программа выполняется1’); }!! /* Метод для иерархического меню для работы с электронными таблицами */ Def ssPopup(self popup) { popup := new(Popup(MenuItem, "&Spreadsheets"); addItem(popup, new(MenuItem, "Excel", 208, #type)); addItem(popup, new(MenuItem, "Zing", 209, #type)); addItem(popup, new(MenuItem, "Quattro Pro", 210, #type)); A popup
Простой контроллер исполнения 195 }!! /* Метод для иерархического меню для работы с различными программными системами */ Def miscPopup(self popup) { popup := new(Popup(MenuItem, "&Miscellaneous"); addItem(popup, new(MenuItem, "Toolbook", 208, #type)); addItem(popup, new(MenuItem, "Project", 209, #type)); addItem(popup, new(MenuItem, "Almanac", 210, #type)); Apopup }П /* Метод для загрузки файлов */ Def fileLoad(self, wp newfileName) { if not(fileDlg) then fileDlg := new(FileDialog, "."); endif; if runModal(fileDlg, FILE_BOX, self) == IDOK then load(loadFile(fileDlg)); endif; tepaint(self)? setFocus(self) ; } M /* Метод для иерархического меню для работы с системами программирования */ Def develPopup(self popup) { popup := new(Popup(MenuItem, *'&Development Tools"); addItem(popup, new(MenuItem, "Actor", 212, #type)); addItem(popup, new(MenuItem, "С++ Views", 213, #type)); addItem(popup, new(MenuItem, "Spy", 214, #type))? addItem(popup, new(MenuItem, "Heapwalker", 215, ^type)); addItem(popup, new(MenuItem, "Zoomin", 216, #type)); Арорир }П /* Метод для иерархического меню для работы с приложениями */ Def appsPopup(self popup) { popup := new(Popup(MenuItem, "A&pplications"); addItem(popup, wpPopup(self)); addItem(popup, ssPopup(self)); addItem(popup, graphPopup(self)); addItem(popup, miscPopup(self)); addItem(popup, utilityPopup(self)); addItem(popup, develPopup(self)); ^popup }!! /* Метод-заглушка для незавершенных команд меню */ Def save(self, wp) { errorBox(getString(menu, wp), "Работает программа Save”); } i i Def help(self, wp) { errorBox(getString(menu, wp), ”Здесь идет диалог для подсказок (Help)”); }
196 Объектно-ориентированный пользовательский интерфейс для Windows ! ! /* Инициализирует новое окно с динамическим иерархическим меню */ Def init(self pMenu) { menu := create(new(Menu), self); pmenu := newPopup(MenuItem, "&File"); addItem(pMenu, new(MenuItem, ’’&Load" + asString(Tab) + "Ctrl+W",100, #fileLoad)); addItem(pMenu, new(MenuItem, "&Save" + asString(Tab) + "Ctrl+S",101, #save)); addItem(menu, pMenu); pmenu := newPopup(MenuItem, "&Accessories"); addItem(pMenu, new(MenuItem, "Calculator", 102, #stub)); AZ addItem(pMenu, new(MenuItem, "Clock", 103, #stub)); addItem(pMenu, new(MenuItem, "Notepad", 104, #stub)); addItem(pMenu, new(MenuItem, "Control Panel", 105, #stub)); addItem(pMenu, new(MenuItem, "Cardfile", 106, #stub)); addItem(pMenu, new(MenuItem, "Calendar", 107, ^stub)); addItem(pMenu, new(MenuItem, "Write", 108, #stub)); addItem(pMenu, new(MenuItem, "Painbrush", 109, ^stub)); addItem(pMenu, new(MenuItem, "Recorder", 110, #stub)); addItem(menu, pMenu); addItem(menu, appsPopup(self)); addItem(menu, new(MenuItem, "&Help!", 99, #help)); drawMenu(self); } » i /* Отвечает за события меню.Аргумент wp дает выбранный идентификатор меню Ш.Берет символ сообщения из объекта-меню */ Def command(self, wp, ip msg) { if msg := action(menu, wp) then Aperform(self, wp, msg) endif; } !! ■ НЕКОТОРЫЕ ИЗВЛЕЧЕНИЯ ИЗ ПРИМЕРА РЕСУРСНОГО ФАЙЛА Данный раздел (листинг 2) содержит некоторые извлечения из примера ресурсного скрипт-файла (файла описания), который используется при вызове среды Актора. Он будет особенно полезен при изучении класса ActorApp. Листинг 2. Пример синтаксиса ресурсного сценария. #include "style.h" #include "actor.h" #include "track.h" #include "demos.h" work ICON work.ico Browser ICON browser.ico FileWindow ICON filewind.ico
Некоторые извлечения из примера ресурсного файла Inspector ICON inspect.ico cube DATA cube. dot 700 BITMAP actlogo.bmp 701 BITMAP act30.bmp 702 BITMAP acttext1.bmp 703 BITMAP acttext2.bmp 704 BITMAP acttext3.bmp 705 BITMAP acttext4.bmp 706 BITMAP acteye.bmp Actor ACCELERATORS BEGIN VK_INSERT, EDIT_PAST, VIRTKEY VK_DELETE, EDIT_CUT, VIRTKEY VK_SUBTRACT, EDIT_CUT, VIRTKEY VK_ADD, EDIT_COPY, VIRTKEY VK_LEFT, VK_LEFT, VIRTKEY VK_UP, VK_UP, VIRTKEY VK_RIGHT, VK_RIGHT, VIRTKEY VK_DOWN, VK_DOWN, VIRTKEY ,,Aa", EDIT_SELALL "AgM, BR_CGOTO "^r", BR_REFORM ,,Az", BR_ZOOM VK_TAB, EDIT__TAB, VIRTKEY VK_PRIOR, EDIT_PRIOR, VIRTKEY VK_NEXT, EDIT_NEXT, VIRTKEY VK_HOME, EDIT_HOME, VIRTKEY VK_END, EDIT_END, VIRTKEY VK_F1, VK_F1, VIRTKEY VK_F2, VK_F2, VIRTKEY VK_F3, VK_F3, VIRTKEY VK_F4, VK_F4, VIRTKEY VK_F5, VK_F5, VIRTKEY VK_F6, VK_F6, VIRTKEY VK_F7, VK_F7, VIRTKEY VK_F8, VK_F8, VIRTKEY VK_F9/ VK_F9, VIRTKEY VK_F11, VK_F11, VIRTKEY VK_F12, VK_F12, VIRTKEY VK_DELETE, EDIT_CUT, VIRTKEY, SHIFT VK_INSERT, EDIT_COPY, VIRTKEY, CONTROL VK_INSERT, EDIT_PASTE, VIRTKEY, SHIFT END ABOUT_BOX DIALOG DISCARDABLE 59, 79, 151, 128 STYLE WS_POPUP WS_DLGFRAME BEGIN CTEXT '*Actor\256 3.0” -1, 1, 12, 147, 10 CTEXT '*Copyright\251 1986-1990" -1 , 1, 28, 147, 10 CTEXT "The Whitewater Group, Inc." -1, 1, 39, 147, 10 CTEXT "All rights reserved." -1, 1, 50, 147, 10 ICON "work" 5, 24, 98, 13, 17 ICON "browser" 6, 114, 98, 13, 17 CTEXT "Portions Copyright\251 1987-1990" -1, 1, 68, 147, 10 CTEXT "Microsoft Corporation", -1, 1, 79, 147, 10
198 Объектно-ориентированный пользовательский интерфейс для Windows DEFPUSHBUTTON "OK"IDOK, 57, 99, 32, 14, WS_GROUP END INPUT_BOX DIALOG DISCARDABLE 77, 94, 165, 71 STYLE WS_BORDER WS_CAPTION WS_DLGFRAME WS_POPUP BEGIN EDITTEXT FILE_EDIT, 10, 32, 138, 12, WS_BORDER WS_CHILD WS_TABSTOP ES_AUTOHSCROLL LTEXT "", INPUT_MSG, 11, 5, 143, 18, WS_CHILD DEFPU SHBUTTON "&OK" IDOK, 32, 50, 32, 14, WS_CHILD PUSHBUTTON "&Cansel", IDCANSEL, 99, 50, 32, 14, WS_CHILD END REPLACE_BOX DIALOG DISCARDABLE 77, 94, 165, 85 STYLE WS_BORDER WS_CAPTION WS_DLGFRAME WS_POPUP BEGIN CONTROL M,\ FILE_EDIT, "EDIT", WS_CHILD WS_VISIBLE WS_BORDER WS_TABSTOP 0x80L, 10, 16, 138, 12 CONTROL "", RPLC_EDIT, "EDIT”, WS_CHILD WS_VISIBLE WS_BORDER WS_TABSTOP 0x80L, 10, 46, 138, 12 CONTROL "", INPUT_MSG, "STATIC", WS_CHILD WS_VISIBLE WS_GROUP, 11, 5, 143, 9 CONTROL "Replace with", 501, "STATIC", WS_CHILD WS_VISIBLE WS_GROUP, 11, 34, 143, 9 CONTROL "&OK", IDOK, "BUTTON", WS_CHILD WS_VISIBLE WS_TABSTOP 0x1L, 10, 64, 32, 14 CONTROL "&Replace All", RPLC_ALL, "BUTTON", WS_CHILD WS_VISIBLE WS_TABSTOP 48, 64, 64, 14 CONTROL "&Cansel", IDCANSEL, "BUTTON", WS_CHILD WS_VISIBLE WS_TABSTOP, 117, 64, 32, 14 END f ERR_BOX DIALOG DISCARDABLE 48, 32, 210, 85 STYLE WS POPUP WS_CAPTION CAPTION ^Error Dialog" BEGIN DEFPU SHBUTTON "&OK" IDOK, 172, 8, 28, 14, WS_GROUP PUSHBUTTON "&Debug", IDYES, 172, 28, 28, 14, WS_GROUP LISTBOX ERR_LB, 4, 8, 160, 70 END DW_BOX DIALOG DISCARDABLE 27, 27, 201, 105 STYLE WS_DLGFRAME WS_POPUP BEGIN LTEXT "The text in Browser edit window has been" 2, 10, 11, 180, 10 LTEXT "changed. Accept or Cut to Clipboard?" 3, 10, 24, 150, 10 PUSHBUTTON "&Accept", DW_ACC, 10, 47, 75, 14, WS_CHILD PUSHBUTTON "Cut to C&lipboard", DW_CTC, 10, 74, 75, 14, WS_CHILD DEFPU SHBUTTON "A&bandon”, DW_ABA, 1 10, 47, 75, 14, WS_CHILD PUSHBUTTON "&Cansel", IDCANSEL, 110, 74, 75, 14, WS_CHILD END FILE_BOX DIALOG DISCARDABLE 27, 23, 170, 116 STYLE WS DLGFRAME w£ POPUP DS ABSALIGN
Некоторые извлечения из примера ресурсного файла 199 BEGIN CONTROL мм FILE_LB, "ComboBox", CBS_SIMPLE CBS_SORT WS_VSCROLL WS_NABSTOP WS_CHILD, 4, 30, 55, 80 CONTROL "Files:" 3, "static”, SS_LEFT WS_CHILD, 4, 19 31 , 10 CONTROL "" FILES_DIRLB, "ListBox", LDS_STANDARD WS_TABSTOP WS_CHILD, 65, 42, 55, 68 CONTROL "Directories:" 3, "static", SS_LEFT WS_CHILD, 65, 31, 38, 10 DEFPUSHBUTTON "&Open", IDOK, 130, 37, 30, 15, WS_CHILD PUSHBUTTON "&Cansel", IDCANSEL, 130, 63, 30, 15, WS_CHILD CONTROL "Directory:" 3, "static", SS_LEFT WS_child, 4 7, 32, 11 CONTROL "" FILE_DIR, "static", SS_LEFT WS_CHILD, 39, 7, 146, 11 END MBrowMenu MENU BEGIN MENUITEM "&Accept!", BR_ACCEPT POPUP "&Edit" BEGIN MENUITEM "Cu&t\tShift+Del", EDIT_CUT MENUITEM "&Copy\tCtrl+Ins", EDIT_COPY MENUITEM "Paste\tShift+Ins", EDIT_PASTE MENUITEM "C&lear", EDIT_CLEAR MENUITEM SEPARATOR MENUITEM "Select &All\tCtrl+A", EDIT_SELALL MENUITEM "&Reformat\Ctrl+R", BR_REFORM END POPUP "&Search" BEGIN MENUITEM "&Find...", EDIT_SRCH MENUITEM "Find &Next\tF3", VK_F3 MENUITEM "&Replace...", EDIT_RPLC END MENUITEM "&Doit", INSP_DOIT MENUITEM "&Inspect!", INSP_ISEL MENUITEM "&Browse!", WORK_BROWSE POPUP "&Utility BEGIN MENUITEM "&Implementors", WORK_IMP MENUITEM "&Senders", WORK_SYMSEND MENUITEM "Window Routine Senders", WORK_WINDSEND MENUITEM "&Global References", WORK_GLOSEND MENUITEM "&References", WORK_SEND END POPUP "&Templates" BEGIN MENUITEM "&do", TEMP_DO MENUITEM "&if/then", TEMP_IF MENUITEM "if/&else", TEMP_IFEL MENUITEM "&block", TEMP_BLOCK MENUITEM "&select/case", TEMP_CASE MENUITEM "&loop", TEMP_LOOP MENUITEM SEPARATOR
200 Объектно-ориентированный пользовательский интерфейс для Windows MENUITEM "&New method", TEMP_NMETH END END ■ ДИРЕКТОРИИ DOS Работа с директориями DOS и именами маршрутов (pathname) в Windows не такая простая, как можно было бы ожидать, посколь¬ ку в Windows нет функций для непосредственного обеспечения этих работ. Хотя имеется функция GetSystemDirectory, но по некоторым причинам нет соответствующей функции SetSystemDirectory. Функция DlgDirListComboBox является спе¬ циальной функцией, работающей только со специальными файло¬ выми диалогами. В классах, которые поставляются с Актором, нет непосредственной поддержки для прямого изменения маршрута. Метод setFileSpec класса FileDialog предназначен для использова¬ ния только с объектами класса FileDialog. Однако имеются неко¬ торые общедоступные классы Public Domain, которые начинал разрабатывать Марк Солински и значительно расширил Джеймс Xoy. Классы вполне отвечают требованиям для выполнения этих работ. Поскольку они будут использоваться в нескольких после¬ дующих примерах, я здесь приведу эти классы (см. листинги 3-6). Для работы с директориями и маршрутами имеются два класса: FileString и Directory. Кроме того существует одна программа, которая улучшает класс Актора SortedCollection<, В классе Directory полное имя хранится в двух экземплярных переменных: diskDrive, которая хранит букву — имя дискового драйвера, и pathName, которая хранит оставшуюся часть марш¬ рута, без имени драйвера. Directory широко использует методы класса (т.е. не экземпляр- ные. — Прим. nepee.). fullName (current(Directory)); "C:\Actor" Dir := current(Directory); а Directory fullName(Dir); "C:\Actor" setDiskDrive(Dir, "D"); a Directory setPathName(Dir,11/") ; a Directory fullName(Dir); "D:/"
Директории DOS 201 Листинг 3. SYSCHNG.ACT /* Системные изменения для поддержки класса Directory ** ** Модифицировано: Джеймсом В. Xoy ** Дата:0б/30/1988 */!! add(Constants, #DOS_OFFSET, 700);!! now(DosStruct)!! /* Взять длинный указатель и разместить значение в корректных регистрах сегмента и смещения */ Def setPtr(self lPtr, Segment, Offset) { putWord(self, high(lPtr), Segment); putWord(self, low(lPtr), Offset); }!! now(Struct)!! /* Дается размещение объекта в памяти в соответствии со структурой: размер данные */ Def fillStruct(self, lpMem) { do(overBy(0, size(self), 2), { using(i) putWord(self, wordAt(lp(Mem + asLong(i)), i); }); Aself; >!! /* ** Изменения в системе, внесенные для класса ** SortedCollection. Эти изменения позволяют ** записать вид сортировки блоков при создании ** экземпляра, а не при помощи отдельного шага. ** Новый метод используется так же, как и раньше. ** Новый метод** ”sortBlock” в дополнение к ** параметру size получает параметр ** "сортировка блоков”. ** ** Модифицировано: Джеймсом В. Xoy ** Дата:06/30/1988 */!! now(SortedCollectionClass)!! /* Выдать новый объект-SortedCollection, используя метод сортировки, выбираемый по умолчанию. Этот метод производит сравнение блоков непосредственно, не обращаясь к методу init. */ Def new(self, size aCollection) { aCollection := init(variableNew(self : Behavior, size)); aCollection.compareBlock := { using(item1, item2) iter^ item2 }; AaCollection } ! • now(SortedCollectionClass)!!
202 Объектно-ориентированный пользовательский интерфейс для Windows /* Выдать новый объект-SortedCollection, используя указанный метод сортировки */ Def sortBlock(self, size, sortBlock aCollection) { aCollection := init(variableNew(self : Behavior, size)); aCollection.compareBlock := sortBlock; ^aCollection } !! now(SortedCollection)!! /* Инициализирует объект SortedCollection. Замечание: Этот метод теперь не записывает сортировку блоков, используемую по умолчанию. Она запоминается в новом методе для класса */ Def init(self) { firstElement := lastElement := 0; } !! Листинг 4. Файл класса FileString: FILESTRI.CLS /* Этот класс используется для разбора строки, в которой содержится имя файла */ !! inherit(Object, #FileString, #(fileString), 2, nil)!! now(FileStringClass)!! /* Выдать новый объект FileString, используя в качестве его имени строку из параметра */ Def new(self, aString) { ^setName(new(self : Behavior), aString); } '!! now(FileString)!! /* Выдать спецификацию маршрута, содержащуюся в fileString. Она не включает в себя имя дискового драйвера и имя файла. Этот метод предполагает, что вся информация, следующая за именем дискового драйвера, определяет только маршрут */ Def asPath(self startPos) { startPos := indexOf(fileString, ':', 0); if startPos startPos := startPos; else startPos := -1; endif; ^subString(fileString, startPos + 1, size(fileString) + 1); } !! /* Дописать aString в качестве имени файла в fileString. Это бывает полезно, когда текущий fileString содержит лишь указа¬ ние на директорий */ Def appendFile(self, aString) { if at(fileString, size(fileString) " 1) = '\' fileString := fileString + aString;
Директории DOS 203 else fileString := fileString + "\" + aString; endif; } !! /* Выдать расширение файла, содержащееся в fileString. Оно не будет содержать имени драйвера диска, спецификации маршрута и имени файла */ Def fileExtention(self fileSpec, dotPos) { fileSpec := fileSpec(self); dotPos := indexOf(fileSpec, '.', 0); if not(dotPos) Л M If . / else ^sudString(fileSpec, dotPos = 1, size(foleSpec) + 1); endif; } !! /* Выдать спецификацию файла, содержащуюся в fileString. Она не включает имени дискового драйвера или спецификации маршрута — только имя файла и его расширение */ Def fileSpec(self curSlash, lastSlash, startPos) { startPos := indexOf(fileString, ":", 0); if startPos startPos := startPos; else startPos := -1 endif; lastSlash := startPos; loop while (curSlash := indexOf(fileString, '\', lastSlash+1)) lastSlash := curSlash; endLoop; ^subString(fileString, lastSlash+1, size(fileString) + 1); } 11 /* Выдать содержимое fileString */ Def fileString(self) { ^fileString } /* Выдать имя файла, содержащееся в fileString. Оно не содержит ни имени дискового драйвера, ни спецификации маршрута, ни расширения */ Def fileName(self fileSpec, dotPos) { fileSpec := fileSpec(self); dotPos := indexOf(fileSpec, '.', 0)? if not(dotPos) ^fileSpec; else AsubString(fileSpec, 0, dotPos); endif;
204 Объектно-ориентированный пользовательский интерфейс для Windows } !! /* Выдать спецификацию маршрута, содержащуюся в fileString. Оно не содержит ни имени дискового драйвера, ни имени файла */ Def pathSpec(self curSlash, lastSlash, startPos) { startPos := indexOf(fileString, ”:", 0); if startPos startPos := startPos; else startPos := -1 endif; lastSlash := startPos; loop while (curSlash := indexOf(fileString, '\', lastSlash+1)) lastSlash := curSlash; endLoop; AsubString(fileString, startPos, lastSlash+1); } !! /* Выдать строку, содержащую букву дискового драйвера, вслед за которой стоит двоеточие. Если в fileString нет буквы драйвера, то вернуть пустую строку */ Def diskDrive(self) { if indexOf(fileString, ':', 0) = 1 AsubString, 0, 2); else А П I» . / endif; } !! /* Присвоить переменной fileString значение aString */ Def setName(self, aString) { fileString := aString); } !! Листинг 5. Файл класса Directory: DIRECTOR.CLS /************************************************************ ** ** Объект Directory представляет директорий с указанной ** буквой драйвера и спецификацией маршрута. Методы, ** используемые этим объектом, наследуются из класса DirStuff, ** разработанного Марком Солинским йз Whitewater Group. ** Public Domain класс, автор: Джеймс В. Xoy ** Дата: 06/29/1988 ************************************************************^ inherit(Object, #Directory, #(diskDrive /* Буква дискового драйвера */ pathName /* не включает драйвера */) t 2, nil) ! ! now(DirectoryClass)!!
Директории DOS 205 /* Выдать новый объект-директорий, соответствующий указанному имени файла */ Def pathName(self, aString theDir, aFileString) { aFileString := new(FileString, aString); theDir := new(Directory); theDir.pathName := asPath(aFileString); theDir.diskDrive(aFileString); if diskDrive(theDir) = "" theDir.diskDrive ;+ currentDisk(self); endif AtheDir; } !! /* Выдать полную спецификацию маршрута, включающую букву дискового драйвера для текущего директория */ Def fullName(self) { AcurrentDisk(self) + currentPath(self); } !i /* Сменить директорий. */ Def makeCurrent(self, aPathName aDTA, ds, lpPN, result) { aDTA := getDTA(Directory); ds := new(DosStruct); lpPN := lP(aPathName); fill(ds, 0); setPtr(ds, lpPN, DOS DS, DOS_DX); setCall(ds, ОхЗВ); /^"функция DOS CHDIR */ call(ds) ; result := getError(ds); if result 0 errorBox("Dos Error", loadString(DOS_OFFSET+result)); endif; freeHandle(aPathName); } !! /* Удалить указанный директорий */ Def remove(self, aPathName aDTA, ds, lpPN, result) { aDTA := getDTA(Directory); ds := new(DosStruct); lpPN := lP(aPathName); fill(ds, 0); setPtr(ds, lpPN, DOS_DS, DOS_DX); setCall(ds, 0x3A); /* функция DOS RMDIR */ call(ds) ; result := getError(ds); if result 0 errorBox("Dos Error”, loadString(DOS_OFFSET+result)); endif; freeHandle(aPathName); } !!
206 Объектно-ориентированный пользовательский интерфейс для Windows /* Создать указанный директорий */ Def create(self, aPathName aDTA, ds, lpPN, result) { aDTA := getDTA(Directory); ds := new(DosStruct); lpPN := lP(aPathName); fill(ds, 0); setPtr(ds, lpPN, DOS_DS, DOS_DX); setCall(ds, 0x39); /* функция DOS MKDIR */ call(ds); result := getError(ds); if result 0 errorBox("Dos Error", loadString(DOS_OFFSET+result)); endif; freeHandle(aPathName); } !! /* Выдать отсортированную коллекцию файлов для указанного маршрута*/ Def filesOf(self, dir, fileStr, mask | ds, lpPN, aDTA, files, result, aPathName, aFileString, anArray, errors) { files := sortBlock(SortedCollection, 1, {using(elem1,elem2) at(elem1, 0)at(elem2, 0()}); ds := new(DosStruct); aDTA := getDTA(self); fileStr := if fileStr then fileStr; else »★ ★". • t endif; mask := if mask then mask; else 0x0; endif; aFileString := if dir then new(FileString, dir); else new(FileString, currentDisk(self) + currentPath(self)); endif; aPathName := fileString(appendFile(aFileString, fileStr)); lpPN := lP(aPathName); fill(ds, о); setPtr(ds, lpPN, DOS_DS,DOS_DX); putWord(ds, mask, DOS_CX); setCall(ds, 0x4E); call(ds); getError(ds); errors := new(Set, 2); add(errors, 2); add(errors, 18);
Директории DOS 207 loop result := wordAt(ds, DOS_AX); while not(result in errors) begin anArray := new(Array, 2); put(anArray, removeNulls(setClass(fillStruct(new(Struct, 13), aDTA+30L),String)), 0); put(anArray, (wordAt(aDTA+21L) bitAnd 0xFF), 1); add(files, anArray); fill(ds, 0); setPtr(ds, lpPN, DOS_DS,DOS_DX); putWord(ds, mask, DOS_CX); setCall(ds, 0x4F); /* функция DOS Find Subsequent */ call(ds); getError(ds); endLoop; freeHandle(aPathName); ^files; } !! /* Выдать отсортированную коллекцию директориев для указанного маршрута*/ Def subdirectoriesOf(self, dir, fileStr | ds, lpPN, aDTA, subdirs, aPathName, aFileString, errors) { subdirs := new(SortedCollection, 1); ds.:= new(DosStruct); aDTA := getDTA(self); fileStr := if fileStr then fileStr; else »★ ★». • / endif; aFileString := if dir then new(FileString, dir); else new(FileString, currentDisk(self) + currentPath(self)); endif; aPathName := fileString(appendFile(aFileString, fileStr)) lpPN := lP(aPathName); fill(ds, 0); setPtr(ds, lpPN, DOS_DS,DOS_DX); putWord(ds, 0x10, DOS_CX); setCall(ds, 0x4E); call(ds); getError(ds); errors := new(Set, 2); add(errors, 2); add(errors, 18); loop result := wordAt(ds, DOS_AX); while not(result in errors) begin
208 Объектно-ориентированный пользовательский интерфейс для Windows if{wordAt(aDTA+21L) bitAnd 0xFF) = 16 /* Директорий */ add(subdirs, removeNull(selfClass(fillStruct(new(Struct, 13) aDTA+30L), String)); endif; fill(ds, 0); setPtr(ds, lpPN, DOS_DS,DOS_DX); putWord(ds, 0x10, DOS_CX); setCall(ds, 0x4F); /* функция DOS Find Subsequent */ call(ds); getError(ds); endLoop; freeHandle(aPathName); Asubdirs; } !! /* Выдать значением DTA по умолчанию */ Def getDTA(self | ds) { ds := new(DosStruct); setCall(ds, 0x2F); call(ds); getError(ds); ^pack(wordAt(ds, DOS_BX), wordAt(ds, DOS_ES)) } !! /* Выдать новый объект-директорий, соответствующий текущему директорию */ Def current(self | theDir) { theDir := new(Direotor^); theDir.pathName := currentPath(self); theDir.diskDrive := currentDisk(self); ^theDir; } !! /* Выдать метку тома для диска, содержащего текущий директорий */ Def volumeLabel(self | ds, lpPN, aDTA, result, volumeLabel, aPathName, aFileString, errors) { ds := new(DosStruct); aDTA := getDTA(self); aFileString := new(FileString, fullName(self)); aPathName := fileString(appendFile(aFileString, "*.*")); lpPN := lP(aPathName); fill(ds, 0); setPtr(ds, lpPN, DOS_DS,DOS_DX); putWord(ds, 0x8, DOS_CX); setCall(ds, 0x4E); call(ds); getError(ds) ; errors := new(Set, 2); add(errors, 2) ;
Директории DOS 209 add(errors, 18); result := wordAt(ds, DOS_AX); if not(result in errors) then volumeLabel := removeNulls(setClass(fillStruct(new(Struct, 3), aDTA + 30L), String)); else volumeLabel := nil;- endif; freeHandle(aPathName); AvolumeLabel; } П /* Выдать строку, содержащую маршрут текущего директория */ Def currentPath(self | ds, lpDir, aTC, aStr, bStr) { ds := new(DosStruct); fill(ds, 0); lpDir := Lp(aStr := new(String, 128)); setPtr(ds, lpDir, DOS_DS,DOS_SI); putWord(ds, 0x8, DOS_CX); setCall(ds, 0x47); call(ds); bStr := "\" + removeNulls(getText(Handles[aStr])); freeHandle(aStr); getError(ds); ’AbStr; } !! /* Выдать букву драйвера диска.*/ Def currentDisk(self | ds) { ds := new(DosStruct); setCall(ds, 0x19); call(ds); getError(ds); AasString(asChar(atLSB(ds, DOS_AX) + asInt('A'))) + ":" } !! now(Directory)!! /* Выдать отсортированную коллекцию файлов для self */ Def filesOf(self, fileStr, mask | ds, lpPN, aDTA, files, result, aFileString, aPathName, anArray, errors) { files := sortBlock(SortedCollection, 1, {using(elem1,elem2) at(elem1, 0)at(elem2, 0()}); ds := new(DosStruct); aDTA := getDTA(Directory); fileStr := if fileStr then fileStr; else »«* *". • / endif; mask := if mask then mask;
210 Объектно-ориентированный пользовательский интерфейс для Windows else 0x0; endif; aFileString := new(FileString, fullName(self)); aPathName := fileString(appendFile(aFileString, fileStr) ) ; lpPN := lP(aPathName); fill(ds, 0); setPtr(ds, lpPN, DOS_DS,DOS_DX); putWord(ds, mask, DOS_CX); setCall(ds, 0x4E); call(ds); getError(ds); errors := new(Set, 2) ; add(errors, 2); add(errors, 1 8); loop result := wordAt(ds, DOS_AX); while not(result in errors) begin anArray := new(Array, 2); put(anArray, removeNulls(setClass(fillStruct(new(Struct, 13), aDTA+30L), String)), 0); put(anArray, (wordAt(aDTA+21L) bitAnd 0xFF), 1); add(files, anArray); fill(ds, 0); setPtr(ds, lpPN, DOS_DS,DOS_DX); putWord(ds, mask, DOS_CX); setCall(ds, 0x4F); /* функция DOS Find Subsequent */ call(ds); getError(ds); endLoop; freeHandle(aPathName); ^files; } П /* Выдать true, если aDirectory представляет тот же директорий, что и получатель данного сообщения; в противном случае — false */ Def =(self, aDirectory) { AfullName(self) = fullName(aDirectory) } П /* Выдать true, если получатель имеет хотя бы один поддиректорий */ Def hasSubdirectory(self | ds, result, lpPN, aDTA, aFileString, aPathName, errors) { ds := new(DosStruct); aDTA := getDTA(Directory); aFileString := new(FileString, fullName(self)); aPathName := fileString(appendFile(aFileString, "*.*"));
Директории DOS 211 lpPN := lP(aPathName); fiU(ds, 0); setPtr(ds, lpPN, DOS_DS,DOS_DX); putWord(ds, 0x10, DOS_CX); setCall(ds, 0x4E); call(ds); getError(ds); errors := new(Set, 2) ; add(errors, 2); add(errors, 18); result := wordAt(ds, DOS_AX); if result = 0 then result := true; else if not(result in errors) then errorBox("DosError", LoadString(DOS_OFFSET+result)) ; endif; result := false; endif; freeHandle(aPathName) ; Aresult; } !! /* Удалить директорий, указанный в self */ Def remove(self | aPathName, aDTA, ds, lpPN, result) { aDTA : = getDTA(Directory); ds := new(DosStruct); aPathName := fullName(self); lpPN := lP(aPathName); fill(ds, 0); setPtr(ds, lpPN, DOS_DS, DOS_DX); setCall(ds, 0x3A); /* функция DOS RMDIR */ call(ds); result := getError(ds); if result 0 errorBox("Dos Error", loadString(DOS_OFFSET+result)); endif; freeHandle(aPathName); } ! ! /* Создать директорий, указанный в self */ Def create(self | aPathName, aDTA, ds, lpPN, result) { aDTA := getDTA(Directory); ds := new(DosStruct); aPathName := fullName(self); lpPN := lP(aPathName); fill(ds, 0); setPtr(ds, lpPN, DOS_DS, DOS_DX); setCall(ds, 0x39); /* функция DOS MKDHl */ call(ds); result := getError(ds); if result 0
212 Объектно-ориентированный пользовательский интерфейс для Windows errorBox("Dos Error", loadString(DOS_OFFSET+result)); endif; freeHandle(aPathName); } ! ! /* Сменить директорий, указанный в self */ Def makeCurrent(self | aPathName, aDTA, ds, lpPN, result) { aDTA := getDTA(Directory); ds := new(DosStruct); aPathName := fullName(self); lpPN := lP(aPathName); fill(ds, 0); setPtr (ds, lpPN, DOS_DS, DOS_DX); setCall(ds, 0x3B); /* функция DOS CHDIR */ call(ds); result := getError(ds); if result 0 errorBox("Dos Error”, loadString(DOS_OFFSET+result)); endif; freeHandle(aPathName); } !! /* Выдать отсортированную коллекцию директориев для указанного объекта*/ Def subdirectoriesOf(self, fileStr | ds, lpPN, aDTA, subdirs, aFileString, aPathName, errors) { subdirs := new(SortedCollection, 1); ds := new(DosStruct); aDTA := getDTA(self); if fileStr = nil then fileStr := "*.*"; endif; aFileString := new(FileString, fullName(self)); aPathName := fileString(appendFile(aFileString, fileStr)); lpPN := lP(aPathName); fill(ds, 0); setPtr(ds, lpPN, DOS_DS,DOS_DX); putWord(ds, 0x10, DOS_CX); setCall(ds, 0x4E); call(ds); getError(ds); errors := new(Set, 2); add(errors, 2); add(errors, 18); loop result := wordAt(ds, DOS_AX); while n^>t(result in errors) begin \ if(wordAt(aDTA+21L) bitAnd 0xFF^ - 16 /* Директорий */ add(subdirs,
Директории DOS 213 removeNulls(setClass(fillStruct(new(Struct, 13) aDTA+30L), String)); endif; fill(ds, 0); setPtr(ds, lpPN, DOS_DS,DOS_DX); putWord(ds, 0x10, DOS_CX); setCall(ds, 0x4F); /* функция DOS Find Subsequent */ call(ds) ; getError(ds); endLoop; freeHandle(aPathName); ^subdirs; /* Установить новое значение маршрута данного объекта равным aString */ Def setPathName(self, aString) { pathName := aString; } ! ! /* Установить новое имя дискового драйвера данного объекта-директория равным aCharacter */ Def setDiskDrive(self, aCharacter) { diskDrive := asString(aCharacter) + ”:"; } !! /* Выдать для данного директория полную спецификацию маршру¬ та, включая букву дискового драйвера */ Def fullName(self) { ^diskDrive(self) + pathName(self); } !! /* Выдать метку тома для диска, содержащего объект */ Def volumeLabel(self | ds, lpPN, aDTA, result, volumeLabel, aFileString, aPathName, errors) { ds := new(DosStruct); 'aDTA := getDTA(Directory); aFileString := new(FileString, fullName(self)); aPathName := fileString(appendFile(aFileString, "*.*")); lpPN := lP(aPathName); fill(ds, 0); setPtr(ds, lpPN, DOS_DS,DOS_DX); putWord(ds, 0x8, DOS_CX); setCall(ds, 0x4E); call(ds); getError(ds); errors := new(Set, 2); add(errors, 2); add(errors, 18);
214 Объектно-ориентированный пользовательский интерфейс для Windows result := wordAt(ds, DOS_AX); if not(result in errors) then volumeLabel := removeNulls(setClass(fillStruct(new(Struct, 13), aDTA + 30L), String)); else volumeLabel := nil; endif; freeHandle(aPathName); AvolumeLabel; } !! /* Выдать маршрут, ассоциированный с данным объектом-директорием */ Def pathName(self); { ApathName; } !! /* Выдать дисковый драйвер, ассоциированный с данным объектом-дирскторием */ Def diskDriver(self) { AdiskDrive; } !! Листинг 6. Direct.RC (Справа от текста диагностических сообщений приводится их русский пере¬ вод.— • Пф «5|¥ • ¥Ф .) ;Dos error messages (Error number+DOS_OFFSET) DOS_OFFSET+1, "Invalid function number" ^ неверный номер функции D0S_0FFSET+2, "File not found" ^ файл не найден DOS_OFFSET+ 3, "Path not found" ^ маршрут не найден DOS_OFFSET+4, "Too many open files" " слишком много открытых файлов D0S_0FFSET+5, "Invalid function number" ' неверный номер функции DOS_OFFSET+6, "File not found" ~ файл не найден DOS_OFFSET+7, "Path not found" " маршрут не найден D0S_0FFSET+8, "Too many open files" ^ слишком много открытых файлов D0S_0FFSET+9, "Invalid function number" ^ неверный номер функции DOS_OFFSET+10, "Invalid environment" ' неверная среда DOS_OFFSET+11, "Invalid format" ^ неверный формат DOS_OFFSET+12, "Invalid access code" “ неверный код доступа DOS_OFFSET+1 3, "Invalid data" " неверные данные D0S_0FFSET+15, "Invalid drive specified" " неверно указан драйвер DOS_OFFSET+16, "Attempt to remove current directory" попытка уничтожить текущий директорий DOS_OFFSET+1 7, "Not same device" ^ не то же устройство DOS 0FFSET+18, "No more files" ^ нет больше файлов
Директории DOS 215 DOS_OFFSET+19, DOS_OFFSET+2O, DOS_OFFSET+21, DOS_OFFSET+22, DOS_OFFSET+23, DOS_OFFSET+24, DOS_OFFSET+25, DOS_OFFSET+26, DOS_OFFSET+27, DOS_OFFSET+28, DOS_OFFSET+29, DOS_OFFSET+ 3 0, DOS_OFFSET+ 31 , DOS_OFFSET+32, DOS_OFFSET+33, DOS_OFFSET+34, DOS_OFFSET+ 3 5, DOS_OFFSET+36, DOS_OFFSET+50, DOS_OFFSET+51, DOS_OFFSET+52, DOS_OFFSET+53, DOS_OFFSET+54, DOS_OFFSET+ 5 5, DOS_OFFSET+56, DOS_OFFSET+57, DOS_OFFSET+58, DOS_OFFSET+59, DOS_OFFSET+6O, DOS_OFFSET+61, DOS_OFFSET+ 6 2, DOS_OFFSET+63, DOS_OFFSET+64, DOS_OFFSET+65, "Attempt to write on write-protected diskette" " попытка писать на дискету, защищенную от записи "Unknown unit" ^ неизвестное устройство "Drive not ready" ^ драйвер не готов "Unknown command" ' неизвестная команда "Data error (CRC)" " ошибка в данных (CRC) "Bad request structure length" неверная длина структуры требования "Seek error" ^ ошибка поиска "Unknown media type" ^ ^известный тип среды "Sector not found" “ сектор не найден "Printer out of paper" ^ кончилась бумага в принтере "Write fault" ^ зарегистрировать неисправность "Read fault" ' прочитать неисправность "General failure" " общий отказ "Sharing violation" ^ нарушение доступа к общим данным "Lock violation" ^ нарушение блокировки "Invalid disk change" ^ неверная смена диска "FCB unavailable" ^ недопустимый FCB "Sharing buffer overflow" ^ переполнение буфера общих данных "Network request not supported" ^ работа с сетью не поддерживается "Remote computer not listening" удаленный компьютер не доступен "Duplicate name on network" дублирующееся имя сети "Network name not found" ^ имя сети не найдено "Network busy" ^ сеть занята "Network device no longer exists" сетевое устройство больше не существует "NETBIOS command limit exceeded" ^ превы¬ шено ограничение команды системы NETBIOS "Network adapter hardware error” аппаратная ошибка в сетевом адаптере "Incorrect response from network" некорректный возврат из сети "Unexpected network error" неописанная ошибка сети "Incompatible network adapter" несовместимый сетевой адаптер "Print queue full" ' очередь принтера заполнена "Not enough space for print file" нет места для файла печати "Print file was deleted" ^ файл печати был удален "Network name was deleted" " имя сети было уничтожено "Access denied" доступ запрещен
216 Объектно-ориентированный пользовательский интерфейс для Windows DOS_OFFSET+66, DOS_OFFSET+67, DOS_OFFSET+68, DOS OFFSET+69, DOS_OFFSET+7Q, DOS_OFFSET+71, DOS OFFSET+72, DOS__OFFSET+8O, DOS__OFFSET+82, DOS_OFFSET+83, DOS_OFFSET+84, DOS_OFFSET+85, DOS_OFFSET+86, DOS_OFFSET+87, DOSOFFSET+88, "Network device type incorrect" некорректный тип сетевого устройства "Network паше not found" " имя сети не найдено "Network name limit exceeded" превышено ограничение на имя сети "NET3I0S session limit exceeded" превышено ограничение сеанса системы NETBIOS "Temporarily paused" временная задержка "Network request not accepted" ' -сетевой запрос не принят . "Printer or disk redirection is paused" переназначение принтера или диска задерживается "File exist" ^ файл (уже) существует "Cannot make directory entry" " нельзя создать вход в директорий "Fail on INT 24" ^ отказ на прерывании 24 "Too many redirections" ' слишком много переназначений "Duplicate redirection" ' повторное переназначение "Invalid password" " неверный пароль "Invalid parameter" " неверный параметр "Network data fault" ^ поврежденные сетевые данные
Глава 7 ■ ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ГРАФИЧЕСКОЕ ПРОГРАММИРОВАНИЕ ДЛЯ WINDOWS Глава посвящена подробному рассмотрению основ объектнс-ори- ентированного программирования графики в Windows, использу¬ ющего встроенные возможности Актора. Среди рассматриваемых вопросов битовые карты и рисование цветных прямоугольников, эллипсов, многоугольников и диаграмм. То, что среда Windows поддерживает рисование графических объектов в своих окнах, не является неожиданностью. Однако некоторые следствия из этого факта далеко не очевидны. Одна из первых трудностей, которую надо преодолеть, связана с координа¬ тами. Любое окно в среде типа Windows может двигаться по экрану. Однако хотелось бы, чтобы графические объекты, создан¬ ные в окне, двигались вместе с ним и сохраняли свои очертания всегда, когда окно видно на экране. Это подразумевает наличие двух различных систем координат: одной для экрана, второй для окна. В большинстве систем программирования в Windows вам приходится сталкиваться с этой проблемой постоянно. ■ ВСТРОЕННЫЕ ГРАФИЧЕСКИЕ КЛАССЫ В Акторе всего шесть основных графических классов: Point, Graphics Object, REct, RndRect, WinPolygon и WinEliipse. Эти классы не являются производными от Point. На самом деле они производные от класса Struct. Классы построены как коллекция объектов Point, а не как специализации этого класса. В системе с множественным наследием графические классы могли бы насле¬ довать из более чем одной линии наследования, так что автомати¬ чески приобретались бы как графические, так и другие свойства.
Объектно-ориентированное графическое программирование для Windows Все классы графических объектов обеспечивают свои собственные версии метода draw. Иерархия встроенных графических классов в Акторе выглядит так: Object Collection IndexedCollection ByteCollection Struct GraphicsObject Rect RndRect WinEllips WinPolygon В последующих разделах мы рассмотрим наиболее важные из этих встроенных графических классов. ■ КЛАСС POINT Объекты Point являются двумерными, с двумя экземплярными переменными x и у. Эти переменные представляют пары коорди¬ нат, определяющих двумерную точку. Объект Point можно создать одним из трех различных способов: 1) используя сообщение new, с последующей установкой значения координат при помощи сооб¬ щений setX и setY; 2) создавая литеральную точку непосредствен¬ но, аналогично способу, при помощи которого мы могли бы создать некоторый массив; 3) при помощи сообщения point. Ниже приво¬ дятся примеры трех различных способов создания одной и той же точки: Start := new(Pomt}; setX(Start, 30); setY(Start, 40); Start := 30040; Start := point(30,40); ■ SCRIBBLE (КАРАКУЛИ) Демонстрационный пример Scribble, который поставляется вместе с Актором, использует одноименный класс, показывающий, как относительно просто можно быстро получить полезные графиче¬ ские программы. Для того, чтобы создать окно Scribble, исполь¬ зуйте следующие сообщения: Sc := defaultNew(Scribble, "Scribble Away"); show(Sc, 1);
Scribble (каракули) 219 Вообще класс Scribble — это хорошее введение в графическое программирование при помощи Актора и объектно-ориентирован¬ ных систем, поскольку он предоставляет возможности ручного рисования при помощи мыши, путем реализации всего лишь четырех методов и единственной экземплярной переменной. Мы последовательно рассмотрим каждый из компонентов данного приложения. Как и в любых интерактивных графических программах Актора, использующих мышь, управление ею в Scribble обеспечивается посредством трех методов, называемых beginDrag, drag и endDrag соответственно. Три 'Jти метода соответствуют сообщениям Windows: WM_LBUTTONDOWN, WM_MOUSEMOVE и WM_LBUTTONUP. Эти сообщения посылаются, когда нажимается левая кнопка мыши, когда мышь передвигается при нажатой кнопке и когда кнопка отпускается. Единственной экземплярной переменной Scribble служит перемен¬ ная dragDC, которая хранит позицию, куда была приведена мышь. Сообщение beginDrag запоминает контекст дисплея в экземпляр¬ ной переменной dragDC и устанавливает положение точки, где в данный момент находится мышь. Этот метод выглядит так: Def beginDrag(self, wP, point) { dragDC := get(Context(self); moveTo(point, dragDC); } Метод drag проводит прямую, связывающую точку с позицией из dragDC. Def drag(self, wP point) { lineTo(point, dragDC); } Метод endDrag завершает рисование прямой, освобождая контекст дисплея, когда кнопка отпускается. Def endDrag(self, wP, point) { releaseContext(self, dragDC); } И наконец вводится метод для использования правой кнопки мыши для очищения экрана. Def WM_RBUTTONDOWN(self, wp, lp) { repoint(self); } Хотя данная программа делает не слишком много, но этого доста¬ точно для тех задач, для которых она предназначена. Просто удивительно, какой короткий код пришлось написать для того, чтобы сделать все это. И важно, что программа интересна не только сама по себе, все интерактивные графические программы можно писать таким же образом, с использованием одних и тех же трех
220 Объектно-ориентированное графическое программирование для Windows методов управления мышьк: beginDrag, drag и endDrag. Ниж приводится формальное опьсание класса Scribble. ■ ОПИСАНИЕ КЛАССА SCRIBBLE Исходный файл: SCRffiBLE.CLS Наследует из: Window Экземплярные переменные: dragDC Методы объекта: beginDrag Инициализирует передвижение мыши, запоминая контекст дисплея для рисования drag Реагирует на сообщение от мыши, рисует прямую, соединяющую текущую позицию и указанную точку endDrag Завершает передвижение мыши, освобождая контекст дисплея. WM_RBUTTONDOWN реагирует на соответствующее сообщение MS-Windows, стирая окно при нажатии правой кнопки на мыши ■ ПРЯМОУГОЛЬНИКИ Говоря на языке геометрии, прямоугольник — это особый тип многоугольников, поэтому можно было бы ожидать, что Rectangle (прямоугольник) должен быть подклассом из WinPolygon (много¬ угольников). Однако в Акторе это не так. Rect находится на одном уровне с классом WinPolygon. Причиной этого служит особое внимание, придаваемое прямоугольникам в AP^ поскольку пря¬ моугольники используются буквально повсеместно, при их помо¬ щи создаются окна и элементы управления Q?uc. 7.1). Для прямоугольников нет необходимости наследовать свои возможно¬ сти. Эти возможности заложены в функциях Windows. Ниже приводится интерактивный сеанс, в котором создается пря¬ моугольник R1 и затем запрашиваются его ширина и высота. R1 := rect (5, 5, 15, 10) ; Rect (5 5 15 10) with(R1); 10 height(R1); 5 Следующий метод вычисляет площадь прямоугольника: Def area(self) { ^abs(right(self) - left(self)) * abs(bottom(self) - top(self) ) ;
Прямоугольники 221 Рис. 7.1. Рисование в окне гурямоугольника с закругленными краями Для применения этого метода к прямоугольнику, определенному выше, достаточно сообщения: area(R1); 50 Аналогично мы можем определить метод для вычисления пери¬ метра: Def perimeter(self) { ^(2 * abs (right(self) - left(self)) + (2* abs(bottom(self) - top(self)))); } • perimeter(R1) ; 30 И наконец ниже приводится метод, который использует теорему Пифагора для вычисления диагонали прямоугольника: Def diagonal(self) { ^(square(abs(right(self) - left(self))) + (square(abs(bottom(self) - top(self)))) ** .5; } diagonal(R1) ; 11.18033989
222 Объектно-ориентированное графическое программирование для Windows ■ ПРЯМОУГОЛЬНИКИ С ЗАКРУГЛЕННЫМИ КРАЯМИ Прямоугольники с закругленными краями реализуются в Акторе при помощи класса RndRect. Ниже приводится интерактивный сеанс, в котором сначала создается объект RndRect, а затем задается его размер: RR := new(RndRect) RndRect(0 0 0 0 0 0) init(RR, 5, 5, 15, 10, 10, 2); RndRect(5 5 15 10 10 2) Для того чтобы нарисовать объект RndRect, мы должны создать окно, в котором он будет нарисован. WW := new(Window, ThePort, nil, "Window For Round Rectangle”, &(275, 60, 500, 200)); show(WW, 1); RR := new(RndRect); imt(RR, 25, 25, 75, 80, 1 5, 10); Wct := getContext(WW); draw(RR, Wct); releaseContext(WW, Wct); Для того чтобы увеличить размеры такого прямоугольника, надо просто послать следующие сообщения: inflate(RR, 20, 20); Wct := getContext(WW); draw(RR, Wct); releaseContext(WW, Wct); ■ ЭЛЛИПСЫ Лри рисовании эллипса необходимо воспользоваться тем же опы¬ том, что и при рисовании прямоугольника. Сначала надо создать подходящее окно. После этого эллипс инициализируется как объект класса WinEllipse, сохраняется контекст, изображение выдается на дисплей. WW := new(Window, ThePort, nil, "Window For Ellipse”, & (275, 60, 500, 200)); show(WW, 1); E1 := new(WinEllipse); init(E1, 55, 25, 155, 75); cxt := getContext(WW); draw(E1, cxt); releaseContext(WW, cxt); ■ МНОГОУГОЛЬНИКИ ДХ'Я реализации многоугольников Актор использует класс WinPolygon 0vuc. 7.2). Объект WinPolygon является по существу коллекцией объектов Point. Это более сложный графический объ¬ ект, чем те, с которыми мы имели дело ранее, но вместе с тем
Многоугольники 223 Рис. 7.2. Псввдотрвхмврный многоугольник развитие того же подхода, какой использовался для прямоуголь¬ ников. Для использования класса WinPolygon при рисовании параллелог¬ рамма можно выполнить следующие сообщения: WW := new(Window, ThePort, nil, "Window For Parallelogram", &(275, 60, 600, 300)); show(WW, 1); P1 := new(WinPolygon, #(100050 250050 2500150 150@150 100@50)); cxt := getContext(WW); draw(P1, cxt); rele'aseContext(WW, cxt); Метод draw для WinPolygon записывается в одну строчку: Def draw(self, hdc) { Call Polygon(hdc, self, size(self)/4); } Как можно заметить, метод draw класса WinPolygon просто выьы- вает функцию Windows Polygon, но при этом делит размер яв четыре для того, чтобы подготовить корректный аргумент nCount Ключом к пониманию класса Polygon является метод класса new, его определение таково:
Объектно-ориентированное графическое программирование для Windows Def new(self, aColl aPoly) { aPoly := variableNew(self : Behavior, size(aColl) * 4); do(over(0), size(aColl)), { using)idx) putWord(aPoly, x(aColl(idx), idx * 4); putWord(aPoly, y(aColl(idx), (idx * 4) + 2) }; ^aPoly; } Что здесь происходит? Просто этот метод перебирает всю коллек¬ цию точек и использует метод putWord, наследуемый из класса Struct, для получения памяти под каждую точку в коллекции при запоминании данных многоугольника. Метод variableNew, предо¬ ставляемый в классе Behavior, как раз и используется для опре¬ деления новых методов класса Collection. Эти сообщения используют класс WinPolygon при рисовании ”псевдотела”: Pc := new(Window, ThePort, nil, "Window For Solid”, &(175, 50, 600, 400)); show(Pc, 1); P1 := new(WinPolygon, #(1000100 2000100 2500200 1500200 1000100)); P1 := new(WinPolygon, #(1000100 154075 250075 2000100 1000100)); P1 := new(WinPolygon, #(2500200 2930167 250075 2000100 2500200)); cxt := getContext(Pc); draw(P1, cxt); draw(P2, cxt); draw(P3, cxt); releaseContext(Pc, cxt); При использовании WinPolygon важно помнить, что базисная функция Polygon всегда будет закрывать рисунок, таким образом придется специфицировать точки для всего четырехугольника, иначе появятся ненужные диагонали и автоматически они закро¬ ют рисунок. Ниже приводится последовательность сообщений, которая иллю¬ стрирует использование функции Windows PolyPolygon: PP1 := new(Wmdow, ThePort, nil, "Window For PolyPolygon”, &(175, 50, 600, 400)); show(PP1, 1); cxt := getContext(PP1); Call PoiyPolygon(cxt, #(#(20020 40020 60080 40080 20020)), #(5), 1); releaseContext(PP1, cxt); Другой пример альтернативного способа рисования и анимации псевдокуба представлен в демонстрационной программе Актора Hypercybe. Если вы хотите запустить программу Актора для анимации гиперкуба в рамках интерактивного сеанса, в рабочей области Актора, воспользуйтесь сообщениями:
Многоугольники 225 CW := defaultMew(CubeWindow, "HyperCube"); show(CW, 1); run(CW); Одно из наблюдений, которое вы могли сделать по поводу всех графических объектов, рисовавшихся до сих пор, состоит в том, что они исчезают, как только фокус ввода переносится в другое окно или окно передвигается или изменяет свои размеры. Это происходит потому, что окно перерисовывает себя и не имеет возможности узнать, что в нем имеется объект, который тоже надо перерисовать. Для того, чтобы быть уверенным, что содержимое окна будет перерисовываться всегда при получении сообщения paint, необходима новая версия сообщения paint. Руководство по Актору предлагает следующий метод paint для класса GraphicsWindow: Def paint(self, hDC theRect) { theRect := rect(10, 10, width(clientRect(self))/2, height(clientRect(self))/2); draw(theRect, hDC); } Проследим за действиями этого метода. Если у вас есть Актор, введите метод в класс GraphicsWindow и выполните эти сообщения в рабочей области: W := defaultNew(GraphicsWindow, "Try Me"); show(W, 1); Как вы можете видеть, в окне уже есть прямоугольник, и он перерисовывается каждый раз, когда окно получает сообщение paint (рисовать) из Windows. Это же вы видите и в данной программе. Вместе с тем то, что написано здесь, имеет крайне узкую направленность, метод paint занимается тем, что всегда перерисовывает именно этот прямоугольник. Один из способов придать этой программе большую общность состоит в том, чтобы определить экземплярную переменную, ко¬ торая будет поддерживать коллекцию всех графических объектов, нарисованных в данном окне. В метод draw следовало бы добавить работу с объектом, который представляет коллекцию перерисовы¬ ваемых объектов . В этом случае метод paint можно было бы повторно вызывать для каждого элемента коллекции и перерисо¬ вывать их. Важной деталью здесь является поиск подходящего способа передачи имени особого окна в метод draw, так, чтобы было известно, в каких окнах следует изменить список объектов. Было бы идеально, если бы методы draw переписывались так, чтобы они могли работать и с окнами, которые поддерживают такой механизм. «-857
Объвктно-оривнтированнов графическое программирование для Windows Ш ЦВЕТНЫЕ БИТОВЫЕ КАРТЫ Как мы уже отмечали в Windows, начиная с версии 3,0 и далее, поддерживаются аппаратно-независимые цветные битовые карты (bitmaps). Нет необходимости изменять один раз написанцую программу для запуска приложения на той или иной аппаратной платформе. Так же как меню и диалоги, битовые карты можно объявлять как статические ресурсы. Наименьшей битовой картой, которую можно использовать в шаблоне для кисти, является карта 8 на 8. Хотя встроенные классы Актора не включают в себя большого количества прямых средств управления битовыми кар¬ тами, из Актора доступны все функции Windows для работы с ними. Некоторые из этих функций приведены в табл. 7.1. В среде Актора общий размер экрана хранится в системе, и для того, чтобы узнать его, вам придется послать следующее системное сообщение screenSize: screenSize(); Если у вас монитор VGA, сообщение вернет следующее значение: 6400480 В действительности это координатный адрес самой дальней точки в нижнем углу в системе координат экрана. Помимо системы координат экрана имеется и еще одна система координат, связанная с окном. Она называется системой коорди¬ нат клиента. Сначала, перед тем, как что-нибудь рисовать в окне, вы должны взять контекст дисплея. После того как рисование закончено, вы должны вернуть, освободить этот контекст. Эти операции выпол¬ няются при помощи сообщений getContext и releaseContext. Если мы нарисовали некоторый графический объект в обычном окне, этого еще недостаточно для того, чтобы рисунок сохранялся при изменяющихся условиях. Обычно это бывает связано с перекры¬ тием изображения другим окном, изменением размеров окна и другими модификациями, в результате которых перерисовка изо¬ бражения не выполняется. Чтобы быть уверенным, что изображе¬ ние перерисовывается, следует рисовать его в окнах, обладающих методом paint, они производят перерисовку автоматически. Таблица 7.1. Функции Windows для работы с битовыми картами CreateDIBitmap LoadBitmap GlobalUnlock BitBlt PatBlt PolyLine
Трехмерная точка 227 ■ ТРЕХМЕРНАЯ ТОЧКА Из всего изложенного очевидно, что встроенные графические средства, которые мы успели обсудить, работают только с двумя измерениями. Отметим, что они не только используются для рисования на двумерном дисплее — практически вся компьютер¬ ная графика связана с этим ограничением, — описанные нами объекты сами по себе являются двумерными. В этом разделе предлагаю простое упражнение в графическом объектно-ориенти- рованном программировании (листинг 1). Оно состоит исключи¬ тельно в разработке нового класса для представления точек, класса, который описывает точку в трехмерном пространстве. Листинг 1. Трехмерные точки I ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ j /* The Point3-D Class */ /* */ j ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ j Def (z(self) { ^z } Def setZ(self, zVal) { ^z := zVal); } Def set(self, xVal, yVal, zVal) { x := xVal; у := yVal; z := zVal; } Def round(self) { ^point(asInt( if x < 0 then x - 0.5 else x + 0.5 endif), asInt( if у < 0 then у - 0.5 else у + 0.5 endif), asInt( if z < 0 then z - 0.5 else z + 0.5 endif)); } Def asStruct(self | struct) { struct := new(Struct, 6); putWord(struct, x, 0); putWord(struct, у, 2); putWord(struct, z, 4); Astruct; }
Объектно-ориентированное графическое программированиедля Windows Def +(self, arg) { ^paint(x(arg) + x, y(arg) + у, z(arg) + z); } Def - (self, arg) { ^paint(x(arg) - x, y(arg) - у, z(arg) - z); } Def =(self, a3DPt) { ^class(a3DPt) == class(self) cand x = x(a3DPt) cand у = y(a3DPt) cand z = z(a3DPt); } Def printOn(self, aStream) { printOn(x, aStream); nextPutAll(aStream, "0"); printOn(y, aStream); nextPutAll(aStream, "@"); printOn(z, aStream); } DEf point3D(self, yVal, zVal a3DPoint) { a3DPoint := new(Point3D); set(a3DPoint, self, yVal, zVal); ^a3DPoint } 3D1 := point3D(30, 40, 50); 30040050 Метод moveTo класса Point изменяет текущее положение окна на то, которое указано. При этом используется специфицированный дескриптор контекста устройства. Метод lineTo рисует прямую от текущей позиции дисплея, используя дескриптор, специфициру¬ ющий его контекст, до позиции, соответствующей объекту self, но не включая ее. Затем он переустанавливает текущую позицию окна, принимая в этом качестве положение получателя. Метод d*aw для рисования точки просто вызывает функцию Windows Rectangle с аргументами, требующимися для рисования ’’безраз¬ мерного” прямоугольника. Def draw(self, hdc) { Call Rectangle(hdc, x, у, x, у) ; } ■ ДИАГРАММЫ Поскольку для различных целей возможно существование огром¬ ного многообразия графических приложений, в традиционном программировании типичной картиной является разработка каж¬ дой новой графической программы с нуля. Только с появлением объектно-ориентированных систем вполне реально встал вопрос, каким образом на некотором общем фундаменте может разрабаты¬ ваться широкий спектр графических приложений. В данном раз¬ деле я продемонстрирую несколько практических примеров
Диаграммы 229 построения простых столбчатых и секторных диаграмм, базирую¬ щихся на небольшом числе классов Актора, которые легко расши¬ рить. На основании этих примеров мы сделаем ряд выводов о том, как можно построить графические системы более общего типа. Поставяемые классы Актора, поддерживающие рисование диаг¬ рамм, приведены в следующей ветви иерархии классов: Object Chart HBarChart PieChart VBarChart W indowsObject Window BCWindow Класс BCWindow является специализацией класса Window, кото¬ рая имеет экземплярную переменную chart для запоминания данных диаграммы и особый метод paint, который использует эти данные при перерисовке диаграммы в окне. Если классы BCWindow и HBarChart загружены в систему, то следующие Рис. 7.3. Горизонтальная столбчатая диаграмма
230 Объектно-ориентированное графическое программирование для Windows сообщения создают горизонтальную столбчатую диаграмму, пока¬ занную на puc. 7.3. Н := new(HBarChart); setData(H, #(7, 15, 18, 22)); setLabels(H, #("Jan", "Feb", "Mar", "April")); setArea(H, 1006100); С := defaultNew(DCWindow, "Chart"); context := getContext(C); draw(H, context); releaseContext(C, context); setChart (С, H); show(С, 1); Практически так же можно построить класс PieChart для создания изображений секторных диаграмм: Pie := new(PieChart); setData(Pie, #(7, 15, 18, 22)); setArea(Pie, 1006100); adjustScale(Pie, 80); С ;= defaultNew(DCWindow, "Chart"); context := getContext(C); drawData(Pie, context); releaseContext (С, context); setChart (С, Pie); show(С, 1); Результат работы этого фрагмента показан на puc. 7.4. Рис. 7.4. Вокнесекторнаядиаграмма
Анимация 231 ■ АНИМАЦИЯ Анимация (реализация движения изображения) поставила особые проблемы перед средой Windows, поскольку здесь требуется муль- тизадачность. Если Windows будет иметь возможность прервать вашу процедуру анимации даже на небольшой отрезок времени, вы никогда не сможете контролировать процесс анимации. Windows получает управление, когда приложение запрашивает следующее сообщение. Только в этой точке может включаться мультизадачность и происходить передача сообщений другому приложению. Обычно это происходит, когда очередь сообщений к исходному приложению пуста. Единственный способ сохранить управление в ходе анимации — это гарантировать, что очередь сообщений никогда не будет пустой. Для того чтобы избежать мерцания во время анимации, необходимо добиться, чтобы ни в какой момент времени изображение на экране не пропадало и не изменялось слишком резко. Один из методов анимации в Windows состоит в том, чтобы образовать дочернее окно в точности по размеру изображения и затем передвигать это окно, а не многократно рисовать и перери¬ совывать изображение большего окна. Другой метод состоит в создании объекта анимации в виде пиктограммы и пользовании функцией Windows DrawIcon, а не MoveWindow. Если вы заранее знаете направление движения, то иногда можно создавать битовые карты с незаполненными участками, тогда при перерисовке достаточно затереть предшествующее изображение. Это избавляет от необходимости специальной процедуры для сти¬ рания изображения. Один из способов контролировать время при анимации состоит в использовании сообщения WM_TEVIER. Следующие демонстраци¬ онные программы анимации в Акторе используют ’’оживляющее” (’aHve’) сообщение для обеспечения мультизадачности. Def flipActor(self | idx, array, count) { idx := -1; array := #(0 1 2 3 2 1); count := (size(array) * 4) + 1); loop alive(self) cand checkMessage(); while alive(self) cand (count := count - 1) = 0 begin doDraw(self, bitmaps[array[idx + 1] mod size(array)]], { using(bitmapArray, hDC, hMemDC) Call BitBlt(hDC, bitmapArray[l], bitmapArray[2], bitmapArray[3], bitmapArray[4], hMemDC, 0, 0, bitmapArray[5], ); }); delay(self, 30);
232 Объектно-ориентированное графическое программирование для Windows endLoop; } /* Нарисовать строки человечков */ Def flipPeople(self hDC, count, idx, rect) { rect := rect(50, 85, 400, 112); loop alive cand checkMessage(); while alive(self) idx := 2000; do(findNumber(self, loadString(idx)), { using(number jc, sz, str, which) if alive(self) then ос := new(OrderedCollection, number); do(number, { using(i) add(oc, loadString(idx ;= idx + 1));}); loop while alive(self) cand (sz := size(oc)) 0 begin hDC := getContext(self); Call SetTextColor(hDC, 0xFFFFFFL); Call SetBkMode (hDC, T^*ANSPARENT); wyich := random(sz); Call DrawText(hDC, asciiz(oc : Object[which]), -1, rect, 0x25 /* DT_CENTER, DT_VCENTER & DT_SINGLELINE */); releaseContext(self, hDC); remove(oc, which); delay(self, 1000); scrollRect(self, rect, point(#( -1 1 )[random(2)], #( -1 1 )[random(2)]), height(rect) ) ; endLoop; endif; }) ; endLoop; } ■ НЕКОТОРЫЕ ИТОГИ Одной из наиболее привлекательных сторон Актора является его интерактивная среда. Это означает, по крайней мере теоретически, что вы можете попробовать ввести любую программу и тут же получить ее результат, не используя традиционный цикл компи¬ ляции и редактирования внешних связей. Среди других досто¬ инств отметим, что программист избавляется от траты времени, борьбы с чужими ошибками и необходимости заниматься не своими проблемами. Одним из факторов, который может свести на нет все эти преимущества,является то, что Актору приходится иметь дело с компилируемыми ресурсами типа меню. По этой причине, хотя это требует чуть большего труда, я рекомендовал бы в процессе разработки приложения пользоваться только дина¬
Описания графических классов 233 мическими ресурсами. Потом, когда вы завершите разработку про¬ граммы, можно будет переделать эти ресурсы в компилируемые. ■ ОПИСАНИЯ ГРАФИЧЕСКИХ КЛАССОВ Turtle /* Черепашка */ Исходный файл: TURTLE.CLS Происходит из: Object Экземплярные переменные: position, heading, visibility, penDown, stepSize, kitchenFloor, kitchenFloorDC, turConstants Методы класса: Методы объектов: b Возвращает черепашку назад. Если перо было опущено, след будет стерт backup (то же самое) down Опускает перо для того, чтобы когда начнется движение, за черепашкой оставался след draw Метод для рисования панциря черепашки на экране erase Стирает черепашку на экране, перерисовывая свое изображение белым цветом f Передвигает черепашку на x шагов вперед face Ориентирует черепашку под определенным углом. 0 градусов соответствует направлению на север faceRad Задает для черепашки направление в радианах form Рисует изображение черепашки на экране forward Передвигает черепашку на x шагов вперед getWindowDC Берет контекст для kitchen floor окна goTo Перемещает черепашку в точку, специфицированную в nextPosition hide Скрывает (делает невидимой) черепашку home Устанавливает черепашку в центр экрана
234 Объектно-ориентированное графическое программирование для Windows init koch 1 left leftRad letGoWindowDC next peano peanoRecursel peanoRecurse2 r recurseKoch recurseSqKooh right rightRad setWindow show sqKoch starl star2 starry up Устанавливает размер шага, инициализирует окно для черепашки, создает коллекцию turConstants и устанавливает черепашку в центр экрана Рисует фрактальную кривую Коха порядка n Поворачивает черепашку налево (против часовой стрелки) на x градусов Поворачивает черепашку налево (против часовой стрелки) на x градусов Задает поворот черепашки налево в радианах Освобождает контекст дисплея для базисного серого (kitchen floor) окна Возвращает следующую точку, в которую должна прийти черепашка Рисует кривую Пеано указанного размера и порядка Обеспечивает рекурсию для рисования кривых Пеано Обеспечивает рекурсию для рисования кривых Пеано Поворачивает черепашку на x градусов направо (по часовой стрелке) Обеспечивает рекурсию для метода koch Обеспечивает рекурсию для метода sqKoch Поворачивает черепашку на x градусов направо (по часовой стрелке) Задает поворот черепашки направо в радианах Переустанавливает kitchen floor окно Делает черепашку видимой (рисует ее на экране) Рисует ”квадратную” фрактальную кривую Коха порядка n Рисует пятиконечную звезду без пересекающихся прямых Рисует с пересекающимися прямыми Рисует n звезд со случайными размерами и ориентацией Поднимает перо, после этого черепашка не будет оставлять след ShapesWindow (Формы окна) Исходный файл: SHAPESWI.CLS Наследует из: Window
Описания графических классов 235 Имеет наследников: TrackWindow Переменные класса: $NativeLiterals Переменные экземпляров: shapes currentShape blackBrush pen outline Методы класса: Методы объектов: command init paint setup Массив типов очертаний Текущая форма для рисования Темная кисть Текущий объект-перо Набор форм для контура прямоугольника Получает командное сообщение и проверяет выбранный айтем в меню после старого айтема меню, который не проверяется Устанавливает начальную форму и цвет кисти. Кроме того, добавляет about-альтернативу (комментарий) для данного приложения в системное меню Устанавливает цвет пера и кисти для очертаний, затем рисует их Устанавливает систему координат, которую будет использовать окно при рисовании. Кроме того, делает это так, что форма не будет изменяться при изменении размеров окна TrackWindow (Перетащить окно) Исходный файл: TRACKWIN.CLS Наследует из: ShapesWindow Имеет наследников: Переменные класса: $NativeLiterals Переменные экземпляров: dragDC pOrigin pOld Методы класса: Методы объектов: beginDrag drag Контекст дисплея для drag Начальная точка shape-окна Старая концейая точка shape-окна Направляет все сообщения от мыши в данное окно и устанавливает для заполненной кисти черный цвет Стирает нарисованный перед этим shape-прямоугольник и рисует новый
236 Объектно-ориентированное графическое программированиедля Windows endDrag (эта программа вызывается каждый раз, когда мышь передвигается к новой точке) Стирает shape-прямоугольник, освобождает контекст дисплея и посылает аннулирующее сообщение в окно (для того, чтобы вызвать перерисовку) DemosWindow Исходный файл: Наследует из: Переменные экземпляров: demos Методы объектов: addDemo DEMOSWIN. CLS TurtleArea command eol init initDemos recreate Добавляет демонстрационную программу в коллекцию demos Отвечает за командные сообщения Пересылает сообщения eol (end of line — прим. nepee.), посланные самому себе, объектам в OutPort Инициализирует DemosWindow Инициализирует списковое окно Demos с подходящими альтернативами Восстанавливающий метод для окна демонстрационных программ Chart (Диаграммы) Исходный файл: CHART.CLS Наследует из: Object Имеет наследников: HbarChart, PieChart, VBarChart Переменные класса: $NativeLiterals Переменные экземпляров: data labels scale area prarea lead space brushes Методы класса: Методы объектов: Коллекция данных Метки, ассоциированные с данными Указание масштаба Указание размеров области значений Указание размеров страницы Указание расположения начала диаграммы в окне Промежуток между айтемами Запоминает цветовую таблицу
Описания графических классов 237 addData addLabel checkError draw drawBox drawKey drawText getData getLabel init load prReScale save setArea setData setLabels size Добавляет элемент данных Добавляет метку Берет номер ошибки из файла и выдает подходящее окно с ошибкой на экран Рисует диаграмму в данном дисплейном контексте. Область перед рисованием должна быть уже установлена Принимает на входе прямоугольник и выдает заполненное окно, определяемое этим прямоугольником Принимает прямоугольник и список цветов на входе и выдает заполненное окно, определяемое этим прямоугольником. После того, как создается кисть и окно заполнено, новая кисть должна быть уничтожена и старая кисть опять должна быть сделана активной Пишет на экран текст в окнах, определенных в контексте устройств, в позиции x и у Берет данные из диаграммы Берет метку из диаграммы Инициализирует новый объект-диаграмму Считывает данные диаграммы из файла и передает их экземплярные переменные объекта-диаграммы данного ASCII-файла Изменяет масштаб диаграммы, опираясь на значения data и area Сохраняет данные диаграммы в ASCII-файле, имя которого указано в аргументе Устанавливает диапазон изменения данных в диаграмме. Должно быть сделано до начала рисования Устанавливает значения данных диаграммы Устанавливает метки диаграммы Возвращает размер данных HBarChart (Горизонтальные столбчатые диаграммы) Исходный файл: HBARCHAR.CLS Наследует из: Chart Методы объектов: adjustPrScale Регулирует масштаб на основании значений data и area
238 Объектно-ориентированное графическое программирование для Windows adjustScale drawData drawLabel getClkRgn resetLead resetPrLead resetPrSpace resetScale resetSpace Регулирует масштаб на основании значений data и area. Использует дробные масштабы, если они < 1 Рисует элементы данных, диаграммы в форме горизонтальной полоски. Для определения того, где должна начаться диаграмма по отношению к нижнему левому углу окна, используется значение экземплярной переменной lead. Экземплярная переменная scale регулирует размеры диаграммы при изменении размеров окна Рисует метку в позиции, соответствующей расположению полоски. Преобразует метку в строку, если это не было сделано раньше Берет пару координат и определяет, какая из полосок была отмечена при помощи нажатия кнопки (мыши) Переустанавливает значение lead для уточненных значений данной диаграммы Переустанавливает значение lead для уточненных значений данной диаграммы Переустанавливает величину промежутка между полосками с тем, чтобы промежутки были подходящими для данного типа диаграммы Переустанавливает значение scale для уточненных значений диаграммы данного типа Переустанавливает величину промежутка между полосками с тем, чтобы промежутки были подходящими для данного типа диаграммы VBarChart (Вертикальные столбчатые диаграммы) Исходный файл: VBARCHAR.CLS Наследует из: Chart Имеет наследников: Переменные класса: $NativeLiterals- Переменные экземпляров: Методы класса: Методы объектов: adjustPrScale Регулирует масштаб на основании значений data и area
Описания графических классов 239 adjustScale drawData drawLabel getClkRgn resetLead resetPrLead resetPrSpace resetScale resetSpace Регулирует масштаб на основании значений data и area. Использует дробные масштабы, если они < 1 Рисует элементы данных, диаграммы в форме вертикальной полоски. Для определения того, где должна начаться диаграмма по отношению к верхнему левому углу окна, используется значение экземплярной переменной lead. Экземплярная переменная scale регулирует размеры диаграммы при изменении размеров окна Рисует метку в позиции, соответствующей расположению полоски. Преобразует метку в строку, если это не было сделано раньше Берет пару координат и определяет, какая из полосок была отмечена при помощи нажатия кнопки (мыши) Переустанавливает значение lead для уточненных значений данной диаграммы Переустанавливает значение lead для уточненных значений данной диаграммы Переустанавливает величину промежутка между полосками с тем, чтобы промежутки были подходящими для данного типа диаграммы Переустанавливает значение scale для уточненных значений диаграммы данного типа Переустанавливает величину промежутка между полосками с тем, чтобы промежутки были подходящими для данного типа диаграммы PieChart (Секторные диаграммы) Исходный файл: PIECHAR.CLS Наследует из: Chart Имеет наследников: Переменные класса: $NativeLiterals Переменные экземпляров: Методы класса: Методы объектов:
240 Объектно-ориентированное графическое программирование для Windows adjustPrScale adjustScale drawData drawLabel getClkRgn resetLead resetPrLead resetPrSpace resetScale resetSpace Регулирует масштаб картинки. Он выбирается с таким расчетом, чтобы по вертикали занимал 80% размеров окна Регулирует масштаб картинки. Он выбирается с таким расчетом, чтобы по вертикали занимал 80% размеров окна Рисует секторную диаграмму, масштабируя с учетом параметров диаграммы и размеров окна. На основании значения экземплярной переменной lead устанавливается расстояние от диаграммы до верхнего левого угла окна. Масштаб изменяет размер диаграммы в соответствии с высотой окна Рисует окошко для клавиши и текстовую метку для каждого из цветов секторов круга. На всякий случай преобразует метку в текст Берет пару координат и определяет, какой из секторов был отмечен при помощи нажатия кнопки (мыши) Устанавливает расстояние от верхнего левого угла до точки начала секторной диаграммы Устанавливает расстояние от верхнего левого угла до точки начала секторной диаграммы Переустанавливает расстояние между окошками для клавиш с тем, чтобы промежутки были подходящими для данной секторной диаграммы Переустанавливает значение scale для уточненных значений диаграммы данного типа Переустанавливает расстояние между окошками для клавиш на основании величины area. Использует некоторое умолчание, если значение area еще не установлено
Глава 8 Я ПРОГРАММИРОВАНИЕ ПРИ ПОМОЩИ OBJECTGRAPHICS Хотя Windows 3.0 дает основательный фундамент для создания графических приложений, постройка всех остальных частей про¬ граммного здания при помощи базисных средств все равно остается за прикладным программистом. Это означает, что сохраняется ог¬ ромная потребность в инструментах для программирования графики в среде Windows. По мере роста популярности объектно-ориентиро- ванных инструментов поддержка графического программирования в Windows будет одним из самых главных желаний разработчиков. Однако до сих пор именно в область графики были направлены основные усилия серьезных разработчиков. Средства ObjectGraphics группы Whitewater также служат удовлетворению этих требований. Использование ObjectGraphics в Windows 3.0 существенно упрощает работу с графикой, теперь программист уже не представляется первопроходцем, вступившим на нетронутую территорию, не имея ни карты, ни компаса. Библиотека объектно-ориентированной графики демонстрирует настолько эффектно, насколько это возможно, каковы преимуще¬ ства и потенциал среды программирования Актор для пользовате¬ лей Windows 3.0. ObjectGraphics спроектирован в расчете на три связанные между собой типа поддержки: инструменты для фор¬ мирования изображения (rendering rools), фильтры платформ и графические объекты. Как убедительно показывает демонстраци¬ онная программа ObjectDraw, библиотека поддержки в основном нацелена на приложения, связанные с объектно-ориентированным рисованием и CAD (Computer Aided Design — проектирование при помощи компьютеров). ObjectDraw весьма распространенная про¬ грамма. Многие пользователи несомненно оценят ее значение во множестве профессиональных проектов. И поскольку все исход¬ ные тексты поставляются, программисты будут склоняться ис¬ пользовать ее как пусковую установку для своих еще более
242 Программирование при помощи ObjectGraphics изощренных графических приложений. И в заключение мы рас¬ смотрим, как строятся программы, подобные этой, в ObjectGraphics, и обсудим некоторые улучшения, которые в нее можно внести. ■ ОБЗОР OBJECTGRAPHICS Перед тем как перейти к реализации ObjectGraphics, рассмотрим некоторые возможности библиотеки объектно-ориентированной графики подобного типа. Как я уже упоминал, помимо инструмен¬ тов для работы с графическими объектами как таковыми ObjectGraphics предоставляет объектно-ориентированные инстру¬ менты для формирования изображения и фильтры для платформ. Основные приложения, использующие данную библиотеку, ори¬ ентированы на двумерное рисование, проектирование и CAD-про¬ граммы. Инструменты для формирования изображения ObjectGraphics включают в себя средства типапера, кисти, тексто¬ вого пера, графических пространств. И, конечно, ObjectGraphics поддерживает шесть стилей линий, предоставляемых Windows 3.0. Замысел ObjectGraphics основан на идее графического пространст¬ ва, свойства которого можно отобразить на реальный дисплей. Одной из ключевых возможностей здесь служит возможность группировать объекты в более крупные объединения, а затем, если необходимо, "разгруппировывать” их. Помимо этого ObjectGraphics предоставляет объекты Picture (рисунки), которые представляют собой коллекции графических объектов. На базе таких объектов определен протокол для общей рассылки сообще¬ ний для формирования изображений с тем, чтобы унифицировать ассортимент рисунков и текстов. Кроме того, предоставляются специальные классы для временных инструментов типа горизон¬ тальных и вертикальных масштабных линеек (rules), полимарке¬ ров и окон-палитр. В таблицах, приведенных в данной главе, приводится информа¬ ция, которую вы можете не найти в других руководствах по ObjectGraphics. ■ ДЕМОНСТРАЦИОННЫЙ ПРИМЕР SAMPLEDRAW Простейший способ оценить мощность ObjectGraphics — это рас¬ смотреть демонстрационную программу SampleDraw. Класс SampleDraw является потомком класса Window. Он имеет три новых экземплярных переменных и семь методов, которые реали¬ зуют простую программу для рисования линий. SampleDraw по¬ зволяет строить прямые при помощи мыши и производить выбор в меню WidthI (ширина) и Color! (цвет) для того, чтобы задавать, как должна выглядеть та или иная линия. SampleDraw использует
Добавление меню и новых форм в SampleDraw 243 три экземплярные переменные: theLine, width и rgbBox. Как вы и могли ожидать, theLine используется для того, чтобы запоми¬ нать новый объект-линию, пока она создается при перемещении мыши. Переменная rgbBox используется для хранения экземпля¬ ра класса RGBDialog, который, и вы не должны удивляться услышав это, является диалогом, применяемым для выбора цвета путем определения интенсивности основных тонов красного (red), зеленого (green) и синего ф1ие). Переменная width просто содер¬ жит целое число, которое является текущим значением ширины линии. Чтобы вызвать SampleDraw, достаточно воспользоваться следующими сообщениями: S1 := defaultNew(SampleDraw, "Window for Drawing Lines"); show(S1, 1); SampleDraw включает в себя следующие методы: init, command, gPaint, drag, beginDrag, endDrag и getWidth. Эти методы весьма интересны, поскольку те или иные их вариации потребуются почти во всех приложениях, которые пишутся при помощи ObjectGraphics. Метод init инициализирует все экземплярные пе¬ ременные и объекты, которые использует SampleDraw. Сюда отно¬ сятся переменные port и picture, а также диалогы и меню. Тройка методов для работы с мышью отражает схему внутренних сообще¬ ний Актора, которые инициируются, когда пользователь вводит информацию посредством мыши. Сообщение beginDrag соответст¬ вует WM_LBUTTONDOWN. Сообщение drag соответствует WM_MOUSEMOVE, и endDrag — WM_LBUTTONUP. Метод command содержит два оператора case. Оба они открывают диало¬ ги, которые получают сведения о цвете и типе линий. Метод gPaint дает уверенность, что любая линия, которая была нарисована ранее, будет перерисована, когда окно SampleDraw получит сооб¬ щение WM_PAINT. Как упоминалось ранее, в ObjectGraphics имеется четыре основных инструмента для формирования изображения: перья, кисти, тек¬ стовые перья и графические пространства. ■ ДОБАВЛЕНИЕ МЕНЮ И НОВЫХ ФОРМ В SAMPLEDRAW Каков простой способ расширить программу типа SampleDraw, чтобы она была еще более полезной? В порядке эксперимента в добавлении новых функциональных возможностей в эту програм¬ му начнем с того, что включим систему динамического меню. После этого можно будет поэкспериментировать с добавлением различных функций и опций меню для их активации. И, наконец, придется написать новый метод init, который управляет инициа¬ лизацией системы динамического меню. Наилучший способ про¬
244 Программирование при помощи ObjectGraphics двинуться в этом направлении — создать подкласс SampleDraw, который назовем NewDraw. Ниже приводится пример метода init для такого класса NewDraw. Def init(sel) { port := new(Port); picture := new(Picture); RGBox := new(RGBDialog); setCursor(self, #graphics); width := 0; createMenu(self); changeMenu(self, 0, asciiz("&Shape!"), 100, MF_APPEND); changeMenu(self, 0, asciiz("&Width!"), 101, MF_APPEND); changeMenu(self, 0, asciiz("&Color!"), 102, MF_APPEND); changeMenu(self, 0, asciiz("&Dimensions!"), 103, MF_APPEND); drawMenu(self); } К имевшимся исходным меню Width! и Color! добавлены меню Shape! и Dimensions! Теперь вам предстоит решать, какие опции для определения форм и размерности надо задать в качестве позиций меню и как они будут реализовываться. Чтобы ознако¬ миться с одним из подходов к решению такой задачи, можно изучить демонстрационную программу Track!, которая написана на Акторе с использованием встроенных графических функций. Если хотите, можете исследовать ряд интересных возможностей, которые содержат в себе средства ObjectGraphics. Для того чтобы создать новые меню для работы с программой рисования, необходим новый метод command. Его можно напи¬ сать следующим образом: Def command(self, wP, lP) { select case wP == 100 getShape(self); endCase case wP == 101 getWidth(self); endCase case wP == 102 runModal(RGBox, RGBOX, self); endCase case wP == 103 runModal(DIMBox, DIMBOX, self); endSelect; } В этом эскизе метода command предполагается, что вы хотите использовать класс DimensionDlg из ObjectGraphics для реализа¬ ции диалога с новым меню Dimensions! Для вас это будет своего рода небольшое и интересное исследование. Вы относительно быстро получите некоторые результаты, увидите, как реализуются
ObjectDraw 245 ваши идеи. Такая оперативная обратная связь становится возмож¬ ной благодаря организации относительно простой исследователь¬ ской среды, в результате чего повышается скорость процесса обучения. ■ OBJECTDRAW ObjectDraw — существенно более сложная программа по сравне¬ нию с SampleDraw. Она демонстрирует применение почти всех расширений библиотеки ObjectGraphics. Хотя полное понимание реализации этой программы потребует хорошего знания принци¬ пов и выводов, приведенных в последующих разделах данной главы, получение четкого представления об общей схеме програм¬ мы — это один из лучших способов охватить всю библиотеку ObjectGraphics в целом. Главное окно ObjectDraw подобно большинству программ для рисования и закрашивания имеет полосу верхнего меню и полосу палитры или инструментальную планку (toolbox strip) сбоку. Графические инструменты предоставляют следующие возможно¬ сти: выбор, распахивание (zoom), прямая, прямоугольник, прямо¬ угольник с закругленными краями, эллипс, многоугольник, ломаная линия, кривая, пиктограмма, редактор битовой карты (bitmap cropper) и текст. Меню F.ile в ObjectDraw поддерживает сохранение, загрузку и печать рисунков. Кроме того, имеется обеспечение, позволяющее открывать Windows в формате битовых карт версии 2.0. Самая уникальная часть этой программы представлена меню ArrangeMenu. Оно позволяет спланировать расположение объек¬ тов в соответствии с особым порядком. Команды Bring to Front (переместить вперед) и Send to Back (отослать назад) задают позицию выбора графических объектов, используя дисциплину стека. Графические объекты, перемещаемые на передний план, как и следовало ожидать, будут последовательно перекрывать объекты, лежащие за ними. Команда Group собирает отобранные объекты в рисунки таким образом, что после этого ими можно манипулировать как единым целым. Команда Ungroup разделяет все отобранные рисунки на отдельные объекты. И наконец, Align to Grid выравнивает источники всех отобранных объектов в соот¬ ветствии с текущей активной сеткой. Все это должно дать вам представлениеобуникальной мощности программы. Теперь я по¬ пытаюсь дать небольшой обзор организации ObjectDraw. С точки зрения понимания программы ObjectDraw, наиболее су¬ щественными, ключевыми являются два класса: Draw и ObjectDraw. Оба они являются потомками Window и находятся на одном уровне. ObjectDraw предоставляет родительское окно, а
246 Программирование при помощи ObjectGraphics Draw — большое дочернее окно, в котором происходит рисование. Интересной особенностью ObjectDraw является то, что каждое из четырех дочерних окон реагирует на сообщения paint по-разному. Класс ObjectDraw управляет инициализацией всех дочерних окон, включая большой объект Draw, который формирует главную об¬ ласть графического рисования и отвечает за управление и моди¬ фикацию всех инструментов формирования изображения. ObjectDraw предоставляет базис для создания сложных объектов, составленных из владельца объекта ObjectDraw и различных до¬ черних объектов-окон, таких как окно Draw, toolbox (набор инст¬ рументов), и вертикальных и горизонтальных масштабных линеек. Класс Draw реализует окна, обеспечивающие интерактивное рисо¬ вание и средства восстановления изображений, необходимые для ObjectDraw. Оно обеспечивается возможностью распахнуть окно (на весь экран) средствами горизонтального и вертикального скроллирования, копирования, замазывания при помощи работы с системным буфером (clipboard) и интерактивного выравнивания любых выбранных графических объектов вдоль некоторой сторо¬ ны, в цеитре, у центральной горизонтали и в форме сетки. Кроме того, окно Draw предоставляет графическое пространство, которое модифицируется интерактивно. Каждый раз, когда приходит сообщение gPaint, объект Draw совершает следующие пять действий: 1. Рисует окошко вблизи мирового прямоугольника графиче¬ ского пространства данного окна для того, чтобы отметить страницу. 2. Рисует сетку графического пространства, если эта опция задана. 3. Рисует целиком графический образ, представленный в дан¬ ном окне и хранящийся как коллекция графических объ¬ ектов экземплярной переменной picture. 4. Рисует маркеры (marking handles) всех графических объек¬ тов, отмеченных в настоящий момент. 5. Переустанавливает маркеры полосок скроллирования с тем, чтобы иметь возможность показывать любые, даже очень большие объекты. Окно-объект Draw может создавать графические объекты различ¬ ных типов, используя имя класса, хранящееся в экземплярной переменной ObjectDraw, в зависимости от того, какой инструмент выбрал пользователь в палитре инструментов. Выбранный графи¬ ческий объект всегда запоминается в экземплярной переменной Draw под названием TheChosen.
ObjectDraw 247 Метод toolBoox в ObjectDraw записывается так: Def toolBox(self) { Atool; } Он просто возвращает значение экземплярной переменной tool. Метод toolBox используется методом beginDraw из Draw. Метод beginDraw содержит четыре оператора case. В случае, когда ни один из case-onepaTopoB не сработает, нужно выполнить следую¬ щую инструкцию: theGraphic := build(toolBox(parent), aPaint, aPaint); Это сообщение предоставляет класс графического объекта, кото¬ рый должен рисоваться как аргумент для конструируемого метода. Отличный пример анимации при помощи ObjectGraphics представ¬ ляет собой метод spinDividers из класса AboutObjGfx. Def spin(Dividers(self index2) { associate(port, self); loop alive(self) cand checkMessage(); while alive(self) begin fastDraw(centerAt(frames[index], 98@153), port); index2 := if (index 5) index - 6 else index + 6 endif; fastDraw(centerAt(frames[index2], 1906153], port); if (index := index + 1) > 11 index := 0; endif; delay(self, 5); endLoop; dissaciate(port); } /* Сообщение для перерисовывания нового стиля. Реагируют перерисовкой: 1. Окошко вокруг ”мира”, чтобы все выглядело как страница. 2. Если надо, то сетка графического пространства. 3. Объект Picture. 4. Если надо, маркеры объектов (marking handles). */ Def gPaint(self, aPort, badRect page) { page := asRextangle(worldRect(space)); setPattern(brush(page), #invisible); setPen(page, normal(Pen, primary(Color, #block))); setCombo(pen(page), #nxor); draw(page, aPort); /* setColor(pen(page), primary(Color, #blue)); setStyle(pen(page), #dot);
248 Программирование при помощи ObjectGraphics inflate(page, */ if isMenuCheckd(parent, DW_GRID) drawGrid(aPort)? endif; draw(picture, aPort); draw(theMarks, aPor t); Call SetScrollPos (hWnd, SB_HORZ, left (mappmgRect (space)) , 1) ; Call SetScrollPos (hWnd, SB_VERT, top(mappingRect(space)) , 1) ; Если у вас остались некоторые неясные места в данной главе, то этого и следовало ожидать. Некоторые из концепций , на которых базируется ObjectGraphics, мы еще не объясняли. Их описания будут даны в последующих разделах. Поэтому было бы неплохо вернуться к этому разделу и перечитать его после того, как прочтете данную главу до конца. После этого некоторые вещи, которые были не ясны с первого раза, приобретут гораздо больше смысла. ■ РАСШИРЕННЫЙ АКТОР Перед тем как двигаться дальше, я должен заметить, что ObjectGraphics вносит несколько важных изменений в систему Актор. При загрузке классов поддержки в классах Актора Window и Printer выполняются существенные модификации, что, как можно догадаться, влияет на имеющиеся программы. В Window добавляются четыре новых экземплярных переменных: port, space, picture и cursor. Это важные элементы данных, которые используются при создании окон, поддерживающих протоколы ObjectGraphics. Одним из важнейших отличий является то, что когда переменцая port некоторого окна инициализирована путем присваивания ей некоторого порта вместо paint, автоматически для модификации окна становится действующим сообщение gPaint. Наиболее существенной особенностью, которая обеспечи¬ вает новые возможности библиотеки классов ObjectGraphics, яв¬ ляется присваивание графического пространства и порта окну. Присваивание объектов экземплярной переменной picture из Window позволяет обеспечить их автоматическую перерисовку, но это не так существенно, как присваивание окну порта и графиче¬ ского пространства. ObjectGraphics добавляет более тридцати нозых методов объекта в класс Window. Это могло бы быть основанием для создания нового отдельного класса графических окон, но у группы Whitewater вполне определенные намерения сделать так, чтобы для всех окон были доступны средства ObjectGraphics. Аннотированный список
Фильтры платформ 249 новых методов класса Window приведен в справочном разделе в конце главы. Как мы уже заметили, одним из наиболее важных новых методов является gPaint. Для целей графики он заменяет старый метод paint класса Window. ■ ФИЛЬТРЫ ПЛАТФОРМ Фильтры платформ представляют собой слой программ, который обеспечивает аппаратно-независимую работу графики в специфи¬ ческих аппаратных средах. Главными классами фильтров в ObjectGraphics являются: Port, PhysicalPort, Bitmap и Color. Весь графический вывод в ObjectGraphics как на экран, так и в виде твердой копии выполняется через классы Port и PhysicalPort. Любой объект Port имеет графическое пространство, связанное с ним, и все рисование в данном объекте выполняется по отношению к этому графическому пространству. Port — это абстрактный класс, который является аппаратно-независимым. Реальный ин¬ терфейс с аппаратным дисплеем осуществляется потомком класса Port, называемым PhysicalPort. Эти классы реализованы таким образом, что объектно-ориентиро- ванный подход позволяет упрятать аппаратную зависимость еще дальше, чем это делает сама система Windows. Хотя с точки зрения программиста, текст программы выглядит так, как будто иници¬ ализируется класс Port, реально создается лишь экземпляр класса Таблица 8.1. Экземплярные переменные класса Port theDisplay Текущая связь thePen Текущее перо theBrush Текущая кисть theTPen Текущий шрифт theSpace Текущее графическое пространство invRect Текущий прямоугольник изменений invRegion Текущая область изменений palette Цветовая палитра (ОС) Таблица 8.2. Экземплярные переменные класса PhysicalPort hDC Дескриптор Window, Printer или Bitmap, с которыми связан данный Port hBrush Дескриптор текущей кисти hPalette Дескриптор текущей палитры stockPen Стандартное перо платформы stockBrush Стандартная кисть платформы stockFont Стандартный шрифт платформы
250 Программирование при помощи ObjectGraphics PhysicalPort. Таким образом, специфические аппаратные аспекты PhysicalPort никак не попадают в программы приложений. ■ КЛАСС COLOR Класс Color в ObjectGraphics имеет четыре экземплярные перемен¬ ные: red, green, blue (красный, зеленый, синий) и physical. Кроме того, он использует переменную класса $Primaries, которая в действительности представляет собой символьный цветовой сло¬ варь с символьными ключами для основных цветов. Помимо этого класс Color имеет интересный набор методов класса: addSymColor, primary, read и RGB. Подход к манипулированию с цветом в ObjectGraphics сочетается с основными цветами RGB (red, green, blue) и основными цветами CMY (cyan, magenta, yellow), так же, как и с черно-белыми вариантами, которые используются в боль¬ шинстве систем, базирующихся на MS-DOS. Однако цветовая система ObjectGraphics при этом еще и расширяема в том смысле, что вы вправе добавить новые символы и значения их основых цветовых компонентов в символьный цветовой словарь. Вы можете определить новый цветовой символ при помощи метода класса RGB и использовать метод класса addSymColor для того, чтобы корректно добавить его в цветовой словарь. ■ БИТОВЫЕ КАРТЫ Средства для непосредственной работы с битовыми картами в объектно-ориентированной манере предоставляются классами: LogBitmap, Bitmap и HugeBitmap. Для того чтобы бегло обрисо¬ вать содержание этих классов, можно рассмотреть их экземпляр¬ ные переменные. Естественно, что сообщения этих классов также важны, но здесь мы ограничимся лишь кратким описанием. Сообщение asBitmap позволяет конвертировать любой графиче¬ ский объект в соответствующий Bitmap объект — битовую карту. Объекты классов Bitmap и HugeBitmap — это лишь объекты из библиотеки классов ObjectGraphics. В пределах библиотеки клас¬ сов ObjectGraphics только объекты классов Bitmap и HugeBitmap представляют одновременно визуализируемые изображения и ус¬ тройства вывода, при помощи которых происходит рисование. ■ ПРЯМОУГОЛЬНИКИ Роль прямоугольников как в Windows, так и в ObjectGraphics велика, и поэтому совсем не удивляет то, что с этими объектами работают два новых класса. Класс Rectangle заменяет класс Rect, который ранее использовался в графике Актора. Класс MathRect принадлежит к расширению ObjectGraphics, его появление связа¬ но с одной новой идеей. Новый класс понадобился для того, чтобы
Формы 251 Таблица 8.3. Значения цветовых составляющих в RGB Символ Красный Зеленый Синий #black 0 0 0 #white 255 255 255 #red 255 0 0 #green 0 255 0 #blue 0 0 255 #cyan 0 255 255 #magenta 255 0 255 #yellow 255 255 0 Таблица 8.4. Экземплярные переменные класса LogBitmap height Число пикселов по высоте width Число пикселов по ширине scanBytes Размер данных на строку сканирования (scanline) bitsPixel Размер данных на пиксел planes Число цветовых плоскостей clipRect Локальный буферный прямоугольник bits Биты изображения brush Кисть фона color #mono или #color theSpace Графическое пространство объекта Bitmap pen Перо переднего плана отразить математические аспекты понятия "прямоугольник”. Ме¬ тод asMathRect класса Rectangle предназначен для возвращения для каждого экземпляра Rectangle эквивалентного ему объекта MathRect. Прямоугольники — объекты одной и той же формы, и до некоторой степени это утверждение верно, но то обстоятельство, что различные объекты имеют различные аспекты рассмотре¬ ния — математические и изобразительные, несколько усложняет ситуацию. Несмотря на то, что работа в ObjectGraphics с прямо¬ угольниками и не самоочевидна, она и не особенно сложна, объекты Rectangle и их потомки являются потомками класса Shape (формы) и должны следовать протоколу, определенному в этом классе. Поэтому имеет смысл, перед тем как двигаться дальше, остановиться на вопросах работы с формами. ■ ФОРМЫ Класс Shape обеспечивает базис для всех графических объектов, которые изображаются последовательным вычерчиванием точек при помощи объекта Pen и затем заполнением объектом Brusl-
252 Программирование при помощи ObjectGraphics внутреннего пространства, ограниченного данной формой. Объек¬ ты Shape имеют две новых экземплярных переменных pen и brush и, кроме того, origin и corner, которые наследуются из класса Graphics. Для того чтобы сообщить отправителю, какой из мето¬ дов класса PhysicalPort используется для их рисования, потомки класса Shape применяют сообщение drawMethod. Например, при помощи своего сообщения drawMethod объект Rectangle возвраща¬ ет метод drawRect из класса PhysicalPort, используемый для вывода прямоугольников на экран. ■ ГРАФИЧЕСКИЕ ПРОСТРАНСТВА Графические пространства управляют следующими параметрами: направление осей, мировые размеры, система координат, единицы измерений, рамки ограничений для точек координатной сетки, масштабирование, пеннинг (панорамирование) и организация сет¬ ки. Пеннинг — это синоним скроллинга в контексте графики. Коротко говоря, графические пространства управляют выдавае¬ мым на дисплей изображением как в целом, так и в отдельных деталях. Объекты GraphSpace состоят из трех различных прямо¬ угольников, все они являются экземплярами класса MathRect. Это мировой прямоугольник, отображающий прямоугольник и прямо¬ угольник-дисплей. В отличие от объектов Rectangle MathRect-объ- екты нельзя нарисовать на экране. Они используются для представления прямоугольников в их реальных размерах, а не в тех, в которых они будут изображаться на дисплее. Хотя графи¬ ческое пространство определяет графический мир, этот мир в действительности не содержит определяемые в нем объекты. Объект GraphSpace создается путем определения мирового прямо¬ угольника. Исходная и краевая точки координат (origin и corner) мирового прямоугольника одновременно определяют и рамки и направление координат в этом пространстве. Если какой-либо ко¬ ординате даются отрицательные значения в мировом прямоугольнике, то это определяет мир, в котором координаты увеличиваются в соот¬ ветствующем направлении и уменьшаются в противоположном. Все графические объекты наследуют две экземплярные перемен¬ ные origin и corner из класса Graphic. Эти переменные использу¬ ются для хранения координат двух точек, которые определяют прямоугольник, ограничивающий графический объект. Этот огра¬ ничивающий прямоугольник используется как главная рабочая область для самых разнообразных манипуляций с графическими объектами. Для визуализации содержимого различных графических объектов особенно удобны Инспекторы. Ниже приводится несколько сооб¬ щений, которые создают и ’’инспектируют” графический объект:
Пиктограммы 253 Таблица 8.5. Экземплярные переменные класса GraphSpace wordRect Логический мировой прямоугольник mappingRect Логический оконный прямоугольник displayRect Физический оконный прямоугольник units Единицы измерения granularity Число тактов в единицу времени aspectLock Значение true соответствует изотропному отображению zoom Коэффициент распахивания в процентах grid х@у ячейки / сетка в точках g1 := build(Graphic, 20020, 100075); Graphic 02- 20020 inspect(g1); В целом для тех операций, которые доступны для любых графи¬ ческих объектов5предоставляется единообразный протокол, дис¬ циплина работы. Как и в других объектно-ориентированных системах, имена соответствующих сообщений одинаковы, даже если реализация для различных классов-потомков выполнена по- разному. Так, например, любой графический объект можно пока¬ зать на дисплее при помощи сообщений draw и fastDraw. Еще одним сообщением такого рода служит invert, оно выполняет инвертирование цвета у графических объектов. Это сообщение изменяет каждый из цветов объекта на его дополнительный. ■ ПИКТОГРАММЫ Как видно из иерархии классов, Icon (пиктограммы) — это объек¬ ты, происходящие из класса Graphic и наследующие весь прото¬ кол, при помощи которого можно манипулировать с графическими объектами. С пиктограммой вы можете делать все, что и с любым другим графическим объектом. Кроме того, как это ни кажется невероятным, пиктограммами можно управлять и манипулиро¬ вать при помощи ваших программ некоторым способом, который не зависит от аппаратной и программной платформ. Класс Icon разработан таким образом, что даже такими специфически аппа¬ ратными вещами,как пиктограммы,можно управлять в соответст¬ вии с некоторой общей запрограммированной схемой. Как и в случае других графических объектов, вы можете использовать сообщения build и inspect. Как вы могли уже заметить, объекты Icon имеют дополнительную экземплярную переменную, которая используется для хранени^ пиктограммного ресурсного скрипта ID. Конечно, ясно, что для того, чтобы обеспечить некоторую схему, которая будет переносима на различные платформы, тре¬ буются определенные усилия.
254 Программирование при помощи ObjectGraphics ■ ПОЛИФОРМЫ И ЛОМАНЫЕ ЛИНИИ Объекты PolyShape (полиформы) определяют свое визуальное про¬ явление в виде коллекции точек. PolyShape имеет две визуальных переменных: vertices, которая хранит OrderedCollection из объек¬ тов Point, и physical, которая хранит структуру данных. PolyShape — это абстрактный или формальный класс, используе¬ мый для того, чтобы обеспечить едикый базис для своих потомков: PolyLine (ломаная), Curve (кривая) и Polygon (многоугольник). PolyShape реализует лишь одну новую экземплярную переменную vertices. Кроме того, он берет на себя и все управление тем, что наследует из класса Shape. PolyLine (ломаная) является потомком PolyShape и может исполь¬ зоваться для создания более сложных и интересных фигур. Пред¬ ставленные ниже две группы сообщений демонстрируют типичный пример работы с объектами PolyLine. Два сообщения size демон¬ стрируют, как сказывается действие операций добавления точек. PL := new(PolyLine); setOrigin(PL, 20020); size(PL); 0 add(PL, 20020); aad(PL, 100020); add(PL, 20050); size(PL); 3 ■ КРИВЫЕ Класс Curve и его потомки предоставляют важное расширение GDI-машины MS-Windows, который сам по себе не вносит никакой особой поддержки для кривых. Класс Curve реализует кривые при помощи отображения в аппроксимирующую их ломаную, пред¬ ставленную объектом PolyLine. PolyLine и Curve оба представляют собой коллекции точек. У объектов PolyLine и Polygon коллекции точек используются для определения вершин графа, которые соединяются отрезками прямых. В классе же Curve эти коллекции точек используются как контрольные точки. Контрольные точки необязательно лежат на кривой, но служат как указательные или опорные точки для отдельных участков кривой. Экземплярная переменная approxPoly содержит Polygon-объект, который форми¬ рует линейную аппроксимацию кривой. Экземплярная перемен¬ ная step определяет размер отрезка прямой в такой аппроксимации. Помимо собственно Curve имеются еще и два TWi^MKa этого класса: Bezier и Cubic. Различные подклассы Curve огаслечивают различные способы, при помощи которых по конт¬ рольным точкам определяется форма кривой.
Рисование треугольников 255 ■ РИСОВАНИЕ ТРЕУГОЛЬНИКОВ Для такой богатой библиотеки, как ObjectGraphics, почти всегда имеется не один способ использования в той или иной работе. Обычно у каждого метода найдутся как преимущества, так и недостатки. Рассмотрим простой пример с рисованием треуголь¬ ника в некотором окне. Из предшествующих рассуждений вы, вероятно, поняли, что нарисовать треугольник при помощи класса PolyLine должно быть довольно просто. Ниже приводится несколь¬ ко сообщений, которые позволяют нарисовать прямоугольный треугольник: W1 := defaultNew(Window, "Drawing а Triangle: First Way"); P1 := new(Port); show(W1, 1); set(W1, P1); setPort(W1, P1); PL := new(PolyLine); setOrigin(PL, 50025); add(PL, 50025); add(PL, 300025); add(PL, 500200); add(PL, 50025); associate(port(W1), W1); draw(PL, port(W1)); dissociate(port(W1)); Для того чтобы реализовать более традиционный способ рисова¬ ния треугольников, мы могли создать отдельный класс Triangle как прямой потомок PolyLine с экземплярными переменными pointl, point2 и point3. С этой целью мы вероятно дополнили бы класс пятью следующими методами: /* устанавливает первую точку */ Def firstPoint(self, aPoint) { point1 := aPoint; } /* устанавливает вторую точку */ Def secondPoint(self, aPoint) { point2 := aPoint; } /* устанавливает третью точку */ Def thirdPoint(self, aPoint) { point3 := aPoint; } /* добавляет три точки в PolyLine и возвращается в исходную точку */ Def triangle(self) { add(self, point1); add(self, point2); add(self, point3);
256 Программирование при помощи ObjectGraphics add(self, point1); } /* определяет, какой метод будет использоваться для рисования */ Def drawMethod(self) { А #drawPolyLyne } Сообщения, которые при помощи класса Triangle рисуют тот же самый прямоугольный треугольник, имеют следующий вид: W1 := defauItNew(Wmdow, "Drawing а Triangle: Second Way"); P1 := new(Port); show(W1, 1); set(W1, P1); TR := new(Triangle); firstPoint(TR, 50020); secondPoint(TR, 300025); thirdPoint(PL, 500200); triangle(TR); associate(port(W1), W1); draw(TR, port(W1)); dissociate(port(W1)); Шестой метод для Triangle соберет все эти шаги в один, так, что треугольник можно будет описать одним сообщением: Def createTriangle(self, aPoint1, aPoint2, aPoint3) { pomt1 := aPoint1; point2 := aPoint2; pomt3 := aPoint3; triangle(self); } W1 := defaultNew(Window, "Drawing а Triangle: Third Way"); P1 := new(Port); show(W1, 1); set(W1, ?1); TR := new(Triangle); createTnangle (TR, 50025, 300025, 500200); associate(port(W1), W1); draw(TR, port(W1)); dissociate(port(W1)); При необходимости можно построить класс TriangleWindow с автоматическим диалогом, который, как только открывается ок¬ но, запрашивает точки для нового треугольника. Вся программа в этом случае была бы сосредоточена в методе init, тогда для создания окна потребовались бы только два сообщения: TW := defaultNew(TriangleWmdow, "Draw а Triangle") ; show(TW, 1); Такой же стратегией можно было бы воспользоваться для рисова¬ ния параллелограммов. Листинг, приведенный ниже, демонстри¬ рует простую программу, реализующую первый вариант определения класса параллелограммов.
Рисование треугольников 257 /*********************************/и /* Файл класса Parallelogram */!! /* Первая версия */!! /*********************************/Ц inherit(PolyLine, #Parallelogram, #(origin corner originAngle secondAngle pen brush side1 side2), 2, nil)!! now(class(Parallelogram))!! now(Parallelogram)I! /* задать точку начала (origin) */ Def setOrigin(self, aPoint) { add(self, aPoint); } ! I /* задать оставшиеся точки */ Def setPoints(self); { secondPoint(self) ; thirdPoint(self) ; fourthPoint(self) } ! I /* угол угол прц начальной точке */ Def originAngle(self) { origmAngle (self) } ! ! /* вернуть дополнительный угол */ Def secondAngle(self) { AsecondAngle }!! /* вернуть первую сторону */ Def side1(self) { Aside1 }!! /* вернуть вторую сторону */ Def side2(self) { Aside2 }!! /* задать точку для возвращения */ Def endPomt(self) { add(self, at(vertices(self), 0)); } ! ! /* задать четвертую точку */ Def fourthPomt(self) { add(self, point(x(at(vertices(self), 0)) - (side2(self) * cos(degToRad(secondAngle))), y(at(vertices(self), 2)))); 9-857
258 Программирование при помощи ObjectGraphics }П /* задать третью точку */ Def thirdPoint(self) { add(self, point(x(at(vertices(self), 1)) - (side2(self) * cos(degToRad(secondAngle)), (side2(self) * sin(degToRad(secondAngle)) + y(at(vertices(self), 1)))); }!! /* задать вторую точку */ Def secondPoint(self) { add(self, pomt(side1 + x(at(vertices(self), 0)), y(at(vertices(self), 0)) )); }!! /* задать углы */ Def setAngles(self, a1) { origmAngle := a1; secondAngle := 180 - a1; }!! /* задать стороны */ Def setSides(self, s1, s2) { side1 := s1; side2 := s2; }!! /* метод рисования для использования в классе PhysicalPort */ Def drawMethod(self) { А #drawPolyLme }!! Как вы увидите, если попытаетесь реализовать класс Parallelogram таким образом, он предлагает отнюдь не лучший способдля решения данной проблемы. Хотя в принципе способ правильный, но тригонометрические вычисления делают рисование слишком чувствительным к начальным параметрам этого метода. Дополни¬ тельной трудностью может стать требование определять паралле¬ лограмм лишь через две стороны и угол между ними. Для более точного и эффективного решения этой проблемы можно применять различные методы, например использовать тригонометрические таблицы. Читатель приглашается поэкспериментировать с мето¬ дами, которые, сохранив общий замысел, улучшат точность и скорость тригонометрических вычислений. ■ РИСУНКИ (PICTURES) Класс Picture расширяет поведение и протоколы графических объектов на случай коллекций таких объектов. Он делает это, вводя экземплярную переменную elements, которая хранит объек¬
Области (Region) 259 ты OrderedCollection. Эти графические коллекции являются мно¬ гоуровневыми, состоящими из вложенных гнезд, и ключевой проблемой разработки мощных интерактивных объектно-ориенти- рованных графических приложений является изучение методов обработки таких коллекций, позволяющих делать это искусно и эффективно. Версия сообщения asBitmap может преобразовывать целые коллекции графических объектов в объекты Bitmap. Если к объекту Picture посылается сообщение draw, то это сооб¬ щение распространяется (ретранслируется) далее на все графиче¬ ские элементы данной коллекции. pic1 := new(Picture); Picture0O 0@0 Объекты Picture придерживаются стандартного протокола из одиннадцати сообщений OrderedCollection, который включает в себя следующие сообщения: add, insert, put, at, find, first, last, remove, collect, do и extract. ■ ОБЛАСТИ (REGION) Объекты класса Region имеют широкий спектр использования, включая нацеленное (hit) тестирование, обрезание (clipping) и обратное движение. Метод boundsRegion класса Graphic, который создает и возвращает объекты Region, определяется просто: Def boundsRegion(self aRgn) { Таблица 8.6. Экземплярные переменные класса Pen width Ширина пера color Цвет объекта — Color style Стиль линии — Symbolic combo Комбинированный режим — Symbolic transparent Пустой/заполненный — булевская величина Таблица 8.7. Экземплярные переменные класса TextPen color Цвет текста — Color backColor Цвет фона — Color style Гарнитура шрифта — Symbol height Высота в мировых единицах font Шрифт — Symbol combo Комбинированный режим — Symbol transparent Прозрачность — Symbol dirty Булевский флаг, который указывает на то, что изменился некоторый атрибут, влияющий на размеры изображения
260 Программирование при помощи ObjectGraphics add(aRgn := new(Region), self); AaRgn; } К сожалению, объекты WinPolygon нельзя собирать в коллекции Picture, т.к. они не являются потомками класса Graphic. В объект Picture можно включать только потомков класса Graphic. Объекты RichText — это особый вид потомков Picture, которые ограничивают членство своей коллекции только объектами Label и объектами RichText. ■ ПАЛИТРЫ Класс Palette представляет собой общий механизм для построения панелей из пиктограммных кнопок, которые бывают на toolbox- панели, или цветовых палитр. Он использует две переменные класса: $Maps для запоминания имен пиктограммных кнопок и $IDs для хранения их ГО-номеров. $Мар := #("chooser", "zoomer", "line", "rect", "roundRect", "circle", "polygon", "polyline", "curve", "icon", "cropper", "text"); Метод beginDrag класса Palette иллюстрирует работу с экземпляр¬ ной переменной picture. Def beginDrag(self, wP, aPoint) { if selectTool(self, contains(picture, aPoint)) command(parent, $IDs[find(picture, selectedTool)], 0L); endif; } ■ ГРУППИРОВКИ Одной из наиболее широко используемых и мощных возможностей библиотеки ObjectGraphics являются средства поддержки интерак¬ тивной группировки и ”разгруппировки” графических объектов. Это выполняется при помощи методов groupChoice и unGroupChoice класса Draw. Метод groupChoice определяется так: Def groupChoice(self) { add(picture, theChosen); do(theChosen, { using(aGraphic) remove(picture, aGraphic); }); updateChoise(self); } С другой стороны, определение unGroupChoice таково: Def unGroupChoice(self) {
Группировки 261 do(theChosen, { using(aGraphic) if isAncestor(class(aGraphic, Picture) remove(picture, aGraphic); do(aGraphic, { using(element) add(picture, element); }? endif; }) ? updateChoise(self); } start(new(ObjectDraw)); Для того, чтобы дать пользователям возможность задавать основ¬ ные параметры рисования для графического пространства, в ObjectDraw используется объект DimensionDlg. Вы можете сме¬ нить единицы измерения, установить параметр для зернистости (granularity) и специфицировать ширину и высоту страницы в Таблица 8.8. Экземплярные переменные класса Draw theGraphic Объект перемещается thePoly Полиформа перемещается -PolyShape theChosen Выбранные графические объекты — Pic theMarks Маркер дескриптора — PolyMark startStop Состояние таймера editor Текстовый редактор для меток — Text clipRect Используется для сборки битовой карты fName Имя файла — String startPt Перемещение началось отсюда marker Используется для маркировки объектов при выборе — Graphic Таблица 8.9. Экземплярные переменные класса ObjectDraw display Объект Draw hRuler Горизонтальное окно с масштабной линейкой vRuler Вертикальное окно с масштабной линейкой toolBox Графическая палитра tool Текущий инструмент colorDlg RGB-диалог dimDlg Измерение Dlg thePen Перо по умолчанию theBrush Кисть по умолчанию theTPen Текстовое перо по умолчанию fileDlg Файловый открытый диалог
262 Программирование при помощи ObjectGraphics графическом пространстве. Метод sizeKids класса ObjectDraw ис¬ пользует такой оператор: setCRect(toolBox, rect(0, 0, 25, bottom(cRect))); setCRect — это метод, наследуемый из класса WindowsObject. Он задает размер прямоугольника окна. Аргумент cRect в сообщении bottom — это экземплярная переменная, которая также наследу¬ ется из WindowsObject. $Colors := static(%Dictionary( 810 -> #black 811 - #white 812 -> #red 813 - #green 814 ->#blue 815 - #cyan 816 ->#magenta 817 - #yellow)); $BrushStyles := static(%Dictionary( 761 ->#solid 762 - #invisiole 763 ->#ten 764 - #fifteen 765 ->#thirty 766 - #fifty 767 ->#seventy 768 - #e~gnty5 769 ->#vertical 770 - #horizontal 7 71 ->#diagonal 7 72 - #hatch 773 ->#pebble 774 - #brick));!! colorBox := newChild(Palette, 1040, self, nil); cbBrush := new(Brush); colors := new(Picture); patterns := new(Picture); ■ ПРИМЕНЕНИЕ ИНСТРУМЕНТОВ ФОРМИРОВАНИЯ ИЗОБРАЖЕНИЯ Использование инструментов, формирующих изображения для рисования в некотором окне, относительно просто. Должен быть инициализирован порт, выбраны перья, кисти и цвета. После того как эти приготовления сделаны, можно нарисовать любой из графических объектов, имеющихся в наборе. Ниже приводятся два примера использования инструментов ObjectGraphics для ри¬ сования прямоугольника в окне. Второй пример иллюстрирует использование объекта Brush. W1 := defaultNew(Window, "Graphics Drawing"); P1 := new(Port); setPort(W1, P1); p1 := new(Pen); setColor(p1, primary(Color, #red)); setPen(port(wD, p1); show(W1, 1) ; R1 := new(Rectangle); setOrigin(R1, 120@20); setCorner(R1/ 200@100); associate(port(W1), W1); draw(R1, port(W1)); dissociate(port(W1));
Применение инструментов формирования изображения 263 W1 := defaultNew(Window, "Graphics Drawing"); P1 := new(Port); setPort(W1, P1); show(W1, 1); R1 := new(Rectangle); setOrigin(R1, 120020); setCorner(R1, 2000100); B1 := new(Brush); setBrush(R1, B1); setColor(brush(R1), RGB(Color, 255, 0,0)); associate(port(W1), W1); draw(R1, port(W1)); dissociate(port(W1)); Конец данного раздела посвящен различным способам получения одних и тех же рисунков при значительном сокращении длины программ. Если хотите, мы пройдем путь от поразительно малых до грандиозных результатов. Задача, которой мы займемся, край¬ не проста: надо нарисовать круговую мишень (такого типа мишени вы могли видеть в стрелковом тире). Первая версия программы будет сделана прямым методом, громоздко и многословно (puc. 8.1). Все действия выполняются методом init класса TargetWindow: /* инициализировать экземпляр класса Target методом прямым, неэффектвным */ Def init(self) { port := new(Port); picture1 := build(Ellipse, 000, 2000200) picture2 := build(Ellipse, 000, 1800180) picture3 := build(Ellipse, 000, 1600160) picture4 := build(EHipse, 000, 1400140) picture5 := build(EHipse, 000, 1200120) picture6 := build(Ellipse, 000, 1000100) picture7 := build(Ellipse, 000, 80080); picture8 := build(Ellipse, 000, 60060); picture9 := build(Ellipse, 000, 40040); setColor(brush(picture1), primary(Color, #red)); setStyle(pen(picture1), #invisible); setColor(brush(picture2), primary(Color, #white)); setStyle(pen(picture2), #invisible); 'setColor(brush(picture3), primary(Color, #red)); setStyle(pen(picture3), #invisible); setColor(brush(picture4), primary(Color, #white)); setStyle(pen(picture4), #invisible); setColor(brush(picture5), primary(Color, #red)); setStyle(pen(picture5), #invisible); setColor(brush(picture6), primary(Color, #white)); setStyle(pen(picture6), #invisible); setColor(brush(picture7), primary(Color, #red)); setStyle(pen(picture7), #invisible); setColor(brush(picture8), primary(Color, #white)); setStyle(pen(picture8), #invisible); setColor(brush(picture9), primary(Color, #red)); setStyle(pen(picture9), #invisible);
264 Программирование при помощи ObjectGraphics Рис. 8.1. Изображение мишени setCursor(self, #finger); ^self } Для того чтобы быть уверенными, что мишень будет перерисова¬ на, что бы ни случилось с окном, мы напишем следующий метод gPaint: /* перерисовать объекты в окне */ Def gPaint(self, aPort, invalidRect bullsEye) { centerAt(picture, bullsEye); centerAt(picture2, bullsEye); centerAt(picture3, bullsEye); centerAt(picture4, bullsEye); centerAt(picture5, bullsEye); centerAt(picture6, bullsEye); centerAt(picture7, bullsEye); centerAt(picture8, bullsEye); cer.terAt (picture9, bullsEye) ; draw(picture, aPort); draw(picture2, aPort); draw(picture3, aPort); draw(picture4, aPort); draw(picture5, aPort); draw(picture6, aPort); draw(picture7, aPort); draw(picture8, aPort);
Применение инструментов формирования изображения 265 draw(picture9, aPort); } Теперь, пока эта программа работает, мы можем с уверенностью заявить, что выбрали не лучший способ решения нашей задачи. Но наверняка в процессе усовершенствования программы мы сумеем найти какие-нибудь мощные возможности класса Picture и эффективнейшие приемы их использования. В следующей версии инициализируется экземплярная переменная picture и при помощи сообщений add изображения, построенные сообщениями build для эллипсов добавляются к рисунку. /* инициализировать экземпляр Target */ Def init(self) { port := new(Port); picture := build(Picture, 40040); add(picture, build(Ellipse, 000, 2000200)); add(picture, build(Ellipse, 000, 18O018O)); add(picture, build(Ellipse, 000, 16O016O)); add(picture, build(Ellipse, 000, 14O014O)); add(picture, build(Ellipse, 000, 1200120)); add(picture, build(Ellipse, 000, 1000100)); add(picture, build(Ellipse, 000, 8O08O)); add(picture, build(Ellipse, 000, 6O06O)); add(picture, build(Ellipse, 000, 4O04O)); setColor(brush(picturel), primary(Color, #red)); setStyle(pen(picturel) , #invisible); setColor(brush(picture2), primary(Color, #white)); setStyle(pen(picture2), #invisible); setColor(brush(picture3), primary(Color, #red)); setStyle(pen(picture3) , #invisible); setColor(brush(picture4), primary(Color, #white)); setStyle(pen(picture4), #invisible); setColor(brush(picture5), primary(Color, #red)); setStyle(pen(picture5) , #invisible); setColor(brush(picture6), primary(Color, #white)); setStyle(pen(picture6), #invisible); setColor(brush(picture7), primary(Color, #red)); setStyle(pen(picture7), #invisible); setColor(brushXpicture8), primary(Color, #white)); setStyle(pen(picture8) , #invisible); setColor(brush(picture9), primary(Color, #red)); setStyle(pen(picture9) , #invisible); setCursor(self, #finger); ^self } Def gPaint(self, invalidRect bullsEye) { bullsEye := point(right(displayRect(self))/2, bottom(displayRect(self))/2); alignCenter(picture, bullsEye); draw(picture, aPort); }
266 Программирование при помощи ObjectGraphics Окончательная версия сожмет программу еще сильнее за счет использования двух циклов, которые придут на смену тотальному вызову инструментов формирования изображения прямым мето¬ дом. /* инициализировать экземпляр Target */ Def init(self i j) { port := new(Port); picture := build(Picture, 40040); add(picture, build(Ellipse, 000, 2000200)); add(picture, build(Ellipse, 000, 18O018O)); add(picture, build(Ellipse, 000, 1600160)); add(picture, build(Ellipse, 000, 1400140)); add(picture, build(Ellipse, 000,' 12O012O)); add(picture, build(Ellipse, 000, 1000100)); add(picture, build(Ellipse, 000, 80080)); add(picture, build(Ellipse, 000, 6O06O)); add(picture, build(Ellipse, 000, 4O04O)); { i := 0; loop while i 9 setColor(brush(at(picture, i)), primary(Color, #red)); i := i + 2; endLoop; }; { j := 1; loop while j 9 setColor(brush(at(picture, j)), primary(Color, #white)); j := j + 2; endLoop; }; do(picture, { using(pic) setStyle(pen(at(picture, pic)), #invisible) }); setCursor(self, #finger); ^self } ■ ПОЛИМАРКИ ObjectGraphics представляет класс, который называется PolyMark. Он используется для создания объектов, которые можно увидеть в окне, но они не будут являться частью законченного рисунка фис. 8.2). Полимарки — это объекты, состоящие из коллекций маленьких прямоугольничков, окружностей или дру¬ гих графических элементов, которые используются для маркиров¬ ки границ других графических объектов. PolyMark разработан с расчетом на то, чтобы использовать другие графические элементы,
Полимарки 267 Рис. 8.2. Квадратсчетырьмяполимарками которые создаются стандартным способом, затем размножать их в необходимых количествах и расставлять по узлам (вершинам) графических объектов. Некоторые из методов в классе Polymark являются методами класса. Кроме того, используется и перемен¬ ная класса, названная $StdSize. Это означает, что в любой момент времени используется только один стандартный размер для всех полимарок. Сообщение standardMark используется для получения размеров стандартного маркерного объекта, измеренного в пиксе¬ лах. Следующий пример демонстрирует рисование квадрата и последу¬ ющую маркировку каждой из его вершин при помощи квадратных маркеров: W1 := defaultNew(Window, "Marking а Square with PolyMarks"); P1 := new(Port); show(Wl, 1); R1 := build(Rectangle, 20020, 1000100); setPort(Wl, P1); Markl := build(Rectangle, 20020, stand¬ ardMark (PolyMark)); Pmarkl := new(PolyMark);
268 Программирование при помощи ObjectGraphics setMark(Pmark1, Mark1); add(Pmark1, origin(R1)); add(Pmark1, corner(R1)); add(Pmark1, point(left(R1), bottom(R1))); add(Pmark1, point(right(R1), top(R1))); associate(port(W1), W1); draw(Rl, port(W1)); draw(Pmark1, port(W1)); dissociate(port(W1)); ■ ИЕРАРХИЯ КЛАССА OBJECTGRAPHICS Object GraphSpace Включает в себя оси координат, размер мира и единицы измерения, координатные ограничения на точки сетки. GraphSpace может для окон, связанных с ним, преобразовывать логические координаты в физические Pen Реализует логическое перо для рисования как текстов, так и рисунков TextPen Специализация класса Pen для текстов Brush Реализует логические графические кисти, используемые для заполнения Shape-объектов Color Реализует независимую от платформы стандартную спецификацию цвета, которая является разновидностью общеупотребительной цветовой модели RGB. Каждый из трех основных цветов представляется целым в диапазоне от 0 до 255 IndexColor Позволяет специфицировать цвет при помощи указателя внутри какой-нибудь цветовой палитры, установленной для порта, где эти цвета используются Graphic Формальный класс, который объединяет все объекты, рисующие себя сами, порт дается в качестве аргумента Icon Реализует рисуемые пиктограммы LogBitmap Реализует независимый от платформы режим работы с частями битовой карты Bitmap Реализует независимый от платформы режим работы с частями битовой карты
Иерархия класса ObjectGraphics 269 ObjectGraphics HugeBitmap Shape Picture PolyMark Region RichText Rectangle Chooser MathRect RoundRect PolyShape Curve Bezier Cubic Port Поддерживает битовые карты размером более 16 Кбайт Объединяет все объекты — потомки Graphic, которые рисуют себя, очерчивая границы при помощи Реп-объектов, а затем закрашиваются при помощи объектов Brush Содержит коллекцию упорядоченных по вложенности объектов из Graphic. Полностью поддерживает протокол OrderedCollection, так же как и протоколы Pen и Brush Приспособления, которые используются для маркировки углов и вершин в других рисунках Реализует области как некоторые коллекции замкнутых изображений Реализует коллекции атрибутированных текстовых меток Реализует объекты прямоугольной формы Реализует интерактивный режим выбора Используется для математических действий, а не для рисования. Поскольку такие объекты не создают и не используют Pen или Brush, к ним не применимы сообщения draw и fastDraw Реализует прямоугольники с закругленными краями с регулируемыми радиусами кривизны Объединяет потомков Shape, которые определяются как коллекции точек (Point) Объединяет все кривые линии в ObjectGraphics Создает и рисует параметрическую кубическую кривую Безье Создает и рисует параметрическую кубическую кривую Обеспечивает работу дисплея в оконной среде
270 Программирование при помощи ObjectGraphics PhysicalPort Реализует ту часть взаимодействия между Port и PhysicalPort для MS-Windows, которая является специфической для различных машин ■ РАСШИРЕНИЯ КЛАССА WINDOW В OBJECTGRAPHICS Методы объектов: attachPort Каждое устройство вывода реализует метод attachPort, который вместе с PhysicalPort: associate подготавливает его к рисованию. (См. Bitmap:attachPort и Printer:attachPort.) Задает разрешение GraphSpace окружения для того, чтобы учесть характеристики принтера, представленного в данном контексте дисплея Создает новый оконный класс Struct. Переключение символьного курсора требует нулевого значения со смещением 14, при этом курсор будет пустым Очищает контрольные маркеры (checkmarks) в части меню, определенной при помощи low и high IDs. Затем проверяет menuID Создает окно в MS-Windows в соответствии с параметрами, указанными в аргументах. Аргумент style определяет стиль нового окна Освобождает контекст дисплея для self-объекта, Port отключает окно от устройства Определяет, объявлен ли данный пункт меню как контролируемый Возвращает область прямоугольного окна. Этот метод является частью общего протокола устройств вывода, Который распределен между классами Window, Bitmap и Printer Запрашивает текущую позицию курсора в мировых координатах Прячет курсор Переустанавливает позицию курсора (в мировых координатах) setResolution newWClass checkMenuGroup create detachPort isMenuChecked displayRect getCursorPos hideCursor setCursorPos
Расширения класса Window в ObjectGraphics 271 fenceCursor free gPaint greyMenuGroup modMenuGroup physicalOffset picture port scroll setBackColor setCursor Ограничивает движение курсора рамками указанного прямоугольника (в мировых координатах) Освобождает курсор от ограничений, наложенных ранее, после этого курсор может двигаться по всему экрану Сообщение для перерисовки в новой манере. aPort — это порт, который был связан с указателем на объект self для рисования. badRect определяет текущий прямоугольник, который требует модификации. ObjectGraphics автоматически будет отвергать попытки рисовать критический по времени рисунок снаружи от badRect. Используйте badRect, если хотите избавиться от ненужного рисования в дальнейшем Серые позиции меню (неиспользуемые) определяются при помощи low и high IDs Применяет метод, указанный в модификаторе, для части меню, определяемой при помощи low и high IDs Возвращает координаты верхнего левого угла для печати или рисования на данной странице. Класс Window в отличие от Printer не имеет физического смещения Возвращает оконный объект Picture Возвращает оконный объект Port Вызывается при обслуживании оконных портов для работы с графическими пространствами Window и Port. Этот метод входит в общий протокол устройств вывода (OutputDevice) Изменяет цвет фона всех окон данного MS-Window класса на Color. Если параметр nil, то фон не будет закрашиваться при посылке сообщений WM_ERASEBKGND. На открытых в текущий момент представителях класса изменение скажется только после аннулирования Переустанавливает курсор окна
272 Программирование при помощи ObjectGraphics showCursor space toggleMenuItem unGreyMenuGroup wait endWait setSpace setPicture setPort Показывает курсор Возвращает для данного окна объект GraphSpace. Формально выдает новое Space, запросившему пространство, если ничего не найдет в IVar. Потомки могут переопределять выбор из нескольких пространств, построение специальных пространств во время выполнения и т. п. Для контрольных маркеров в меню, отмеченных в menuID, меняет состояние на противоположное Делает доступными позиции меню, определенные в low и high IDs Инициирует ожидание для длинной операции Завершает ожидание для длинной операции Задает GraphSpace для окна Записывает в pictureIVar значение, предоставленное из Picture Задает оконный объект Port. Инициализация порта IVar открывает Актору доступ к средствам ObjectGraphics
Глава 9 ■ ПРИМЕР ПРИЛОЖЕНИЯ В WINDOWS 3.0 Эта глава описывает некоторые широко распространенные систе¬ мы. В ней рассматриваются проблемы разработчиков графическо¬ го пользовательского интерфейса (GUI). Показывается, как существенно может помочь техника объектно-ориентированного программирования в разрешении этих проблем. Хотя GUI исполь¬ зует Windows 3.0 и язык Актор, описываемые здесь приемы подходят для любых объектно-ориентированных GUI. Для того, чтобы опробовать объектно-ориентированный подход в среде Windows 3.0, я решил написать некоторую программу, которая была бы полезна для меня самого, дала бы мне одновременно возможность ’’мультидокументного” редактирования и предоста¬ вила средства контроля исполнения над всей разработкой и поль¬ зовательскими приложениями — и все это в одном основном окне. Получившаяся программа поэтому называется Executive Control — контроль исполнения. Данная программа является кон¬ тролером/редактором для разработчиков и рассчитана на особен¬ ности среды Windows 3.0. Хотя подобное применение кажется разумным с точки зрения пользователя, удобство от концентрации в одном месте таких сложных возможностей может превратиться в настоящий кошмар для разработчика GUL ■ КРАТКИЙ ОБЗОР EXECUTIVE CONTROL Как иной раз бывает в имеющихся на сегодняшний день GUI, наряду с достоинствами в среде Windows встречаются и неудобст¬ ва, и несуразности. Наверное, и обычный пользователь Windows, и программист приходят к мысли, как было бы хорошо, если бы удалось обеспечить доступ ко всем предметам, с которыми прихо¬ дится иметь дело, в одном месте. Вопрос только в том, до какой степени можно набивать возможностями одно командное окно, не загромоздив его? На этот вопрос я намереваюсь ответить програм¬
274 Пример приложения в Windows 3. 0 мой. Я пришел к выводу, что использование объектно-ориентиро- ванного подхода и приемов, доступных программистам, позволяет создать программу, которая вместо огромного количества архи- сложных меню и команд и наложенных друг на друга изображе¬ ний имеет лишь одно основное окно, где можно работать со всеми вещами, с которыми сталкивается пользователь или разработчик в ходе сеанса с Windows. Я должен уточнить, что это не просто программа, отвечающая на поставленный вопрос. Это программа, которую я использую сам, и написал я ее для того, чтобы мне было легче жить. Приведенное в этих Листингах является полезным работающим прототипом программы, появится и коммерческая версия. Раз так, то ее очень легко приспособить для вашей собственной системы и расширить так, как вам нравится. Программа Executive Control, представленная здесь, разработана специально для того, чтобы отвечать потребностям и пользовате¬ лей, и разработчиков приложений Windows. Для создания управ¬ ляющего окна, которое немедленно может выполнить множество программ в среде Windows из его системы меню и всё то, что можно было бы ожидать от мультидокументного редактирования, исполь¬ зовался Multiple Document Interface (MDI) — мультидокументный интерфейс Windows. Я пришел к выводу, что написать такую программу, используя объектно-ориентированную среду програм¬ мирования на Акторе, сравнительно несложно. В данной разработке пользовательского интерфейса мне пришлось использовать лишь агрегат из popup-меню. Как видите, еще есть много мест расширения набора вариантов и дополнительных иерархических рорир-инструментов. Хотя все может быть выпол¬ нено и при имеющейся полоске основного меню, если необходимо, ее легко расширить, не опасаясь загромождения. В данном прило¬ жении не было попытки увеличить область пользователя основно¬ го окна, поскольку в этом не было необходимости. Если появится такая необходимость, сделать это будет совсем несложно. Popup-меню настольной системы позволяет выполнять любую на¬ стольную принадлежность, указав на нее в меню. Меню приложе¬ ний Applications — это иерархическое popup-меню системы, которое упрощает доступ к приложениям за счет того, что они разбиты на категории. Ветвь инструментов Development, напри¬ мер, обеспечивает мгновенный доступ к набору инструментов пакета SDK. Приложение Executive Control строится на трех удивительно про¬ стых классах: ExecApp — подклассе класса Application; ExecWin — подклассе класса MDIFrameWindow; и SysDialog — специализации класса FileDialog. В объектно-ориентированных
MDI-поддержка в Акторе 275 GUI много усилий уходит на то, чтобы решить, какие классы надо использовать или создавать. В данном случае наиболее эффектив¬ ный подход состоит в создании небольшого количества новых классов, в которых некоторые основные методы могут переписы¬ ваться для того, чтобы обеспечить альтернативные способы их выполнения. Хотя вначале поиски наилучшего решения могут показаться поисками иголки в стоге сена, но когда правильное решение найдено, обычно сразу становится ясно, что это и есть наилучшее или, по крайней мере, весьма достойное решение поставленной задачи. В качестве инструмента использовались Актор 3.0, интерактивная объектно-ориентированная система программирования, подобная Смолтоку, но имеющая синтаксис, близкий к Си. ПосколькуАктор 3.0 позволяет в ходе диалога вызвать любую функцию Windows, это великолепная лаборатория для изучения средств Windows 3.0. Единственной неприятностью, которая сдерживала использование Актора, была ограниченность памяти. Теперь, когда у вас в Windows имеется масса мегабайтов в расширенном режиме 386-го процессора, этой проблемы больше не существует. Четырьмя глав¬ ными преимуществами Актора на мой взгляд являются: 1) подо¬ бный Си синтаксис; 2) объектно-ориентированность; 3) интерактивность; 4) большая библиотека классов для Windows, уже готовая для использования. ■ МУЛЬТИДОКУМЕНТНЫЙ ИНТЕРФЕЙС (MDI) MDI — одна из наиболее привлекательных новых сторон системы Windows 3.0. Вместе с тем работа по овладению этими средствами сложного взаимодействия между элементами управления фреймо¬ вых окон и рядом выбираемых дочерних окон, которые открывает пользователь, может обернуться лишь неуклюжими попытками как на стадии проектирования, так и на стадии реализации. Но, как оказалось, это как раз тот круг проблем, который превосходно решается объектно-ориентированными системами. ■ MDI-ПОДЦЕРЖКА В АКТОРЕ Актор поставляется вместе с набором классов, которые обеспечи¬ вают полную поддержку MDI стандарта Windows. Как следует из его названия, класс MDIFrameWindow используется для создания фреймовых (образующих общую рамку) окон MDI. Он переадресо¬ вывает соответствующие сообщения Windows своим процедурам и сохраняет цепочку содержащихся в нем дочерних окон. Для того чтобы создать некоторое фреймовое окно приложения, обычно вам требуется лишь создать потомок того класса, который содержит подходящий командный метод. Каждый раз при обработке сооб¬
276 Пример приложения в Windows 3.0 щения, которое создает новое дочернее MDI-окно, вызывается метод processMenu с указанием желаемого текста, названия окна, так что меню *’Windows” в вашем фреймовом окне будет иметь корректный список дочерних окон. Класс MDIFileWindow является потомком MDIFrameWindow. Он разработан для управления обработкой командных сообщений. Класс MDIChild содержит модификации, необходимые для того, чтобы окно могло функционировать как дочернее. Эти составляю¬ щие MDI-приложения предназначены для использования дочер¬ ним окном, являющимся потомком этого класса. Для того, чтобы использовать MDIChild в уже существующих программах, надо либо скопировать этот класс и сделать его потомком класса вашего дочернего окна, либо сделать ваш класс потомком MDIChild. Класс FileChildMDI является потомком класса FileWindow. В FileChildMDI имеются средства, позволяющие использовать FileWindow в MDI-приложениях. Класс MDIClient управляет со¬ зданием и сопровождением MDIClient окна. Это окно должно создаваться с окном MDIFrameWindow как родителем и автомати¬ чески уничтожаться по умолчанию при вызове закрывающей процедуры этого окна. И, наконец, имеется FileApp. Это простой класс, который содержит все необходимые методы для инициали¬ зации готовой MDIFileWindow-программы. Он используется для "распечатывания” (seal off) MDIFileWindow классов и создания автономных приложений. Он определяет главное окно, которое инициализирует все остальные. Это базисные классы, которые определяют мультидокументный интерфейс в Акторе. Давайте рассмотрим некоторые из существенных деталей их реализации. И MDIFRAMEWINDOW Поскольку это самый общий класс, его экземплярные переменные являются ключом к пониманию того, как работает данный под¬ класс Window. Эти переменные приведены в табл. 9.1. Переменная clientWindow хранит экземпляр класса MDIClient, связанный с главным фреймовым окном. Экземпляр класса Dictionary хранится в переменной childList для того, чтобы хра¬ нить бирки (tally) текущих открытых дочерних окон. Другая Таблица 9.1. Экземплярные переменные MDIFrameWindow clientWindow Указывает на оконное пространство клиента childList Словарь дочерних окон activeHWnd Хранит цепочку активных окон childClass Хранит цепочку типов дочерних классов windowMenu Хранит окно ”Window”
MDIFileWindow 277 экземплярная переменная хранит дескриптор текущего активного дочернего окна, за счет этого фокус управления определяется однозначно. Экземплярная переменная windowMenu используется для хранения пунктов динамического меню ”Window”, которое содержит различные опции для переупорядочивания дочерних окон. ■ MDIFILEWINDOW Для каждого MDI-приложения должен создаваться некоторый особый командный метод с таким расчетом, чтобы он знал, как организовать создание нового дочернего окна MDI. После того как MDI-дочернее окно создано, не должно быть проблем с передачей командных инструкций командному методу этого окна. Часть обработки команд, связанная с дочерними окнами, в про¬ грамме Executive Control локализована в методе childCommand, который определен ниже: Def childCommand(self, wP, 1) { if (wP == FILE_OPEN) or (wP == FILE_NEW) then createMDIChild(self, childClass, "(Untitled)", nil); command(childList[activeHWnd], wP, lP); processMenu(self, getText(childList[activeHWnd])); else if childList[activHWnd] command(childList[activeHWnd, wP, lP); endif; endif; exec WindowProc(self:ancestor, #WM_COMMAND, wP, lP); } Def init(self, cmdLine dlg fName) { init(self:ancestor, cmdLine); mainWindow := newMain(MDIFileWindow, "FileEditMenu", "File Editor”, nil); setClassType(mainWindow, #FileChildMDI); show(mainWindow, CmdShow); } Класс FileApp предоставляет средства, при помощи которых мо¬ жет быть создан MDI-редактор и использован либо в среде Актора, либо как автономное приложение. Сообщения для его создания весьма просты: F := new(FileApp); init(F, nil); Однако существует одна проблема в случае, если требуется создать автономное приложение или даже приложение, работающее в среде Актора, но в окне, которое остается открытым, когда главное окно Актора закрывается. Окно, созданное при помощи FileApp, не является главным, таким, как окна, создаваемые сообщением newMain в Акторе. Посылка сообщения
278 Пример приложения в Windows 3.0 F := newMain(FileApp); вызовет ошибку, поскольку оно предназначается для инициализа¬ ции класса. Альтернативный способ состоит в непосредственной инициализа¬ ции объекта MDIFrameWindow. Вот эта программа будет работать: W1 := newMain(MDIFrameWindow, "adirmenu", "К", nil); show(W1, 1); Эта программа уже справляется с созданием главного MDI-окна. Однако меню редактора является стандартным меню и не имеет средств для файловых операций, а без них это, конечно, не настоящий MDI. Даже после того как подходящая система меню будет определена, оно все равно не будет работать, пока не будет написан корректный командный метод для управления команда¬ ми меню. Модификация имеющегося командного метода здесь не поможет. Программа Executive Control разработана для того, чтобы решить проблемы, предоставив для автономных MDI-при- ложений настоящую массовую сборку меню. В ИЕРАРХИЯ КЛАССОВ MDI Как уже отмечалось, приложение Executive Cotitrol построено на трех достаточно простых наследуемых классах: ExecApp — под¬ классе класса Application; ExecWin — подклассе класса MDIFrameWindow; и SysDialog — потомке класса FileDialog. Со¬ ответствующая ветвь иерархии классов Актора, дополненная но¬ выми классами, выглядит так: Object Application ExecApp WindowsObject Control MDIClient Dialog FileDialog SysDialog Window MDIChild MDIFrameWindow ExecWin TextWindow EditWindow WorkEdit FileWindow FileChildMDI
Иерархия классов MDI 279 Выполнение этих сообщений открывает главное окно ExecutiveControl: EW := new(ExecApp); init(EW, nil); Из новых специфических методов в данном приложении важными являются два: метод init в классе ExecApp и командный метод в классе ExecWin. Главное окно использует скомпилированный меню-ресурс ?,ExecMenu’\ который определен в листинге 1, приве¬ денном несколько далее. Иерархические меню создаются просто при помощи вложенных POPUP инструкций. Текст главной программы командного метода ExecWin имеет следующий вид: if (wP == FILE_OPEN) or (wP == FILE_NEW) then createMDIChild(self, childClass, "(Untitled)", nil); command(childList[activeHWnd], wP, lP); processMenu(self, getText(childList[activeHWnd])); else if childList[activeHWnd] command(childList[activeHWnd], wP, lP); endif; endif; execWindowProc(self:ancestor, WM_COMMAND, wP, lP); Последовательность действий такова: выполняется проверка пара¬ метра сообщения WM_COMMAND wP, если оказывается, что был ’’нажат” айтем меню FileOpen, посылается сообщение createMDIChild, которое создает в качестве дочернего окна новый открытый файл. Это окно является экземпляром класса MDIChild. Для рассылки команд меню во все открытые окна используется встроенное сообщение childList. Вложенная условная конструкция else if childList[activeHWnd] command(childList[activeHWnd], wP, lP); endif; перед тем как послать командное сообщение, должна проверить, существует ли дочерний объект. Оставшаяся часть командного метода традиционна для Windows-программ. В ExecutiveControl много таких меню, для которых по технологическим соображени¬ ям требуется расчленять командный метод на несколько команд¬ ных модулей — это единственный способ обеспечить их компиляцию. Но, как оказывается, и в этом мы убедимся далее, почти всегда в такой ситуации для расчленения имеются весомые соображения и не только технологического плана.
280 Пример приложения в Windows 3.0 ■ УПРАВЛЕНИЕ БОЛЬШОЙ И СЛОЖНОЙ СИСТЕМОЙ МЕНЮ Имеется ряд важных вопросов, которые необходимо рассмотреть на двух различных уровнях для того, чтобы досконально разо¬ браться в дизайне пользовательского интерфейса. Прежде всего это планировка полоски меню. Данная хорошо знакомая разбивка полоски позволяет создавать пользовательский интерфейс, кото¬ рый может оказаться и очень хорошим и очень плохим. В связи с этим наиболее правильным решением здесь будет потратить неко¬ торые усилия и улучшить наиболее известные варианты дизайна, а не пытаться разработать совершенно новый подход. Но и при этом имеется масса важных деталей, требующих уточнения в хорошо всем известной типовой структуре. Пример. Пункт popup-меню, связанный с некоторым серьезным необратимым действием, должен располагаться там, где вероят¬ ность случайного нажатия будет минимальна, и должен требовать подтверждения, чтобы дать пользователю шанс вернуться назад. ■ ИЕРАРХИЧЕСКИЕМЕНЮ Иерархические или каскадные меню представляют весьма важ¬ ный тип меню, позволяющий упаковать множество функциональ¬ ных возможностей в единственную полоску меню и избежать при этом ее загромождения. Однако здесь, как и при использовании любого другого полезного средства, важно не переусердствовать. Иерархическое меню фактически представляет собой расширенное эквивалентное меню с целой панелью опций. Но она использует экранное пространство для опций вне собственно панели. На мой вкус, два иерархических меню — это максимум того, что можно размещать в одном окне. Хотя динамические меню можно созда¬ вать и динамически и статически, оказывается, что подход со статическим описанием ресурса-сценария для создания меню на¬ столько прост, что очень часто этим способом имеет смысл поль¬ зоваться с самого начала. Подобно любому другому меню иерархическое может быть создано из статически скомпилированного ресурса или по мере необходи¬ мости, динамически, используя интерфейс Windows. В Акторе имеется несколько различных способов динамического создания меню. Ниже приводится пример динамического иерархического меню, использующий класс MenuItem: Def appsPopup(self popup) { popup := newPopup(MenuItem, "&Applications"); addItem(popup, wpPopup(self)); addItem(popup, ssPopup(self)); addItem(popup, graphPopup(self));
Комбинирование статических и динамических меню 281 addItem(popup, miscPopup(self)); addItem(popup, utilityPopup(self)); addItem(popup, develPopup(self)); Арорир } Def wp(Popup(self popup) { popup := newPopup(MenuItem, "&Word Processors11); addItem(popup, new(MenuItem, "Word for Windows", 228, #word)); addItem(popup, new(MenuItem, "Ami”, 229, #ami)); addItem(popup, new(MenuItem, "Word Perfect", 230, #wpf)); Apopup } Динамические popup-меню полезны тогда, когда еще нет полной определенности, какой будет окончательная форма меню. Недо¬ статком этого варианта является то, что для создания меню он требует специальной процедуры. В случае статического иерархи¬ ческого меню процедура существенно проще. Описание ресурсного сценария для такого меню — это перечисление вложенных POPUP- инструкций, определяющих ресурс для меню. Например, иерар¬ хическое меню, определенное выше как динамическое, статически определялось бы следующим образом: POPUP "&Applications" BEGIN POPUP "&Word Processors" BEGIN MENUITEM "Word for Windows", WFW MENUITEM "Ami", AMI MENUITEM "Word Perfect", WPF END ■ КОМБИНИРОВАНИЕ СТАТИЧЕСКИХ И ДИНАМИЧЕСКИХ МЕНЮ Пользовательский интерфейс с программой Executive Control по¬ зволяет одновременно использовать статические и динамические меню в пределах одной полоски меню. Первая моя попытка добиться этого была безуспешной. Динамические меню захваты¬ вали полоску меню целиком, этот вариант функционировал, но только с динамическими меню. Первый вариант использовал классы Menu и MenuInem. Для создания меню Windows, которое является лишь системой меню на полоске меню, т.е. динамиче¬ ским меню, использовались средства MDIFrameWindow. Другой вариант описывается в ресурсном сценарии в листинге. Почти такое же действие имеет фрагмент из метода init MDIFrameW indow: windowMenu := Call CreatePopupMenu(); Call AppendMenu(window, MF_STRING, 9997, asciiz("&Cascade"));
282 Пример приложения в Windows 3.0 Call AppendMenu(window, MF_STRING, 9998, asciiz("&Tile")); Call AppendMenu(window, MF_STRING, 9999, asciiz("&Arrange Icons")); Call AppendMenu(hMenu, MF_POPUP, windowMenu, ascii z("&Windows")); ■ ДИНАМИЧЕСКИЕ ФАЙЛОВЫЕ ДИАЛОГИ Один из недостатков статических ресурсов состоит в том, что все составляющие ресурса должны быть предопределены. В случае, когда вам необходимо большое число элементов управления, от¬ личающихся друг от друга лишь незначительными деталями, вместо определения отдельного ресурсного элемента для каждого из них предпочительнее использовать динамические ресурсы. Это решение подходит для использования с различными типами фай¬ лового диалога. Часто для различных операций по работе с фай¬ лами можно использовать идентичные файловые диалоги. Для обеспечения этой потребности создан класс SysDialog, являющий¬ ся непосредственным подклассом класса FileDialog; его методы определены так, что диалоги создаются динамически. Например, метод copyDlg определяется следующим образом: Def copyDlg(self dlg) { dlg := new(DialogDesign); setText(dlg, "Copy File"); setSize(dlg, 000, 200070); addItem(dlg, newStatic(DlgItem, "From:", 925, 10010, 60016, 0)); addItem(dlg, newStatic(DlgItem, "To:", 925, 15040, 60016, 0)); addItem(dlg, newStatic(DlgItem, "", 101, 40010, 1 50010, 0)) ; addItem(dlg, newStatic(DlgItem, "", 101, 40040, 150010, 0)); runModa(dlg, nil, ThePort); } ■ ФАКТОРИЗАЦИЯ КОМАНДНЫХ МЕТОДОВ Одним из главных негативных факторов, с которыми встречаешь¬ ся при разработке сложных систем меню GUI, является перспек¬ тива кажущейся бесконечной цепочки саэе-операторов, которая чрезвычайно затрудняет отладку. Общий дух объектно-ориентиро¬ ванного программирования нацеливает на конструирование фун¬ кциональных пакетов, которые будут работать всегда, даже тогда, когда будут разработаны и добавлены в систему многие новые пакеты. По этой причине хороший объектно-ориентированный дизайн требует избегать создания монолитных громоздких коман¬ дных методов для обработки всех имеющихся меню. За счет
Факторизация командных методов 283 факторизации, обработки командных сообщений Windows в не¬ скольких отдельных командных методах, каждый из которых может работать как автономно, так и во взаимодействии друг с другом, программы компилируются быстрее, их легче отлаживать, они требуют меньше ресурсов памяти и их проще модифицировать (см. листинги 1-3). Основной принцип поистине прост. Когда для класса Актора определяется командный метод, обеспечивается возможность при¬ ема им WM_COMMAND-coo6njeHHft от Windows и прерывания его выполнения. Программирование командного метода обычно при¬ нимает форму последовательности саэе-операторов, которые про¬ веряют элемент управления IDs различных позиций меню и затем посылают соответствующие сообщения. Командный метод должен быть либо в классе, определяющем окно, в котором расположено данное меню, либо соответствующие сообщения-извещения (notification) должны передаваться к другим объектам. Однако в случае, когда число позиций меню слишком велико, этот подход неприменим. Вместе с тем главный командный метод можно написать так, что будет вызываться подкомандный метод, который предназначается для работы с несколькими различными функци¬ ональными пакетами. Таким образом, подкомандный метод можно написать так, что он всегда будет работать с каждым меню или с другим элементом управления, не обращая внимания на контекст, в котором используется. За вызов подчиненных командных мето¬ дов, которые требуются, отвечает командный метод-хозяин. Листинг 1. Листинг класса ExecWin /* Класс фреймового окна ExtcWin для программы Executive Control, комбинированное управляющее окно-хозяин Windows 3.0 и мультидокументный редактор */\\ inhent(MDIFrameWindow, #ExecWin, nil, 2, nil)!! now(class(ExecWin))!! now(ExecWin)!! /* запускает файловый диалог для уничтожения файлов */ Def deleteFile(self aFile) { FD := new(FileDialog, "*.*"); runModal(FD, DF_BOX, ThePort); copyAll(existsFile, aFile, 0)); } j * /* запускает файловый диалог для переименования файлов */ Def renameFile(self aFile) { FD := new(FileDialog, "*.*"); runModal(FD, RF_BOX, ThePort);
284 Пример приложения в Windows 3. 0 copyAll(existsFile, aFile, 0)); } » ! /* создает динамический файловый диалог */ Def fileDlg(self fDlg, cBox, lBox) { fDlg := new(DialogDesign); } i i /* запускает файловый диалог для копирования файлов */ Def copyFile(self aFile) { FD := new(FileDialog, "*.*"); runModal(FD, CF_BOX, ThePort); copyAll(existsFile, aFile, 0)); } I i /* подкомандный метод для работы с системными командами */ Def systemCommand(self, wP) { select case wP == CUD is currentDir(self); endCase; case wP == CHD is changeDir(self); endCase; case wP == CRD is createDir(self); endCase; case wP == CF is copyFile(self); endCase; case wP == RF is renameFile(self); endCase; case wP == DF is deleteFile(self); endCase; endSelect; } 1 i /* создать директорий */ Def createDir(self path dlg) { dlg := new(InputDialog, "Create Directory", "Enter path to create", ""); runModal(dlg, INPUT_BOX, ThePort); path := getText(dlg); create(Directory, path); } 1 i /* уничтожить директорий */ Def removeDir(self path dlg)
Факторизация командных методов 285 { dlg := new(InputDialog, "Remove Directory”, "Enter path to remove", "") ; runModal(dlg, INPUT_BOX, ThePort); path := getText(dlg); remove(Directory, path); } ! » /* сменить директорий */ Def changeDir(self newPath dlg) { dlg := new(InputDialog, "Change Directory", "Enter new path", ""); runModal(dlg, INPUT_BOX, ThePort); path := getText(dlg); makeCurrent(Directory, newPath); } 1 ! /* возвращает текущий директорий */ Def currentDir(self aString) { dlg := fullName(current(Directory)); new(ErrorBox, ThePort, "The current directory is" + aString, "Current Directory", 0); }!! /* подкомандный метод для работы с опциями приложений */ Def optionCommand(self, wP) { sdkCommand(self, wP); if wP == GLS glossWin(self, wP); endif; } ! ! /* подкомандный метод для выполнения приложений */ Def appCommand(self, wP) { select case wP == WFW is word(self, wP); endCase; case wP == AMI is ami(self, wP); endCase; case wP == EXL is excel(self, wP); endCase; case wP == ZNG is zing(self, wP); endCase; case wP == OBD is obdraw(self, wP); endCase; case wP == TBK is tbook(self, wP);
286 Пример приложения в Windows 3.0 endCase; case wP == NRT is norton(self, wP); endCase; case wP == XTG is xtree(self, wP); endCase; case wP == CPV is cppv(self, wP)* endCase; endSelect; } t j /********************************************************* Командный модуль, который организует создание дочерних окон для ведения документов и управление этими окнами *********************************************************/ Def childCommand(self, wP, lP) { if (wP == FILE_OPEN) or (wP == FILE_NEW) thencreateMDIChild(self, childClass, "(Untitled)", nil); command(childList[activeHWnd], wP, lP); processMenu(self, getText(childList[activeHWnd])); else if childList[activeHWnd] command(childList[activeHWnd], wP, lP); endif; endif; execWindowProc(self:ancestor, WM_COMMAND, wP, lP); } » t /*************************************************** Модуль командных методов для пакета SDK ***************************************************j Def sdkCommand(self, wP) select case wP == SPY is spy(self, wP); endCase; case wP == HPW is heapwalk(self, wP); endCase; case wP == ZMN is zoomin(self, wP); endCase? case wP == SDP is sdkpaint(self, wP); endCase; case wP == FED is fontedit(self, wP); endCase; case wP == SHK is shaker(self, wP); endCase; case wP == DED
Факторизация командных методов 287 is dlgedit(self, wP); endCase; endSelect; } I i /************************************************** Модуль командных методов Windows Menu, который создает динамическую систему меню Windows **************************************************/ Def windowCommand(self, wP) { select case wP == IDM_CASCADE is Call SendMessage(getHWnd(clientWindow), messageID(#WM_MDICASCADE), 0, 0L); endCase case wP == IDM_TILE is Call SendMessage(getHWnd(clientWindow), messageID(#WM_MDITILE), 0, 0L); endCase case wP == IDM_ARRANGEICONS is Call SendMessage(getHWnd(clientWindow), messageID(#WM_MDIICONSARRANGE), 0, 0L); endCase endSelect; } * j /* открывает один из вариантов окна-глоссария */ Def glossWin(self, wP WW) { WW := new(Window, ThePort, nil, "Glossary", &(375, 63, 535, 300)); show(WW, 1); L := new(ListBox, 375, WW); setCRect(L, &(0, 0, 153, 223)); moveWindow(L); addString(L, "Alpha"); show(L, 1); addString(L, "Beta"); addString(L, "Gamma"); addString(L, "Delta"); addString(L, "Epsilon"); addString(L, "Zeta"); addString(L, "Eta"); addString(L, "Theta"); addString(L, "Iota"); addString(L, "Kappa"); addString(L, "Lambda"); addString(L, "Mu"); addString(L, "Nu"); addString(L, "Xi"); addString(L, "Omicron"); addString(L, "Pi"); addString(L, "Rho"); addString(L, "Sigma"); addString(L, "Tau");
288 Пример приложения в Windows 3.0 addString(L, "Upsilon") addString(L, "Phi1'); addString(L, "Chi") addString(L, "Psi"); addString(L, "Omega"); } 1 i ^*************************************************** Модуль командных методов меню Desk Accessory — настольные принадлежности *************************************************** j Def deskCommand(self, wP) { select case wP == CLC is calc(self, wP); endCase; case wP == CLK is clock(self, wP); endCase; case wP == NPD is note(self, wP); endCase; case wP == CNP is cpanel(self, wP); endCase; case wP == CDF is card(self, wP); endCase; case wP == CLD is cal(self, wP); endCase; case wP == WRT is writer(self, wP); endCase; case wP == PTB is pbrush(self, wP); endCase; case wP == PRM is pnntman(self, wP); endCase; case wP == RCR is record(self, wP); endCase; case wP == STP is setup(self, wP); endCase; case wP == CLB is clipbrd(self, wP) ; endCase; case wP == TRM is terminal(self, wP); endCase; endSelect; }
Факторизация командных методов 289 /* выполняет утилиту SDK Zooming */ Def zooming(self, wp) { exec ("D: \WINDEV\ZOOMIN.EXE11) ; } * j /* выполняет Zing */ Def zing(self, wp) { exec("E:\ZING\ZING.EXE"); } * j /* выполняет Xtree */ Def xtree(self, wp) { exec(”С:\XTREE\XTG.EXE"); } » j /* выполняет MS write */ Def writer(self, wp) { exec("E:\WINDOWS\WRITE.EXE"); } i j /* выполняет MS Word */ Def word(self, wp) { exec(МС:\WINWORD\WINWORD.EXE"); } I j /* запускает терминальную программу */ Def terminal(self, wp) { exec("E:\WINDOWS\TERMINAL.EXE"); } i i /* выполняет утилиту SDK spy */ Def spy(self, wp) { ' exec("D:\WINDEV\SPY.EXE"); } ! ! /* запускает утилиту Shaker */ Def shaker(self, wp) { exec("D:\WINDEV\SHAKER.EXE"); } 1 j /* вызывает setup Windows */ Def setup(self, wp) { exec("E:\WINDOWS\SETUP.EXE"); } J0~857
290 Пример приложения в Windows 3.0 /* выполняет утилиту SDK sdkpaint */ Def sdkpaint(self, wp) { exec("D:\WINDEV\SDKPAINT.EXE"); } * j /* выполняет программу записи */ Def record(self, wp) { exec("E:\WINDOWS\RECORDER.EXE"); } I j /* выполняет программу-менеджер печати */ Def pnntman(self, wp) { exec("E:\WINDOWS\PRINTMAN.EXE" ; } J ! /* выполняет программу Windows рисования кистью */ Def pbrush(seIf, wo) { exec("E:\WINDOWS\PBRUSH\ZXE" ) ; } » j /* выполняет объектное рисование */ Def objdraw(self, wp) { exec("D:\DRAW\OBJDRAW.EXE"); } 1 j /* выполняет программу Windows notepad accessory */ Def note(self, wp) { exec("E:\WINDOWS\NOTEPAD.EXE”); } i j /* выполняет Norton Integrator */ Def norton(self, wp) { exec("E:\NORTON\NI.EXE”); } t j /* метод, который выполняет программу Heapwalker из MS Windows SDK */ Def heapwalk(self, wp) { exec("D:\WINDEV\HEAPWALK.EXE"); } i * /* выполняет редактор шрифтов SDK */ Def fontedit(self, wp) { exec("D:\WINDEV\FONTEDIT.EXE”); }
Факторизация командных методов 291 i ] /* выполняет электронную таблицу excel */ Def excel(self, wp) { exec("D:\EXCEL\EXCEL.EXE"); } i i /* выполняет редактор диалогов SDK */ Def dlgedit(self, wp) { exec("D:\WINDEV\DIALOG.EXE"); } I ! /* запускает броузер С++ Views */ Def cppv(self, wp) { exec("D:\CTV\CB.EXE"); } ! ! /* выполняет утилиту Windows для работы с управляющими панелями */ Def cpanel(self, wp) { exec("E:\WINDOWS\CONTROL.EXE"); } » i /* comment */ Def clock(self, wp) { exec ('*E: \WINDOWS\CLOCK.EXE1') ; } j I /* открывает clipboard-буфер */ Def clipbrd(self, wp) { exec("E:\WINDOWS\CLIPBRD.EXE”); } I f /* выполняет утилиту Windows cardfile */ Def card(self, wp) { exec(”Е:\WINDOWS\CARDFILE.EXE"); } I » /* выполняет утилиту Windows calculator */ Def calc(self, wp) { exec ("E: \WINDOWS\CALC.EXE11) ; } i j /* выполняет утилиту Windows calendar */ Def cal(self, wp; {
292 Пример приложения в Windows 3.0 exec("E:\WINDOWS\CALENDAR.EXE"); } i f /* выполняет текстовый процессор ami */ Def ami(self, wp) { exec(MD:\AMI\AMI.EXE"); } i j ^*********************************************************** Главный модуль командных методов, вызывающий все другие командные методы, такие как childCommand, знающий, как организовать создание нового дочернего MDI-объекта ***********************************************************/ Def command(self, wP, lP) { childCommand(self, wP, lP); windowsCommand(self, wP); systemCommand(self, wP); deskCommand(self, wP); appCommand(self, wP); optionCommand(self, wP); }!! /* Класс ExecApp */ inherit(Application, #ExecApp, nil, 2, nil)!! now(class(ExecApp))!! now(ExecApp)!! /* Создать и показать приложение mainWindow. Загрузить файл данных, если необходимо */ Def init(self, cmdLine dlg fName) { init(self:ancestor, cmdLine); mainWindow := newMainExecWin, "ExecMenu", "Executive Control", nil); setClassType(mainWindow, #FileChildMDI); show(mainWindow, CmdShow); }!! /* Класс SysDialog создает различные типы файловых диалогов */ inherit(FileDialog, #SysDialog, nil, 2, nil)!! now(class(SysDialog))!! now(SysDialog)!! /* Метод, который создает традиционный диалог для указания имен файлов перед их уничтожением */ Def deleteDlg(self dlg) { dlg := new(DialogDesign); setText(dlg, "Delete File"); addItem(dlg, newEdit(DlgItem, "", 101, 30020, 60015, 0)); runModal(dlg, nil, ThePort); - }!!
Факторизация командных методов 293 /* Метод, который создает традиционный диалог для указания имен файлов перед их переименованием */ Def renameDlg(self dlg) { dlg := new(DialogDesign); setText(dlg, "Rename File"); setSize(dlg, 000, 200070); addItem(dlg, newStatic(DlgItem, "From:", 925, 10@10, 60016, 0)); addItem(dlg, newStatic(DlgItem, "To:", 925, 15@40, 60016, 0)); addItem(dlg, newEdit(DlgItem, м", 101, 40010, 150010, 0)); runModal(dlg, nil, ThePort); }!! /* Метод, который создает традиционный диалог для указания имен файлов перед их копированием */ Def copy(self dlg) { dlg := new(DialogDesign); setText(dlg, "Copy File"); setSize(dlg, 000, 200070); addItem(dlg, newStatic(DlgItem, "From:", 925, 10010, 60016, 0)); addItem(dlg, newStatic(DlgItem, "То:”, 925, 15040, 60016, 0)); addItem(dlg, newEdit(DlgItem, им, 101, 40010, 150010, 0)); runModal(dlg, nil, ThePort); }!! /* Управляет событиями файлового диалога (OK, Cansel и т.д.). При выборе Open всегда что-то выполняется, если перезагрузить список, соответствующий текущему фильтру */ Def ,ommand(self, wP, lP action) { action := high(lP); select case wP == IDCOPY is end(self); copyDlg(self) ; endCase case wP == IDRN is renameDlg(self); endCase case wP == IDDL is deleteDlg(self); endCase case wP == IDCANSEL is resetDir(self); end(self, 0); endCase case wP == FILE_LB and action = LBN_SELCHANGE is sendDlgItemMessage(self, FILE_DIRLB, LB_SETCURCEL, -1 , 0) ;); endCase case wP == FILE DIRLB and action = LBN SELCHANGE
294 Пример приложения в Windows 3.0 is sendDlgItemMessage(self, FILE_LB, CB_SETCURCEL, -1, 0); setItemText(self, FILE_LB, getLoadDir(self) + fileSpec); endCase case (wP == FILE_DIRLB and action = LBN_DBLCLK) car(wP == IDOK and getLBSel(sel, FILE_DIRLB)) i.s newDir(self); endCase case wP == FILE_LB and action = LBN_DBLCLK is if getLoadFile(self) then resetDir(self); end(self, IDOK); endif; endCase case wP == IDOK is open(self) endCase endSelect; A1; }!! Листинг 2. Ресурсный файл сценариев для Executive Control #include "exec.h" #include "actor.h" ExecMenu MENU BEGIN POPUP "&File" BEGIN MENUITEM "&New", FILE_NEW MENUITEM "&Open...", FILE_OPEN MENUITEM "&Insert File...", FILE_READ MENUITEM "&Save", FILE_SAVE MENUITEM "Save &As...'\ FILE_SAVEAS END POPUP "&System" BEGIN MENUITEM ’’C&urrent Directory", CUD MENUITEM "C&hange Directory", CHD MENUITEM "C&reate Directory", CRD MENUITEM "Co&py File", CF MENUITEM "Re&name File", RF MENUITEM "Delete File", DF END POPUP "&Edit" BEGIN MENUITEM "Cu&t\tShift+Del", EDIT_CUT MENUITEM "&Copy\tCtrl+Ins", EDIT_COPY MENUITEM "Paste\tShift+Ins", EDIT_PASTE MENUITEM "C&lear", EDIT_CLEAR MENUITEM SEPARATOR MENUITEM "Select &All\tCtrl+A", EDIT_SELALL END POPUP "&Applications" BEGIN
Факторизация командных методов 295 POPUP "&Word Processors" BEGIN MENUITEM "Word for Windows”, WFW MENUITEM "Ami", AMI MENUITEM ”Word Perfect", WPF END POPUP "&Spreadsheets" BEGIN MENUITEM "Excel", EXL MENUITEM "WingZ", WNZ MENUITEM "Quattro Pro", QTP END POPUP "&Graphics" BEGIN MENUITEM "Zing", ZNG MENUITEM "Designer", DSR MENUITEM "Object Draw", OBD END POPUP "Miscellaneous" BEGIN MENUITEM "Toolbook", TBK MENUITEM "Macrocalc", MCLC MENUITEM "Almanac", ALM END POPUP "Utilities" BEGIN MENUITEM "Norton", NRT MENUITEM "Xtree Gold", XTG MENUITEM "Archive", ARC MENUITEM "Other", ETC END POPUP "Developments Tools" BEGIN MENUITEM "Actor", ACT MENUITEM "C++ Views", CPV MENUITEM "Spy", SPY MENUITEM "Heapwalker", HPW MENUITEM "Zoomin", ZMN MENUITEM "SDK Paint", SDP MENUITEM "Font Editor", FED MENUITEM "Shaker", SHK MENUITEM "Dialog Editor", DED END ' END POPUP "&Desktop" BEGIN MENUITEM "&Calculator", CLC MENUITEM "Cloc&k", CLK MENUITEM "&Notepad", NPD MENUITEM "C&ontrol panel", CNP MENUITEM "C&ardfile", CF MENUITEM "Ca&lendar", CLD MENUITEM "&Write", WRT MENUITEM "Paint&brush", PTB MENUITEM "&Print Manager", PRM MENUITEM "&Recorder", RCR MENUITEM "&Setup", STP
296 Пример приложения в Windows 3.0 MENUITEM "Cl&ipboard", CLB MENUITEM "&Terminal", TRM END POPUP "&Options" BEGIN MENUITEM "&Glossary...", GLS MENUITEM "&Menus", MNS MENUITEM "&Child Windows", CWS MENUITEM "Buttons", BTS MENUITEM "Me&ssages", MSS END END CF_BOX DIALOG DISCARDABLE 27, 23, 170, 116 STYLE WS_DLGFRAME WS_POPUP DS_ABSALIGN BEGIN CONTROL "" FILE_LB, "ComboBox", CBS_SIMPLE CBS_SORT WS_VSCROLL WS_TABSTOP WS_CHILD, 4, 30, 55, 80 CONTROL "Files:" 3, "static", SS_LEFT WS_CHILD, 4, 19, 31, 10 CONTROL "" FILE_DIRLB, "ListBox", LBS_STANDARD WS_TABSTOP WS_CHILD, 65, 42, 55, 68 CONTROL "Directories:" 3', "static", SS_LEFT WS_CHILD, 65, 31, 38, 10 DEFPUSHBUTTON "&Copy", IDCOPY, 130, 37, 30, 1 5, WS_CHILD PUSHBUTTON "&Cansel", IDCANCEL", 130, 63, 30, 15, WS_CHILD CONTROL "Directory:" 3, "static", SS_LEFT WS_CHILD, 4, 7, 32, 11 CONTROL "" FILE_DIR, "static", SS_LEFT WS_CHILD, 39, 7, 146, 11 END RF_BOX DIALOG DISCARDABLE 27, 23, 170, 116 STYLE WS_DLGFRAME WS_POPUP DS_ABSALIGN BEGIN CONTROL "" FILE_LB, "ComboBox", CBS_SIMPLE CBS_SORT WS_VSCROLL WS_TABSTOP WS_CHILD, 4, 30, 55, 80 CONTROL "Files:" 3, "static", SS_LEFT WS_CHILD, 4, 19, 31, 10 CONTROL "" FILE_DIRLB, "ListBox", LBS_STANDARD WS_TABSTOP WS_CHILD, 65, 42, 55, 68 CONTROL "Directories:" 3, "static", SS_LEFT WS_CHILD, 65, 31, 38, 10 DEFPU SHBUTTON "&Rename", IDRN, 1 30, 37, 34, 1 5, WS_CHILD PUSHBUTTON "&Cansel", IDCANCEL", 130, 63, 30, 15, WS_CHILD CONTROL "Directory:" 3, "static", SS_LEFT WS_CHILD, 4, 7, 32, 11 CONTROL "" FILE_DIR, "static", SS_LEFT WS_CHILD, 39, 7, 146, 11 END DF_BOX DIALOG DISCARDABLE 27, 23, 170, 116 STYLE WS_DLGFRAME WS_POPUP DS_ABSALIGN BEGIN CONTROL "" FILE_LB, "Combo Box", CBS_SIMPLE CBS_SORT WS VSCROLL WS TABSTOP WS CHILD, 4, 30, 55, 80
Факторизация командных методов 297 CONTROL "Files:" 3, "static”, SS_LEFT WS_CHILD, 4, 19, 31, 10 CONTROL "" FILE_DIRLB, "ListBox", LBS_STANDARD WS_TABSTOP WS CHILD, 65, 42, 55, 68 CONTROL "Directories:"- 3, "static", SS_LEFT WS CHILD, 65, 31, 38, 10 DEFPU SHBUTTON "&Delete", IDDL 130, 37, 34, 1 5, WS CHILD PUSHBUTTON~*'&Cansel", IDCANCEL", 130, 63, 30, 15, WS_CHILD CONTROL "Directory:" 3, "static", SS_LEFT WS_CHILD, 4, 7, 32, 11 CONTROL "" FILE_DIR, "static", SS_LEFT WS_CHILD, 39, 7, 146, 11 END Листинг 3. Файл-заголовок (header file) для Executive Control #define CHD 800 #define CRD 801 #define CUD 802 #define CF 803 #define RF 804 #define DF 805 #define WFW 810 #define AMI 811 #define WPF 812 #define EXL 820 #define WNZ 821 #define QTP 822 #define ZNG 830 #define DSR 831 #define OBD 832 #define TBK 840 #define MCLC 841 #define ALM 842 #define NRT 850 #define XTG 851 #define ARC 852 #define ETC 853 #define ACT 860 #define CPV 861 #define SPY 862 #define HPW 863 #define ZMN 864 #define SDP 865 #define FED 866 #define SHK 867 #define DED 868 #define CLC 870 #define CLK 871 #define NPD 872 #define CNP 873 #define CDF 874 #define CLD 875 #define WRT 876 #define PTB 877 #define PRM 878
298 Пример приложения в Windows 3.0 #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define RCR 879 STP 880 CLB 881 TRM 882 GLS 890 MNS 891 CWS 892 BTS 893 MSS 894 HLP 895 CF_BOX 896 RF_BOX 397 DF_BOX 898 IDCOPY 899 IDRN 900 IDDL 901 MDIFrame Window Исходный файл: MDIFRAME.CLS Происходит из: Window Переменные экземпляров: clientWindow childList activeHWnd childClass windowMenu Методы объектов: add close command createMDIChild defWndProc Указывает на оконное пространство клиента Словарь дочернего объекта Хранит цепочку активных дочерних объектов Хранит цепочку типов дочерних классов Хранит меню ”Window” Добавляет новое дочернее окно к экземплярной переменной childList Перед закрытием MDIFrameWindow пробегает по всем дочерним окнам приложения и удостоверяется, что они готовы к закрытию Обрабатывает все особые командные методы MDIFrameWindow При вызове этого метода с указанием имени класса, текста заголовка и спецификацией прямоугольника для окна будет создано новое дочернее MDI-окно. Затем метод добавит указатель на это дочернее окно в словарь childList, в качестве ключа взяв значение hWnd данного дочернего объекта По умолчанию обеспечивается для диалогов, которые ничего не делают. Возвращает 0 для нормального функционирования Windows. ПРЕДУПРЕЖДЕНИЕ: Не уничтожайте этот
Факторизация командных методов 299 execWindowProc getActiveHW nd getClient init processMenu remove setActiveHWnd setClassType FileChildMDI Исходный файл: Происходит из: Методы объектов: close command метод! Он может быть переопределен у потомков, если они при помощи Windows будут регистрировать собственные классы для их диалогов Удостоверяет, что все сообщения, которые не были обработаны методом Актора, переданы в DefFrameProc в Windows, тем самым фреймовое окно находится под надлежащим управлением Возвращает дескриптор окна активному MDIChild Берет указатель на пространство Frame-окна клиента Определяет фреймовое окно таким образом, что создает MDIClient для работы в нем. Кроме того, динамически создает меню "Windows”, которое инициализируется опциями ’’Cascade”, ”Tile” и ’’Arrange Icons” для управления MDIChild Поскольку текст заголовка в созданном окне многократно меняется, этот метод берет на себя заботу о корректировке соответствующего входа дочернего MDI в меню ’’Windows” Исключает дочерний MDI-объект из словаря childList Присваивает дескриптору активное дочернее окно MDI-приложения Устанавливает тип класса, который будет использоваться данным MDI-приложением при создании нового дочернего объекта в его пространстве клиента FILECHIL.CLS FileWindow Удостоверяет, окно готово к закрытию Управляет событиями меню. Он отличается от одноименного метода в классе FileWindow. Вместо использования командного метода
300 Пример приложения в Windows 3.0 create defWndProc destroy execWindowProc setText своего предка (WorkEdit) он будет использовать командный метод из EditWindow. Это по-существу устраняет возможность выполнения программной строки Актора или инспектирования среды (ни то ни другое невозможно в распечатанных (sealed off) приложениях). Эти опции меню можно удалить из FileEditMenu в ресурсном файле, если в stand-alone приложении используется FileChildMDI. Этот командный метод также можно удалить, если объект FileChildMDI должен использоваться только в среде Актора Создает дочернее MDI-окно в соответствии с указанными спецификациями. Значение параметра par — это родитель данного дочернего объекта. Ему должно быть присвоено фреймовое окно, которое управляет данным MDI-приложением. Параметр wName — это текст, который появится в полоске для заголовка; rect — прямоугольник, представляющий область, в которой должно появиться окно; style определяет, какими атрибутами должно обладать данное окно. Следует отметить, что окно будет создано и показано, как только сообщение WM_MDICREATE будет послано в MDICleint По умолчанию обеспечивается для диалогов, которые ничего не делают. Возвращает 0 для нормального функционирования Windows. ПРЕДУПРЕЖДЕНИЕ: Не уничтожайте этот метод! Он может быть переопределен у потомков, если они при помощи Windows будут регистрировать собственные классы для их диалогов Уничтожает дочернее MDI-окно, посылая сообщение WM_MDIDESTROY MDI Client-окну Определяет, что по умолчанию данная процедура окна будет выполнять функцию DefMDIChildProc, если сообщение не будет обработано системой Актор Записывает в заголовок окна данную строку
Факторизация командных методов 301 WM MOVE WM SETFOCUS WM SIZE MDIChild Исходный файл: Происходит из: Методы класса: new newStyle Методы объектов: close Управляет перемещением дочернего окна внутри фреймового окна. Примечание. Это сообщение должно посылаться процедуре дочернего окна, определенной по умолчанию Вызывается каждый раз, когда фокус перемещается данный экземпляр этого дочернего окна. Таким образом, фреймовое MDI-окно извещает свое дочернее MDI-окно о том, что оно сейчас становится активным. Примечание. Это сообщение должно посылаться процедуре дочернего окна, определенной по умолчанию Вызывается всегда, когда у данного окна изменяется размер. Примечание. Это сообщение должно посылаться процедуре дочернего окна, определенной по умолчанию MDICHILD. CLS Window Создает и возвращает новый оконный объект. В случае дочернего MDI-окна окно после создания автоматически будет показано на экране. При вызове этого метода указывается родитель данного дочернего окна (он должен быть фреймовым MDI-окном), имя для полоски заголовка и прямоугольник, в котором будет показано данное окно Создает и возвращает новое окно. Параметр par — родительское окно. wName — строка, содержащая заголовок для окна, rect — определяет, где и какого размера будет окно. Если rect равно nil, то используется умолчание, style — численная величина, которая является комбинацией величин, определяющих стиль окна. Если style равен nil, то стиль выбирается по умолчанию Гарантирует, что окно готово быть закрытым
302 Пример приложения в Windows 3. О create defWndProc destroy execWindowProc setText WM MOVE WM SETFOCUS Создает дочернее MDI-окно в соответствии с указанными спецификациями. Значение параметра par — это родитель данного дочернего объекта. Ему должно быть присвоено фреймовое окно, которое управляет данным MDI-приложением. Параметр wName — это текст, который появится в полоске для заголовка; fect — прямоугольник, представляющий область, в которой должно появиться окно; style определяет, какими атрибутами должно обладать данное окно. Следует отметить, что окно будет создано и показано, как только сообщение WM_MDICREATE будет послано в MDIClient По умолчанию обеспечивается для диалогов, которые ничего не делают. Возвращает 0 для нормального функционирования Windows. Метод может быть переопределен у потомков, если они при помощи Windows будут регистрировать собственные классы для их диалогов Уничтожает дочернее MDI-окно, посылая сообщение WM_MDIDESTROY MDIClient-окну Определяет, что по умолчанию данная процедура окна будет выполнять функцию DefMDIChildProc, если сообщение не будет обработано системой Актор Записывает в заголовок окна данную строку Управляет перемещением дочернего окна внутри фреймового окна. Примечание. Это сообщение должно посылаться процедуре дочернего окна, определенной по умолчанию Вызывается каждый раз, когда фокус перемещается в данный экземпляр этого дочернего окна. Таким образом фреймовое MDI-окно извещает свое дочернее MDI-окно, о том, что оно сейчас становится активным. Примечание. Это сообщение должно
Факторизация командных методов 303 посылаться процедуре дочернего окна, определенной по умолчанию WM_SIZE Вызывается всегда, когда у данного окна изменяется размер. Примечание. Это сообщение должно посылаться процедуре дочернего окна, определенной по умолчанию MDICIient Исходный файл: MDICLIEN.CLS Происходит из: Control Переменные класса: $NativeLiterals Переменные экземпляров: Методы класса: new Создает новый экземпляр класса MDIClient. В качестве аргументов получает указатель на родительское окно (это должно быть MDIFrame-oKHo) и структуру клиента. Эта структура должна быть четырехбайтовой конструкцией, содержащей дескриптор popup-меню в первом слове и ID для первого дочернего MDI-окна во втором слове. ID для дочернего MDI-окна обязательно должен быть достаточно большим, чтобы не было конфликтов с числами, которые передаются в командные методы MDIFrame-0K0H newStyle Занимается реальным созданием объекта MDIClient. Все параметры, за исключением style, можно посмотреть в методе new. Если в операторе newStyle никакой стиль не указан, то он определяется при помощи метода style MDIClient-объекта. Этот стиль должен подходить для всех MDI-приложений style Возвращает стиль окна, определяемый по умолчанию Методы объектов: create Создает новое MDIClient-окно. Определенные параметры должны быть такими же, как и в new, окно-клиент уже создано, и эти величины
304 Пример приложения в Windows 3.0 уже ’’запаяны” в систему. Например, MDIClient не должен иметь меню, оно всегда будет регистрироваться у ”mdiclient” класса и иметь свою позицию и размер, которые определяются при помощи MDIFrame-oKHa defWndProc По умолчанию обеспечивается рля диалогов, которые ничего не делают. Возвращает 0 для нормального функционирования Windows. Метод может быть переопределен у потомков, если они при помощи Windows будут регистрировать собственные классы для их диалогов destroy MDIClient-окно должно уничтожаться только через свое фреймовое окно, поэтому обычный метод уничтожения здесь неприемлем WM_DESTROY Окно MDIClient будет уничтожаться через его фреймовое окно фреймовой процедурой, определяемой по умолчанию WM_MDroESTKOY Сообщение, которое вызывается каждый раз, когда требуется уничтожить MDIChild. Процедура, определяемая по умолчанию для MDIClient, организует уничтожение дочернего окна. Затем указатель на дочернее окно будет исключен из словаря дочерних окон во фреймовом окне WM_NCDESTKOY При уничтожении окна-клиента удаляет структуры данных, ранее созданные для этого окна
Глава 10 Я ВОПРОСЫ ПРОЕКТИРОВАНИЯ В ОБЪЕКТНО-ОРИЕНТИРОВАННОЙ СРЕДЕ WINDOWS Эта глава посвящена нескольким вопросам объектно-ориентиро- ванного проектирования в среде Windows. Существуют две основ¬ ные точки зрения на проектирование: проектирование, дизайн интерфейса для пользователя и проектирование схемы использо¬ вания ресурсов компьютера. Соответственно, первая часть посвя¬ щена объектно-ориентированному дизайну вообще и среде Windows в частности. Вопрос рассматривается с позиций дизайна приложения, безотносительно к проблемам управления памятью компьютера. Вторая часть затрагивает другую группу вопросов и, в частности, такие темы, как: 1) управление памятью в Windows 3.0; 2) объектно-ориентированное управление памятью; 3) управление памятью в Windows при использовании Актора. ■ ДИЗАЙН ДЛЯ ПОЛЬЗОВАТЕЛЕЙ Первый принцип проектирования программ состоит в том, что проектировать надо программу, которая кем-то будет использо¬ ваться, и поэтому нужно иметь четкое представление о том, кто же будет потенциальным пользователем системы. Хотя каждый согласится с этим принципом, он по-прежнему нарушается гораздо чаще, чем следовало бы. Нельзя утверждать, что принцип не выдерживается из-за полной некомпетентности проектировщиков программных систем. Дело в том, что при проектировании для пользователей надо обладать большим объемом знаний и учиты¬ вать огромное число факторов. Как было бы хорошо получить книгу, которая дает детальные ответы на все вопросы изучаемой темы, именно такова наша цель — рассказать о нескольких
306 Вопросы проектирования в объектно-ориентированной среде Windows важных моментах, которые подскажут читателю дальнейшие на¬ правления изучения и развития. Как известно всем программистам, перед тем как начать выпол¬ нение инструкций, в компьютер надо загрузить данные. Человек же, приступив к некоторой деятельности, может при необходимо¬ сти переключиться на поиск нужных данных, а затем продолжить или завершить начатую деятельность. Очевидно, что в определен¬ ной сфере деятельности человека очень важно — иметь возмож¬ ность работать в таком стиле. Программные приложения приходится проектировать примерно в такой же манере. Хотя это рассуждение носит весьма общий характер, у меня есть несколько конкретных примеров. Большинство читателей со временем познакомится с распростра¬ ненными программами для рисования типа приложения Paintbrush, которое поступает вместе с системой Windows 3.0 и ее более поздними версиями. В этих программах полоска toolbox традиционно составляется из пиктограмм, которые представляют различные доступные для пользователя операции. Пытливый ком¬ пьютерный художник чувствует себя здесь почти как дома. Только вместо кисти он, как правило, использует мышь. Графические инструменты — это, если хотите, ”глаголы”, а графические линии и формы — ’’существительные” в предложениях этого вида про¬ граммной речи. Иногда сначала перед выбором ’’глагола” вы должны выбрать ’’существительное”, но типичным вариантом бу¬ дет обратный порядок: вы сначала будете выбирать ’’глагол” и лишь затем создавать или добавлять ’’существительное”. В целом такая схема подходит для создания картинок, здесь не имеет значенця, в каком из мест картинки будет строиться следующая часть изображения. Вы можете взять один инструмент и перехо¬ дить от одной области к другой до тех пор, пока вам нужен этот инструмент. В конце концов не имеет никакого значения, какой последовательностью действий вы дошли до того результата, ко¬ торый вам требовался. Интересно сравнить эту ситуацию с приложениями, где мы имеем дело с последовательными, линейными, а не пространственными композициями, например музыкальными произведениями, дви¬ жением и компьютерными программами. Здесь не имеет смысла предоставлять полную свободу в просмотре результатов работы. Она разворачивается в некоторую непрерывную последователь¬ ность, так, что обычно можно указать следующее место, где вы должны что-то сделать. Этим естественным концептуальным фо¬ кусом (фокальной точкой) может быть следующий кадр, следую¬ щий такт в музыкальном произведении или следующая строка. Здесь ’’существительное” главенствует над ’’глаголами”. Важно то, как ведет себя следующее ’’существительное”, а не то, какой инструмент придется использовать. Фактически, если попытаться
Разделение процедур и протоколов 307 применить некоторые изощренные инструменты в такого рода приложениях, это может кончиться тем, что будет получен резуль¬ тат, непригодный для большинства. Причиной этого является то, что вы навязали пользователю чуждый ему стиль работы, и многие откажутся от использования такой системы. ■ ЧТО ЖЕ РЕАЛЬНО ДЕЛАЕТ КОМПЬЮТЕР? Второй принцип проектирования программного обеспечения со¬ стоит в том, что программа должна настолько, насколько это возможно, отвечать главной функции, задаче, ради которой ис¬ пользуется машина. При проектировании любой разработки, ко¬ торая будет использоваться многократно, например при проектировании компьютерных программ, вам всегданеобходимо задаваться вопросом о главной функции, которую выполняет машина для пользователя. Как ни банально это звучит, но об этом часто забывают. Какова же главная функция персонального ком¬ пьютера в наши дни? По моему мнению, она состоит в следующем: Одной из наиболее важных функций настольного компьютера является то, что он позволяет быстро визуализировать чрезвы¬ чайно большие объемы информации и при этом способ визуали¬ зации может быть легко модифицирован в соответствии с нашими желаниями. Ключевое слово здесь — визуализация. Компьютеры превосходно умеют делать рисунки, текстовую и числовую информацию види¬ мыми. Если это так, то, следовательно, наилучшими программны¬ ми приложениями будут те, которые успешнее других справляются с данной функцией. Конечно, это словесный трюк, но как решить, какое из приложений является наилучшим? ■ РАЗДЕЛЕНИЕ ПРОЦЕДУР И ПРОТОКОЛОВ Первый шаг в разработке объектно-ориентированной системы — составление списка основных элементов данных и процедур, которые потребуются в данном приложении. Вероятно, список не будет оставаться в неизменном виде в процессе жизни программы, но это позволит взять хороший старт посредством разделения на части основных сущностей, с которыми данная программа будет работать. Если эти сущности ясны и понятны, то детали потом можно будет добавить. Были бы кости, а мясо нарастет! Теперь, когда вы имеете представление о том, из каких основных классов будет состоять ваше приложение, необходимо переходить к проектированию протоколов обмена сообщениями. Эти протоколы включают в себя схему того, кто (какие объекты) и как будет интерпретировать, понимать эти сообщения, как они будут циркулировать по программе и как их будут использовать классы-потомки.
308 Вопросы проектирования в объектно-ориентированной среде Windows В разработке объектно-ориентированных систем важную роль играет использование формальных классов. Это средство, позволя¬ ющее описать функциональную сторону предмета, т.е. его наибо¬ лее общие свойства без специфических особенностей, которые будут бесполезны для многих приложений. Когда класс-предок обладает нежелательными свойствами, их приходится подавлять в классах-потомках путем переопределения. Это означает, что появляются дополнительная работа и потенциальные проблемы. В идеальном случае набор формальных классов должен быть разработан таким образом, чтобы никакой необходимости подав¬ лять не требовалось. Хотя это не всегда возможно, но является одной из целей объектно-ориентированного проектирования. Для объектно-ориентированных систем типично, что для проблем создания системы объектов, отвечающей требованиям к програм¬ ме, имеется более чем одно возможное решение. Довольно часто этих решений несколько. На каком из них остановиться, зависит от главных целей и используемых критериев. Имеются по крайней мере два следующих друг за другом уровня проектирования: уровень стратегии и уровень тактики. На тактическом уровне нельзя уклоняться от проблем собственно программирования. В первую очередь результат данного уровня зависит от тщательной проверки исходного проекта, которая может привести к перепро¬ ектированию и перепрограммированию, в результате должен поя¬ виться окончательный, наилучший вариант проекта. На стратегическом уровне внимание должно быть нацелено на скру¬ пулезный общий дизайн, здесь нет места строкам программы. Везде, где программа может быть повторно используемой, насле¬ дование предпочтительнее переписывания как внутри одного при¬ ложения, так и для многих подобных программ. ■ ПРОЕКТИРОВАНИЕ КЛАССОВ Имеются несколько точек зрения, в соответствии с которыми можно рассматривать процесс проектирования классов. Подходя с формальных позиций, вы можете рассмотреть все переменные, которые обычно используются совместно с соответствующими процедурами*и объединить их в классы. При другом подходе вы можете рассматривать не только данное единичное приложение, но и всю сферу подобных приложений и проектировать такой класс, который будет играть желаемую роль в функциональной иерархии. Последний из подходов особенно понятен при разработ¬ ке формальных или абстрактных классов. Для специфической работы часто гораздо лучше создавать новый специализированный подкласс, поскольку при этом и проектирование, и программиро¬ вание существенно упрощаются.
Объектно-ориентированное проектирование графического интерфейса 309 Квалифицированный проектировщик объектно-ориентированных систем при разработке системы классов держит в поле зрения проблемы не только создаваемого приложения, но и многих других приложений, которые могли бы быть созданы в данной предметной области. Объектно-ориентированную систему вполне естественно представлять в виде некоего владельца нескольких прикладных программ, которые можно собирать из развивающихся наборов автономно функционирующих классов. Для того чтобы построить комплекс, обладающий таким качеством, проектировщик должен иметь в виду и другие приложения, которые могут быть созданы, пока идет данная разработка. В идеальном случае должно форми¬ роваться ядро классов, образующих базис как для создаваемого, так и для многих других приложений, которые будут появляться в будущем. ■ ЭКЗЕМПЛЯРЫ ДУБЛИРУЮЩИХСЯ ПРИЛОЖЕНИЙ Часто требуется разработать приложение так, чтобы в один момент времени его можно было иметь в нескольких экземплярах. Когда приложение состоит из нескольких взаимодействующих объектов, организация посылки сообщений между ними требует дополнительного рассмотрения. Причина этого в том, что каждому объекту приходится знать имя экземпляра объекта в конкретной копии приложения. Для того чтобы система из нескольких экзем¬ пляров мультиобъектных приложений работала корректно, имена соответствующих объектов должны различаться и каждый объект внутри приложения должен знать имена объектов в своей копии приложения. Отсюда следует, что имена экземпляров не могут появиться в текстах программ непосредственно. Решение этой проблемы может быть простым и элегантным. Во-первых, необхо¬ дим некоторый механизм, генерирующий для экземпляров уни¬ кальные имена. Во-вторых, каждый объект должен иметь экземплярную переменную, хранящую список имен объектов, которым он должен передавать сообщения. При посылке сообще¬ ния нужно обращаться к этой экземплярной переменной для того, чтобы определить, какому из объектов оно должно отправляться. Это еще одна важная функция, которую выполняют экземплярные переменные. ■ ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОЕКТИРОВАНИЕ ГРАФИЧЕСКОГО ПОЛЬЗОВАТЕЛЬСКОГО ИНТЕРФЕЙСА Последующие разделы представляют некоторые весьма распрост¬ раненные проблемы, которые возникают практически перед всеми разработчиками графических пользовательских интерфейсов (GUI). Мы должны обратиться к этим проблемам и попытаться
310 Вопросы проектирования в объектно-ориентированной среде Windows показать, как объектно-ориентированная техника может помочь в их решении. Перед тем как углубиться в детали, ответим на один важный вопрос: кого касаются различия, связанные с исполь¬ зованием объектно-ориентированного проектирования, — конеч¬ ного пользователя или только разработчиков приложений? На первый взгляд может показаться, что преимущества объектно-ори- ентированных систем видны только программистам, которые раз¬ вивают уже имеющиеся приложения. Однако если последовательно следовать объектно-ориентированной методоло¬ гии, то становится ясно, что конечный пользователь, как и следо¬ вало ожидать, увидит результаты этого в полученном приложении. В будущем появится ряд программ различного типа, из которых пользователи извлекут немалые выгоды. Чтобы проиллюстриро¬ вать этот вывод, давайте рассмотрим достаточно известный тип программного обеспечения: электронные таблицы. Будет ли объ- ектно-ориентированная электронная таблица отличаться от табли¬ цы, разработанной по традиционной схеме? В следующем разделе мы попытаемся ответить на этот вопрос. В коммерческом программировании требует рассмотрения еще один важный вопрос — переносимость разрабатываемых программ и пользовательских интерфейсов на различные компьютерные платформы. Например, если программист хочет перенести про¬ грамму, написанную для Windows, на Macintosh или X Windows, то для того, чтобы облегчить эту работу, надо решить ряд типич¬ ных проблем. Например, случай управления при помощи мыши. Macintosh имеет на мыши только одну кнопку. В связи с этим, разрабатывая класс для управления при помощи мыши, важно помнить, что должна быть возможность создавать версию програм¬ мы, которая сможет работать и при одной кнопке. ■ ПРИМЕР ПРОЕКТИРОВАНИЯ ОБЪЕКТНО-ОРИЕНТИРОВАННОЙ ЭЛЕКТРОННОЙ ТАБЛИЦЫ Если бы электронная таблица была построена целиком на прин¬ ципах объектно-ориентированного программирования, она была бы устроена совершенно иначе. Существующие электронные таб¬ лицы являются клеточно-ориентированными системами для редак¬ тирования, запоминания и перевычисления массивов зависимых друг от друга чисел. Их называют клеточно-ориентированными потому, что все формулы и операции определяются в терминах клеточек (или ячеек) прямоугольной сетки. Но что такое клетка? Она имеет двойственную природу. С одной стороны, это структура данных, а с другой, — структура визуализации. В клетке вы храните данные и, вместе с тем, клетка — это нечто, что определяется ви тально при помощи координат, которые указывают вам местопо¬
Открытые цепочки действий 311 ложение клетки на экране. Преимущества такой схемы подтвер¬ ждаются годами практического использования этих систем, но объектно-ориентированный подход мог бы революционизировать разработку электронных таблиц. Если электронная таблица разрабатывается исключительно на объектно-ориентированных принципах, нет никакой необходимо¬ сти быть строгим приверженцем клеточной ориентации. Это ут¬ верждение основывается на том, что модульные концепции модели разработки и визуализации должны быть независимыми, отдель¬ ными составляющими системы. В объектно-ориентированной таб¬ лице модели чисел можно было бы представить как формулы, которые применяются к числовым объектам, и не останавливать внимание на том, как визуализируются клеточки массива. В идеальном случае вы могли бы присвоить число клеточке и редак¬ тировать его так же, как и сейчас. И в результате вы смогли бы отделить модели чисел от проблем визуализации клеточек, а все формулы остались бы в нетронутом виде, сохранились бы различ¬ ные возможности для выдачи их на экран, включая и вариант упорядочения в форме таблицы. Другое важное следствие использования модульности при объек¬ тно-ориентированной разработке состоит в том, что объект любого типа можно заслать в клеточку для его визуализации в ней. Кроме того, туда можно заслать и несколько объектов. Хотя преимуще¬ ства этого шага могут быть видны не сразу, но доступ к значениям клетки как к собственно данным позволяет включать эти значения в формулы или исключать их оттуда, когда возникает необходи¬ мость. Это позволяет создавать модели, в которых может присут¬ ствовать или отсутствовать зависимость от количества клеточек. Модели такого рода полнее, чем современные электронные табли¬ цы, поскольку являются многоцелевыми. Из сказанного должно быть ясно, что в настоящее время большая часть явных достоинств объектно-ориентированного GUI обращена на программиста, одна¬ ко вскоре должно появиться новое поколение прикладных про¬ грамм, которые передадут эти преимущества пользователям, так как принципы объектно-ориентированного проектирования одно¬ временно просты в понимании и подталкивают к эффективной работе. ■ ОТКРЫТЫЕ ЦЕПОЧКИ ДЕЙСТВИЙ Понимание разработчиком того, что же ожидает пользователь от его программы, в огромной степени определяет успех разработки объектно-ориентированного программного обеспечения. Эта мысль всегда находит горячий отклик, и я хотел бы предложить некоторые принципы, которые смогут помочь вам. Полезным
312 Вопросы проектирования в объектно-ориентированной среде Windows принципом является принцип открытой цепочки действий. Наи¬ лучшим вариантом его объяснения будет ряд примеров. В одной из предыдущих глав описывалась архитектура HP New Wave. Есливы помните, проект этой системы базировался на идее объектов-приложений. Они могут состоять из нескольких различ¬ ных файлов, специфических для различных прикладных про¬ грамм и интегрированных в один документ-хозяин в пользовательской среде таким образом, что пользователю не при¬ ходится иметь дело ни с какими рутинными объяснениями того, как все это строится. В этом подходе имеется ряд явных досто¬ инств, но есть и несколько потенциальных проблем. Результаты работы пользователя собираются из многих файлов при помощи программного обеспечения высокого уровня и пользователю не важно, как это происходит, однако возможна ситуация, когда приложение будет себя вести как черный ящик, который выходит из-под его контроля. Это представляет здесь наибольшую опас¬ ность. Если ключевым моментом организации приложения явля¬ ется способ, которым множество файлов интегрируются в единое целое и процесс связывания для пользователя имеет неизвестную природу, это может привести к различным трудностям. Во-пер- вых, весьма вероятно, что приложение окажется непереносимым куда-либо. Если пользователь чувствует преимущества в возмож¬ ности переноса приложения в различные среды или на различные платформы, то он столкнется с неудобствами, пока для этого не будет сделано соответствующее обеспечение. Во-вторых, когда что-то испортится и приложение по той или иной причине пере¬ станет работать, хотя в распоряжении пользователя будут все файлы, но и тогда, вероятно, у него не будет никакого представ¬ ления, как разрешить возникшие трудности. Помимо этих общих опасностей, подстерегающих пользователя,не исключены и некоторые мелкие неприятности. Они связаны с тем, что часто прикладные программы, использующие один и тот же формат файла, могут вносить свой специфический для каждого инструмента вклад в один и тот же файл-накопитель результатов. Например, пользователь может использовать более чем один текстовый редактор, электронную таблицу или программу для рисования изображений для создания некоторого результирующе¬ го продукта, поскольку каждый из инструментов имеет некоторое средство, которого нет в других. Рабочий процесс напоминает здесь конвейерную линию. Прежде чем приобрести завершающую фор¬ му, один и тот же файл проходит через ряд подобных программ. В ходе этого процесса формат файла может измениться. Тогда может возникнуть необходимость в нескольких подобных резуль¬ тирующих файлах, которые интегрируются в объект того же типа, что имеется в системе HP New Wave.
Объектно-ориентированное проектирование для MS-Windows 313 Перечисленные трудности не фатальны для архитектуры систем типа New Wave. Однако этот вопрос приходится рассматривать, когда программная среда проектируется в целом. Возможно самая интересная проблема разработки современных сред GUI состоит в том, как облегчить использование программного обеспечения но¬ вичками, не превращая систему в абсолютно ”черный ящик”, который невозможно вскрыть, когда он неисправен. Одним из возможных решений здесь является идея нескольких уровней интеграции и доступа (не следует путать с несколькими уровнями объектно-ориентированного проектирования как такового). То, что здесь имеется в виду, сводится к пожеланию открывать ”черный ящик” или держать его открытым для тех, кто хочет получить к нему доступ. В идеале пользователь должен иметь возможность игнорировать все преграды, скрывающие от него что бы то ни было, если на то нет особых причин. Однако, если такая причина имеется, должен быть способ перехода на более низкий уровень интерфейсов, который разрешает данные проблемы и позво¬ ляет переносить приложение достаточно корректным способом. И ФАКТОРИЗАЦИЯ КОМАНДНЫХ МЕТОДОВ Как отмечалось в предыдущей главе, одной из неприятностей в сложных системах меню в GUI является появление очень длинных последовательностей сазе-операторов, которые крайне трудно от¬ лаживать. Стратегия объектно-ориентированного программирова¬ ния (ООП) базируется на построении функциональных пакетов, которые будут работать всегда, даже при появлении в системе любого количества новых пакетов. Из этого следует, что в хорошем объектно-ориентированном проекте надо уклоняться от больших монолитных командных методов для обработки всех меню, какие могут использоваться в системе. Как было показано ранее, при использовании техники факторизации, разделении обработки ко¬ мандных сообщений Windows между отдельными командными методами, каждый из которых может работать независимо друг от друга, а также в кооперации с другими меню, мы получаем следующие преимущества: программы компилируются быстрее, их проще отлаживать, они требуют меньший объем памяти и их гораздо легче модифицировать. ■ ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОЕКТИРОВАНИЕ ДЛЯ MS-WINDOWS Одной из оригинальных моделей объектно-ориентированного про¬ ектирования является архитектура Model-View-Control (Модель- Изображение-Управление), или коротко — MVC. Ключевым моментом в этой архитектуре служит идея о том, что очень важно разделить описание способов организации информации и ее xpa-
314 Вопросы проектирования в объектно-ориентированной среде Windows нения и описание способов организации доступа к информации и ее визуализации. Другими словами, данные приложений хранятся в подходящем наборе объектов, и модель данных не зависит от того, в какой форме их в конечном счете увидит пользователь. Для визуализации одних и тех же данных можно разработать целый ряд различных способов. Для показа определенного набора дан¬ ных, как правило, разрабатывается некоторый стандартный про¬ токол. Хотя эта идея может показаться очевидной или элементарной, но она отнюдь не лежит в основном русле современ¬ ного программирования. Более того, правилом, а не исключением является скорее противоположный подход. Представляется полезным рассмотреть некоторый конкретный пример. В областях систем обработки документов и настольных издательских систем стало нормой использовать специальные фай¬ лы, известные как style sheets (полосы описания стиля). Хотя не обязательно разрабатывать приложения, которые их используют, с применением объектно-ориентированного программирования, имеется аналогия между использованием style sheets и обсуждае¬ мой нами MVC-архитектурой. Аналогия базируется на идее хра¬ нения текстовых данных в одном файле и формата-стиля, при посредстве которого эти данные будут визуализироваться или печататься, в другом. Таким образом различные стили визуализа¬ ции можно легко и быстро связать с одними и теми же текстовыми данными. Преимущества этого подхода уже оценили производи¬ тели программных систем, и многие приложения для обработки документов уже используют эту технику. Модульность архитектуры MVC, конечно, имеет гораздо более фундаментальный характер и далеко идущие последствия, чем собственно техника style sheets. В сферу проектирования здесь включаются не только файлы на дисках, но и использование оперативной памяти приложений. Сюда относятся различные уровни модуляризации, которые могут весьма заметно сказаться на использовании приложений. Остановимся на трех основных объектно-ориентированных систе¬ мах программирования в Windows: Актор, Views и Common View. В их подходах наблюдается разительная разница, особенно замет¬ ная в отношении к иерархии классов. В подходе С++ Views явно реализуется архитектура MVC. Views в качестве базы слоя управ¬ ления использует класс Notifier (”извещатель”), прямой потомок корневого класса Object. В любом приложении всегда имеется ровно один экземпляр класса Notifier, за которым навсегда закреп¬ лено имя notifier. Объект notifier включается в основной цикл обработки событий. Кроме того, все приложения в качестве самого верхнего визуализируемого объекта имеют экземпляр подкласса из AppView. Все сообщения между объектной системой Views и системой Windows проходят через notifier, ожидающий стартовое
Иерархические меню 315 сообщение, вслед за которым начинается обработка события. Фор¬ мальный класс Window предоставляет методы, обеспечивающие обработку события любого типа. Классы ViewsWindows и View подобны классам WindowsObject и Window в Акторе соответственно. View имеет экземплярную пере¬ менную, названную model, которая используется для хранения ID-объектов, предоставляющих модель данных для приложения. Views использует три класса для реализации систем меню: Menu, PopupMenu и MenuItem. Views отличается от Актора в формировании отдельных классов для различных типов кнопок, таких как PushButton, CheckBox, RadioButton и TriState. Другим важным отличием служит то, что класс TextEditor является потомком Control, а не View, как это было бы, если бы проектирование следовало стилю Актора. Еще одно существенное отличие состоит в том, что класс ViewsDialog — потомок View. Если бы проектирование придерживалось линии Актора, то Dialog был бы не прямым потомком Window, а нахо¬ дился на одном уровне с View. В системе Views полоски меню конструируются при помощи создания экземпляра класса Menu и добавления к нему экземпляров класса PopupMenu. Различие в проектных подходах, используемых в Акторе и Views, легко увидеть в способе, которым эти две объектно-ориентирован- ные Windows-системы реализуют текстовое редактирование. Views предоставляет класс, называемый TextEditor, который является потомком Control и непосредственным подклассом EditBox. Сам по себе объект TextEditor в действительности является окном, способным хранить текст, выравнивать и проверять. Объекты TextEditor спроектированы как часть в композиции объектов, куда, кроме того, входят классы String, Stream и FileStream. Эта композиция интегрирована в еще более крупную композицию, которая образует главное окно. Класс ControlView спроектирован таким образом, чтобы обеспечить возможность интегрировать эк¬ земпляры класса ControlWindow в качестве дочерних элементов изображения полного приложения. Наряду с прочим он организу¬ ет списковые окна элементов управления. ■ ИЕРАРХИЧЕСКИЕ МЕНЮ Как было показано в примере из предыдущей главы, иерархиче¬ ские или каскадные меню предлагают готовое решение, позволя¬ ющее большие массивы функциональных возможностей втиснуть в одну полоску меню и при этом избежать замусоривания экрана. Однако и здесь можно переусердствовать. Похожим и эквивален¬ тным иерархическим меню является вариант с дополнительными полосками опций меню. В этом случае пространство экран*, необ¬ ходимое для опций, располагается вне основного прямоуголь**ш<
316 Вопросы проектирования в объектно-ориентированной среде Windows меню. На мой взгляд, два иерархических меню — это максимум того, что можно размещать в одном окне. В программе я решил все множество опций уместить в одном меню. Хотя иерархические меню можно создавать и динамически, и статически, оказывается, что подход со статическим описанием ресурса-сценария для созда¬ ния меню настолько прост, что очень часто этим способом имеет смысл пользоваться с самого начала разработки. ■ УПРАВЛЕНИЕ БОЛЬШОЙ И СЛОЖНОЙ СИСТЕМОЙ МЕНЮ Как отмечалось в предыдущей главе, у двух разных уровней проек¬ тирования, имеющих целью найти оптимальный дизайн пользова¬ тельского интерфейса, имеется ряд важных сторон. Прежде всего это планировка, разбивка полоски меню. Как упоминалось выше, традиционная разбивка вполне может дать спектр пользовательских интерфейсов от отличных до почти непригодных. Пока в разработке оптимальных пользовательских интерфейсов, так жекак и в других областях объектно-ориентированного программирования, еще нет однозначных методов, приводящих к успеху. ■ COMMONVIEW Актор и Views похожи между собой гораздо больше, чем каждый из них похож на CommonView. В отличие от Актора и Views CommonView не имеет единого корневого класса типа Object. Для создания интерфейса со средой Windows он предоставляет набор независимых базисных классов: Control, Event, Menu, Window и т.д. CommonView близок с Views в том, что DialogWindow является подклассом Window, а Edit является потомком Control. Базис для главных окон обеспечивается классом AppWindow и его прямыми потомками TopAppWindow и ChildAppWindow. Вообще говоря, CommonView транслирует сообщения Window в свои собственные внутренние дескрипторы событий и Event-объекты. Одной из наиболее оригинальных сторон проектирования CommonView является класс Event и его потомки. CommonView использует диспетчер, который отправляет Event-объекты к де¬ скрипторам событий в объектах Window. Event-объекты создаются лишь временно, для передачи возникших событий в Windows. Различные потомки класса Event связаны с соответствующими типами событий. Хотя вопрос, чем по существу различаютсяАктор и View, с одной стороны, и CommonView, с другой, — интересен, архитектура последней из систем не может быть признана удач¬ ной, поэтому в последующих версиях вероятно предстоит корен¬ ная перестройка этой системы.
Классы для изображений в С++ 317 ■ КЛАССЫ ДЛЯ ИЗОБРАЖЕНИЙ В С++ Class Object Assoc Clipboard Container Collection OrdCollect Stack Set Dictionary Tokens Display BitMap Window Control Button PushButton CheckBox RadioButton TriState Group InclusiveGroup ExclusiveGroup ListBox ScrollBar TextBox EditBox EditLine TextEditor View AppView PopupWindow Dialog ListSelect FileSelect Input Report YesNo String Stream File Archiver ToFMStream TagStrm
318 Вопросы проектирования в объектно-ориентированной среде Windows TokenStrm Menu PopupMenu MenuItem Serial Timer Notifier Port Printer Region Polygon Rectangle RoundRect Ellipse ■ КЛАССЫ COMMONVIEW Accel App Bitmap Brush Color Control FixedIcon TextControl Button CheckBox PushButton RadioButton Edit MultiLineEdit SingleLineEdit FixedText ListBox FileListBox Scrollbar HorizScrollbar WndHorizScrollbar VertScrolloar W ndV ertScrollbar Cursor DrawObject LineObject ShapeObject
Классы CommonView 319 EllipseObject RectangleObject TextObject Event ControlEvt ExposeEvt FocusChangeEvt KeyEvt MenuCommandEvt MenuCommandEvt MenuInitEvt MenuSelectEvt MouseEvt MoveEvt ReSizeEvt ScrollEvt EventContext Font Icon Menu SysMenu MessBox ErrorBox Pair Point Dimension Range Selection Pen Pointer Rectangle ResID ResString Window AppWindow ChildAppWindow TopAppWindow ControlWindow EditWindow DialogWindow ModeLessDialog FreeStore GlobalAllocator LocalAllocator
320 Вопросы проектирования в объектно-ориентированной среде Windows Container Lock Stack Ring ■ ПРОЕКТИРОВАНИЕ УПРАВЛЕНИЯ ПАМЯТЬЮ В WINDOWS В мультизадачных системах типа Windows часто в ходе одного сеанса несколько приложений будут запрашивать память. Для того чтобы быть уверенным в том, что доступ к памяти макси¬ мально эффективным образом получают все приложения, предо¬ ставляются соответствующие средства для управления памятью. Чтобы гарантировать своим приложениям в Windows по крайней мере такой объем памяти, какой им необходим, разработчики должны пользоваться именно этими средствами. Всего в Windows имеются около сорока различных функций по управлению па¬ мятью, которые позволяют разрешить как вопросы эффективно¬ сти, так и адресации к оперативной памяти. В следующих разделах я приведу сжатый обзор способов управления памятью при работе под управлением MS-Windows. Память в Windows может выделяться двумя способами: из гло¬ бальной или локальной кучи (heap). Глобальная куча содержит память, которая доступна всем приложениям. Локальная куча предоставляет память только для отдельного приложения. Память в Windows выделяется блоками и может выделяться повторно. Ее можно переместить или даже сбросить, отказаться от нее. Пере¬ мещаемые блоки памяти не имеют фиксированных адресов. В любой момент времени Windows может переместить их в другое место. Наличие перемещаемых блоков позволяет сливать участки свободной памяти для размещения весьма больших блоков. Ёсли выделенный блок памяти лежит между двумя свободными участ¬ ками, его можно передвинуть так, что эти два участка сольются в один блок с последовательной, непрерывной адресацией. Сбро¬ шенная память — это память, которая может быть освобождена или выделена, размещена вновь. Естественно это требует уничто¬ жения данных, которые в ней содержатся. Когда в Windows выделяется блок памяти, приложению, запросившему его, возвра¬ щается дескриптор блока (handle). Этот дескриптор представляет собой не адрес, а скорее средство для поиска текущего адреса данного блока в любой момент времени. Доступ к блокам памяти использует механизм ’’запирания” — блокировки дескрипторов памяти. Пока дескриптор заблокирован, Windows не может перемещать или сбрасывать его. При этом приложению выдается указатель на начало данного блока и оно получает надежный доступ к памяти. ’’Отпирание” — разблоки¬
Типы хранения данных 321 ровка дескрипторов памяти выполняется самим приложением. Эти средства нацелены на обеспечение максимально эффективного управления памятью, пользователи должны следовать правилу, в соответствии с которым по завершении использования блока его дескриптор должен сразу же разблокироваться. Большинство приложений в Windows использует смешанные мо¬ дели памяти. Рекомендуется работать с небольшими сегментами памяти длиной около 4K для того, чтобы Windows было легче перемещать эти сегменты в памяти. Приложения могут получать память из глобальной и локальной кучи. Главное соображение, определяющее, какую из куч следует использовать, обычно связа¬ но с объемом необходимой памяти. Блоки большего размера обычно размещаются в глобальной куче, где один блок может иметь размер больше 64K. Основными функциями Windows по управлению глобальной кучей являются GlobalAlloc, GlobalLock, GlobalUnlock, GlobalCompact и GlobalFree. Локальная куча прило¬ жения — это свободная память в сегменте данных, она может выделяться для различных целей. Локальная куча предоставля¬ ется системой Windows не автоматически, он запрашивается при помощи оператора HEAPSIZE. Естественно, локальная куча не может быть более 64K, что является размером сегмента данных приложения. ■ ТИПЫ ХРАНЕНИЯ ДАННЫХ В Windows можно использовать семь типов хранения данных. Используются для статических переменных, таких как переменные> объявляемые в С при помощи ключевых слов static и extern Используются для переменных, размещаемых при вызове функции в стеке Любые данные в областях памяти, которые размещаются при помощи LocaLAJloc Любые данные в областях памяти, которые размещаются при помощи GlobalAlloc Используются как дополнительное хранилище, которое может потребоваться для оконного класса Используются как дополнительное хранилище, которое может выделяться для структур WNDCLASS Память, используемая для ресурсов в в ЕХЕ-файлах приложений, ii-857 Статические данные Автоматические данные Локальные динамические данные Глобальные динамические данные Дополнительные байты окна Дополнительные байты класса Ресурсы
322 Вопросы проектирования в объектно-ориентированной среде Windows которые могут быть загружены в память Приложения, которые используют средства управления памятью повышенной эффективности, должны обеспечивать реакцию на сообщение WM_COMPACTING. ■ СБРАСЫВАЕМАЯ ПАМЯТЬ Создание приложения в Windows со сбрасываемой памятью долж¬ но происходить явно. Для того чтобы создать сбрасываемый блок памяти, необходимо использовать оба варианта функции GlobaLAiloc: GMEM_MOVEABLE и GMEM_DISCARDABLE. Напри¬ мер, декларация имела бы такой вид: hMem = GlobalAlloc (GMEM_MOVEABLE GMEM_DISCARDABLE) , 4096L) ; Собственно отказ от сбрасываемой памяти в Windows произойдет тогда, когда этого потребует заказ на выделение памяти. От какого из сброшенных блоков в действительности отказаться, Windows определяет на основе алгоритма LRU (least recently used — наибо¬ лее долго не используемый). Функция Windows GlobalDiscard от¬ казывается от данных, хранящихся в блоке, но сохраняет его дескриптор. Функция GlobalReAlloc делает несброшенные блоки памяти сброшенными и наоборот. ■ УПРАВЛЕНИЕПАМЯТЬЮ ПОВЫШЕННОЙ ЭФФЕКТИВНОСТИ В следующих двух разделах описываются две конфигурации па¬ мяти Windows, обеспечивающие повышенную эффективность. Эго стандартная конфигурация и конфигурация для усовершенство¬ ванного режима 386 процессора. ■ СТАНДАРТНЫЙ РЕЖИМ Конфигурация стандартного режима памяти Windows устанавли¬ вается по умолчанию в компьютерах с 286-м процессором, имею¬ щих по крайней мере 1 Мбайт памяти, и в компьютерах с 386-м, имеющих более одного и менее двух мегабайтов памяти. Windows использует защищенный режим процессоров 80286 и 80386 со стандартным режимом памяти. Когда Windows запускается в этом режиме, глобальная куча обычно разбивается на три отдельные блока памяти. Первый сегмент — это обычно сегмент DOS на 640 Кбайт. Второй блок — это расширенная память, которая выделя¬ ется при использовании драйвера расширенной памяти, но потом используется непосредственно. И наконец, третий блок в стандар¬ тном режиме — это область старших адресов памяти (high memory area — HMA), которая может доступна только при условии, что
Библиотека WINMEM32. DLL 323 никакие другие программы до запуска Windows в область не загружались. Глобальная куча Windows формируется при связы¬ вании этих трех областей памяти между собой. Сбрасываемые сегменты памяти выделяются с верхушки кучи, закрепленные — начиная с нижней границы кучи, перемещаемые программы и данные — выше закрепленных сегментов. ■ УСОВЕРШЕНСТВОВАННЫЙ РЕЖИМ 386-ГО ПРОЦЕССОРА В компьютерах с 386-м процессором с двумя мегабайтами памяти и больше Windows может быть запущена в усовершенствованном режиме 386-го процессора. В этом режиме Windows предоставляет виртуальную память, которая использует и расширенную память, и пространство жесткого диска, что позволяет обеспечить про¬ странство памяти, превышающее 64 Кбайт. В усовершенствован¬ ном режиме глобальная куча Windows состоит из большого единого виртуального адресного пространства. Размер пространст¬ ва определяется объемом расширенной памяти и объемом доступ¬ ной памяти на диске. Структура этого пространства виртуальной памяти, представляющая собой один большой блок, напоминает базисную конфигурацию памяти, и ее разбивка является точным аналогом базисной конфигурации, хотя размеры пространства существенно больше. ■ НЕСКОЛЬКО ПРАВИЛ "ХОРОШЕГО ТОНА” 1. Не используйте длинные указатели на статические данные в малых и средних моделях. 2. Не передавайте данные в другие приложения через гло¬ бальный дескриптор. 3. Не рассчитывайте на какую-либо взаимосвязь между де¬ скриптором и длинным указателем в любой из моделей. 4. Не выполняйте арифметических действий над сегментами. 5. Не сравнивайте адреса сегментов. 6. Не считывайте и не записывайте в объекты памяти после их уничтожения. ■ БИБЛИОТЕКА WINMEM32.DLL В качестве одной из составляющих пакета Windows 3.0 SDK фирма Microsoft поставляет библиотеку динамического связывания (dynamic link library), называемую WINMEM32.DLL, которая пре¬ доставляет функции для использования возможностей 32-битовой адресации процессоров 80386 и 80486. Притягательность DLL в том, что она потенциально предоставляет долгожданные возмож¬
324 Вопросы проектирования в объектно-ориентированной среде Windows ности адресации, выходящие за рамки адресации сегментирован¬ ной памяти в мире MS-DOS. В ответ на вопрос, позволяет ли в конце концов DLL использовать однородную модель памяти с простой адресаций (flat memory model), к сожалению, приходится отвечать и да и нет. Это правда, что функции, представленные здесь, используют 32-битные регистры чипов 80386 и 80486. Правда и то, что эти схемы адресации можно использовать при определении единых сегментов памяти с длиной, большей, чем может потребоваться какому-нибудь из них, Однако собственно Windows — это программа, написанная в сегмейтириванной моде¬ ли памяти, как и MS-DOS. Поэтому каждое приложение в Windows имеет по крайне мере один 16-битовый сегмент в старой 16-битовой сегментированной модели. При этом, несмотря на данное ограни¬ чение, львиную долю программ и данных приложений в Windows можно размещать с использованием новой 32-битовой адресации. Главным в DLL является то, что она реализует стандарт для модели памяти с простой адресацией» которого Microsoft обещает твердо придерживаться в будущих версиях Windows. Однако в отноше¬ нии уверений в том, что программы, написанные с использованием WINMEM32.DLL, будут поддерживаться во всех последующих версиях Windows, есть некоторые сомнения. Можно ожидать, что в использовании гибридной модели памяти, которая состоит час¬ тично из 16, а частично из 32-битовой адресации, появится ряд проблем. И не удивительно, что трудности становятся особенно острыми, когда вызываются прерывания. Все программы, напи¬ санные в 32-битовой модели, должны отвечать определенным ограничениям, они не должны содержать программ или данных, к которым возможен доступ во время прерывания. Для разработ¬ чика главным следствием этого ограничения является фактиче¬ ская невозможность использовать языки высокого уровня для написания программ использующих WlNMEM32.DLL. Только программисты, пишущие на ассемблере, могут попытаться исполь¬ зовать эту библиотеку. Это означает, что вероятно треть пакетов поставят ассемблерный код, который можно будет загружать вместе с программами на Си или на других языках для того, чтобы обеспечить доступ к 32-битовой памяти для разработчиков, не принятых еще в члены клуба искусного рукоделия на языке ассемблера. ■ УПРАВЛЕНИЕ ПАМЯТЬЮ В АКТОРЕ Одним из главных отличий между Актором и языком типа Си является то, что Актор предоставляет автоматическую систему управления памятью, а в Си приходится при необходимости заниматься выделением и освобождением памяти. Актор выпол¬ няет инкрементальную сборку мусора в рамках фонового процесса, что является одной из уникальных и важных возможностей среды
Управление памятью в Акторе 325 Windows. Не раз вы могли бы подумать, что Windows и самой нужен хороший сборщик мусора. Актор разделяет ресурсы памяти на статическую и динамическую память. В статической памяти хранятся постоянные объекты, такие как классы и методы. Динамическая память используется для изменчивых конструкций строк и длинных целых. Смысл введения двух типов памяти в том, что при этом мевьше работы остается для сборщика мусора. Сборщик мусора оставляет стати¬ ческую память без внимания я заботиться только о мусоре в динамической памяти. Его работа состоит в кодировании ,чжявых* объектов в новую область памяти, а все остальное остается в старой области. Функция ShoeRoom! в меню рабочего окна показывает три харак¬ теристики: объем статической памяти Актора, объем динамиче¬ ской памяти и объем свободной памяти в Windows. После того как вы запустили Актор, посмотрите на объем памяти Windows. Для этого воспользуйтесь сообщением: Call GlobalCompact(-1); Затем сравните новое и старое значения свободной п&мяти Windows. Таким же образом поступите и с локальной иамятью, используя такое сообщение: Call LocalCompact(-1); Если на вашем компьютере вы располагаете изрядным объемом памяти и имеете собственный MS-Windows 3.0 SDK, вы можете попробовать поставить такой эксперимент. Загрузите программу HeapWalker. Переместитесь в меню Alloc и выберите позицию Allocate All of Memory (выделить всю память). Теперь вся память Windows выделена. Вернитесь назад в меню Alloc и выберите опцию Free 50 Кбайт (освободить 50 Кбайт). Не выходя из HeapWalker, переключитесь на Актор и посмотрите в ShowRoom! Если хотите, выполните Call GlobalCompact(-1); и сравните. Теперь вернитесь в HeapWalker и выберите из меню Alloc Free All (освободить все). После этого можете вернуться назад и сравнить данные функции ShowRoom! в Акторе. Другой способ использования программы HeapWalker в качестве источника справочных сведений состоит в том, чтобы запустить ее в начале сеанса Windows и затем следить, сколько памяти выде¬ ляется для различных средств Windows. Кроме того, в любой момент вы можете при помощи HeapWalker сохранять фиксацию выборочных данных из карты памяти. Затем загрузите несколько различных программ и сравните. Если необходимо,, сохраните одну или несколько фиксаций состояния памяти Windows в раз¬
326 Вопросы проектирования в объектно-ориентированной среде Windows личные моменты сеанса. Таким способом в результате небольших упражнений вы познакомитесь поближе с методами управления памятью в Windows в ситуациях, когда несколько задач конкури¬ руют из-за определенных ресурсов. Опыт управления памятью в Акторе позволяет сделать ряд прак¬ тических выводов, которые условно можно объединить в две группы. Одни касаются управления памятью в среде разработки, а другие — управления памятью для пользователя приложения, которое вы написали. И те и другие тем не менее тесно связаны между собой. В целом то, что сохраняет память в программе, которую вы написали, сохраняет ее и в среде разработки. Неплохо знать, что поскольку сборщик мусора переписывает объекты меж¬ ду двумя динамическими областями памяти то в одну, то в другую сторону, вам требуется вдвое больше пространства, чем вы, каза¬ лось бы, используете. По этой причине всегда, когда вы можете разместить в статической памяти что-то, что до этого находилось в динамической, вы экономите пространство. Вы могли бы предположить, что в статической памяти тоже может накапливаться мусор. Так оно и есть, поэтому в Актор входит и статический сборщик мусора. Эту роль выполняет функция Cleanup!, которую можно найти в рабочем окне Workspace. В реальном режиме размер динамической памяти для Актора уста¬ навливается в диапазоне 25 — 30 Кбайт. Для усовершенствован¬ ного режима 386-го процессора верхняя граница устанавливается в 1 мегабайт и определяется требованиями приложения и процесса разработки. Приложения, написанные на Акторе, не могут извле¬ кать пользу из использования статического сборщика мусора, пока это средство не встроено в них соответствующим образом, однако динамическая сборка мусора является встроенной и автоматиче¬ ской и представляет собой одну из наиболее привлекательных черт приложений, созданных при помощи Актора. ■ СВОППИНГ СТАТИЧЕСКОЙ ПАМЯТИ Один из способов сократить количество памяти, которое програм¬ ма Актора использует в Windows, — это использовать технику своппинга (подкачки) статической памяти. Своппинг статической памяти работает постранично, отправляя наиболее долго неис¬ пользуемые объекты на диск и считывая их обратно, если они понадобятся. Практические методы управления памятью, предоставляемые классом System, включают в себя: checkDynamic, cleanup, dynamicGC, initMemory, staticRoom, staticSwapOff и staticSwapOn. Последние два класса обеспечивают то, что записано в их названии — выключение и включение своппин¬ га. Имеются дополнительные средства для управления своп-
Описания классов для работы с памятью 327 пингом статической памяти — при помощи использования клю¬ чевых слов SwapFlags и SwapFile в файле инициализации Windows WIN.INI. ■ КЛАССЫ ПАМЯТИ АКТОРА Двумя основными классами для управления памятью в Акторе являются MemoryObject и его подкласс Handle. Классы MemoryObject и Handle обеспечивают выделение памяти под объ¬ екты AjCTopa класса Struct из глобальной кучи Windows. В классе Struct имеются несколько методов, используемых для преобразо¬ вания объектов в двоичный формат и обратно. Это дает Актору возможность, если необходимо, интерпретировать блоки памяти как объекты. Методы класса Handle выделяют закрепляемую, перемещаемую или разделяемую память. Сюда же относится подкласс ClassDescription класса MemoryBlock, который заключает в себе ряд интересных возможностей. MemoryBlock предоставляет доступ к блокам памяти Windows размером более 64 Кбайт. Прежде всего в его реализации интерес¬ на переменная класса $LiveBlock, которая хранит дескрипторы всех своих экземплярных блоков памяти. Схема MemoryBlock в чем-то аналогична MemoryObject и Handle, скомбинированным между собой. MemoryBlock использует такие же три экземплярные переменные, как и Handle: handle, address и length. ■ ОПИСАНИЯ КЛАССОВ ДЛЯ РАБОТЫ С ПАМЯТЬЮ Класс MemoryObject Исходный файл: MEMORYOB.CLS Происходит из: Struct Имеет потомков: Handle Методы класса: new Возвращает новый экземпляр MemoryObject Методы объектов: copyFrom Обычный метод для копирования copyFromLong Копирует данные из Long в себя. Полезен для возвращения данных из Windows после того, как они переписывались в область памяти, начинающуюся с Long fill Заполняет полученный Struct-объект словами указанного вида freeHandle Освобождает блок. Предполагает, что соответствующая разблокировка уже сделана getData Возвращает данные self в форме struct
328 Вопросы проектирования в объектно-ориентированной среде Windows getText initCall isIdx keyDo longAt IP putLOng species Возвращает строку из self с удаленными пустышками Вызывает программу обработки прерывания со списком параметров, указанных в self Возвращает true — индексированную очередь инспектора Выполняет одноаргументный блок посредством ключей получателя. В случае IndexedCollection ключи являются целыми индексами коллекции и, вероятно, представляют незначительный интерес. Однако, Do сделан таким образом, что kyeDo может работать с любой коллекцией Возвращает число типа Long для данного смещения Получает длинный указатель закрывая, блокируя блок Запоминает число типа Long по данному смещению Возвращает родовой тип Класс Handle Исходный файл: HANDLE.CLS Происходит из: MemoryObject Переменные экземпляров: handle address length Методы класса: set Методы объектов: addr alloc asHandle asString asStruct Дескриптор глобальной кучи Не равен nil, если заблокирован Реальная длина выделенной памяти Создает новый экземпляр Handle с данным значением дескриптора Возвращает адрес блока. Блокирует его, если он еще не заблокирован Выделяет в глобальной памяти блок данного типа и данной длины и запоминает его информацию Возвращает дескриптор глобальной кучи и возвращает nil, если память не выделена Возвращает объект Актора, представляющий содержимое этого блока памяти в форме строки Возвращает объект Актора, представляющий содержимое этого блока памяти как struct
Описания классов для работы с памятью 329 byteAt checkHandle copyInfo fastPutWord fastWordAt fixed free handle length lock lower movable putByte putWord reAlloc setHandle shared Возвращает значение байта по указанному смещению. Временно, если блокировки еще не было, блокирует дескриптор Проверяет, что память выделена. Если нет, то возбуждается сообщение об ошибке Переписывает ByteCollection получателю. Если необходимо, корректирует длину получателя Записывает словное значение по указанному байтовому смещению. Этот метод предполагает, что память получателя в глобальной куче предварительно была заблокирована. Если это не так, резулыат будет непредсказуемым, возможно катастрофическим Возвращает словное значение из указанного индекса. Этот метод основывается на таккх же предположениях, что и предыдущий Возвращает новый закрепленный дескриптор Освобождает дескриптор, возвращает true, если освобождение выполнено Возвращает дескриптор глобальной кучи Возвращает реальную длину выделенной памяти. Если память не выделена, возвращает nil. Заметьте, что реальная длина может отличаться от той, которая запрашивалась (но никогда не меньше). Это зависит от того, как Windows выделяет память в глобальной куче Блокирует дескриптор, возвращает длинный адрес Выделяет перемещаемую память из неразмещенной части глобальной кучи Windows Возвращает новый перемещаемый дескриптор Запоминает байтовое значение по указанному байтовому смещению Запоминает словное значение по указанному байтовому смещению Повторно выделяет блок глобальной памяти, данного типа и длины и размещает в нем его информацию Устанавливает значение дескриптора блока памяти Возвращает новый дескриптор разделяемой памяти
330^ Вопросы проектирования в объектно-ориентированной среде Windows size sysPrintOn unlock MemoryBlock Исходный файл: Происходит из: Переменные класса: $LiveBlocks Переменные экземп- handle address length Методы класса: destroyAll fixed init liveBlocks lower Возвращает реальную длину выделенной памяти. То же, что для сообщения length Печатает объект Handle в указанный поток Выполняет разблокировку дескриптора, возвращает true, если счетчик ссылок достиг 0 MEMORYBL.CLS Object Дескрипторы всех выделенных на данный момент блоков шров: movable shared Методы объектов: alloc asStructAt byteAt checkHandle сору destroy fastPutWord Уничтожает все ”живые” блоки MemoryBlock Возвращает новый закрепленный дескриптор Инициализирующий метод Возвращает набор ”живых” блоков памяти Выделяет перемещаемую память из еще не распределенной части глобальной кучи Windows Возвращает новый перемещаемый дескриптор Возвращает новый дескриптор из разделяемой памяти Выделяет в глобальной памяти блок данного типа и длины и запоминает его информацию Возвращает Srtuct размера sz, содержащий данные, начинающиеся с указанного смещения Возвращает значение байта по указанному смещению. Временно, если блокировки еще не было, блокирует дескриптор Проверяет, что память выделена. Если нет, то возбуждается сообщение об ошибке Переписывает блок с переносом его хвоста в голову сегмента, если в этом имеется необходимость Разблокирует и уничтожает self и исключает дескриптор из $LiveBlock Записывает словное значение по указанному байтовому смещению. Этот метод предполагает,
Описания классов для работы с памятью 331 fastWordAt free handle length lockLock longAt putByte putLong putStructAt putWord removeRef setHandle setLength sysPrintOn unlock workAt что память получателя в глобальной куче предварительно была заблокирована. Если это не так, результат будет непредсказуемым, возможно катастрофическим Возвращает словное значение из указанного индекса. Этот метод основывается на таких же предположениях, что и предыдущий Освобождает дескриптор, возвращает true, если освобождение выполнено Возвращает дескриптор ivar Возвращает длину блока Блокирует дескриптор, возвращает длинный адрес Возвращает число типа Long для данного смещения Запоминает байтовое значение по указанному байтовому смещению Запоминает значение типа Long по указанному смещению Переписывает nBytes байтов из структуры ByteCollection по данному смещению Запоминает словное значение по указанному байтовому смещению Исключает MemoryBlock из набора LiveBlocks без освобождения памяти. Этот метод используется для блоков памяти, которые будут подаваться в буфер или из буфера clipboard Устанавливает значение собственного дескриптора. Используется только системой Устанавливает собственную длину. Используется только системой Печатает объект Handle в указанный поток Выполняет разблокировку дескриптора, возвращает true, если счетчик ссылок достиг 0 Возвращает словное значение из указанного смещения. Заметьте, что индекс является байтовым смещением, т.е. в случае (handle, 2) возвращается слово, расположенное со смещением в 2 байта. Блокирует временно дескриптор, если он еще не был заблокирован
332 Вопросы проектирования в объектно-ориентированной среде Windows Я ПРОЕКТИРОВАНИЕ ИНДЕКСНО-ПОСЛЕДОВАТЕЛЬНОЙ БАЗЫ ДАННЫХ Этот раздел посвящен другой важной сфере возможностей компь¬ ютеров — массовому хранению данных. Какие преимущества дает объектно-ориентированный подход в повышении эффективности имеющихся сред хранения данных на дисках? В действительности существует ряд преимуществ объектно-ориентированного подхода при реализации баз данных с традиционными файлами прямого доступа, близкими к индексно-последовательному типу. Для того чтобы проиллюстрировать, каковы эти преимущества и как можно проектировать перспективные системы хранения данных с их помощью, я сначала опишу расширение Актора для построения объектно-ориентированных баз данных, называемое Wintrieve. Wintrieve — это пакет для программирования баз данных под управлением MS-Windows, который поддерживает стандарт X/Open ISAM (индексно-последовательного метода доступа) для баз данных. Одним из важных достоинств Wintrieve, которое оценят многие программисты, состоит в том, что он не ограничи¬ вает количество индексов. Двумя другими привлекательными особенностями пакета Wintrieve являются поддержка транзакций и журнализация. В сочетании друг с другом эти средства помогают поддерживать целостность больших и сложных баз данных. Пакет Wintrieve включает средства для работы с языками Актор и Си. То, что из объектно-ориентированного языка типа Актор в системе Windows имеется доступ к файловой системе ISAM, является несомненным достоинством. Данные из файлов ISAM можно счи¬ тывать в предопределенные объекты Актора и писать их обратно. Теперь для разработки баз данных стали доступными основные достоинства хорошо оснащенных объектно-ориентированных сис¬ тем. Таким образом, в определенном смысле вы получаете самое лучшее из того, на что можно было бы рассчитывать. Перед описанием того, как спроектированы эти системы, было бы полез¬ но дать краткий обзор архитектуры Wintrieve. ■ WINTRIEVE - КРАТКИЙ ОБЗОР Наиболее важными классами в Wintrieve, с которыми надо позна¬ комиться в первую очередь, являются IsamFile и IsamManager. IsamFile предназначен для создания объектов, которые могут управлять отдельными файлами данных. У него имеются шесть экземплярных переменных, которые приводятся в табл. 10.1. Файл ISAM можно организовать при помощи метода create класса IsamFile, после чего дескриптор записи запоминается как строка в информационной секции файла.
Wintrieve — краткий обзор 333 Таблица 10.1. Экземплярные переменные в IsamFile filename record keys manager currentIndex block Имя файла Экземпляр IsamRecord Словарь для пар ключей keyName/key Объект IsamManager Значение keyName в текущем индексе Блок в глобальной куче, используемый в com для ISAM Server Класс RelFile предоставляет средства для связывания множества файлов данных в реляционную базу данных. По используемой схеме один из объектов RelFile выступает как владелец для других IsamFile- и RelFile-объектов, которые составляют реляционную базу данных. Метод relate класса RelFile можно использовать для построения связей между полями объекта-владельца типа RelFile и полями других файлов. Эти связи имеют смысл только для операций чтения. BF := new(BookFile); setFilename(BF, "c:\cbook\book.dat"); MasterFile := initFile(new(RelFile), BF, "book", #book); RelFile"book") DependentFile1 := initFile(new(IsamFile), DB, "aut", #author); IsamFile("author") DependentFile2 := initFile(new(IsamFile), DB, "publ", #publisher); IsamFile("author") relate(MasterFile, "publD, DependentFile2, #primary); Подчиненный (dependent) файл в объекте-владельце RelFile сам может быть RelFile-объектом, который является владельцем своих подчиненных, и т.д. Таким образом строится иерархия подчинен¬ ных файлов. Техника, основанная на чтении ISAM-данных в объекты, не безуп¬ речна. Есть ряд обстоятельств, в которых проявляются и недостат¬ ки данного подхода. В реляционной базе данных, если для каждого файла из базы данных у вас имеется отдельный класс, появляются намерения уклониться от инкапсуляции, когда она начинает работать против вас, поскольку объекты одного класса, возможно, не имеют непосредственного доступа к данным объектов других классов. Конечно, имеется ряд способов обойти подобные трудно¬ сти. При использовании интерфейса Си++ для Wintrieve классы могут определяться как классы-друзья. В Акторе классы для файлов данных могли бы быть потомками общего суперкласса, который давал бы полный дескриптор данных для всех полей записей различных файлов. Как обычные потомки этого общего для них класса классы файлов данных имели бы доступ к любым
334 Вопросы проектирования в объектно-ориентированной среде Windows данным. Другой вариант состоит в объединении объектов различ¬ ных файлов данных в один объект-коллекцию. ■ ОБЪЕКТНО-ОРИЕНТИРОВАННЫЕ СИСТЕМЫ РЕЛЯЦИОННЫХ БАЗ ДАННЫХ Объектно-ориентированные системы реляционных баз данных — это специально разработанные приложения, обладающие всеми достоинствами традиционных реляционных баз данных и в то же время предоставляющие привлекательные возможности, которые может дать последовательно вы держанное модульное проектиро¬ вание как в сфере обеспечения доступа, так и в сфере представле¬ ния данных. Одной из наиболее привлекательных особенностей таких систем является то, что объектно-ориентированная система может подпи¬ тываться реляционной базой данных так, что конечный пользова¬ тель даже не будет замечать этого. Объектно-ориентированный подход весьма удобен для создания внешних оболочек (front end) реляционных баз данных, которые скрывают внутренние механиз¬ мы файловой системы и обладают чрезвычайной гибкостью в средствах представления данных для пользователя. В последую¬ щих разделах я опишу архитектуру Wintrieve — объектно-ориен¬ тированной системы для построения реляционных баз данных, построенной как расширение языка Актор. ■ РАЗРАБОТКА ОБЪЕКТНО-ОРИЕНТИРОВАННЫХ БАЗ ДАННЫХ ПРИ ПОМОЩИ WINTRIEVE Как вы уже видели, в Wintrieve при помощи класса RelFile и метода relate для создания связей можно построить полную реля¬ ционную базу данных. В традиционных базах данных наборы файлов данных используются для представления больших связан¬ ных массивов информации, например о различных видах деятель¬ ности в бизнесе. Всегда имеется некоторое поле данных, общее для двух файлов. В идеальном случае каждый файл должен иметь хотя бы один другой файл с полем из общей области. Хотя система файлов данных Wintrieve сама по себе не является иерархической базой данных, но настоящая объектно-ориентиро¬ ванная система всегда может стать таковой, поэтому Wintrieve может быть использована для построения иерархической базы данных. Иерархическая база данных — это такая база данных, которая поддерживает понятийные взаимосвязи между файлами данных, а также перекрытия полей, реализующих связи-отноше¬ ния. Например, все файлы данных в иерархической базе данных, которые, скажем, относятся к средствам передвижения некоторого вида, могли бы иметь некоторую понятийную взаимосвязь неза¬
Классы Wintrieve 335 висимо от того, связаны или не связаны они связью-отношением. Экземплярные переменные объектов подобны полям в иерархиче ской базе данных. Однако в системах типа Wintrieve обычно не обеспечивается произвольный доступ к объектам. Несмотря на то, что реляционные и иерархические базы данных определенно различаются, есть возможность имитировать иерар¬ хические базы данных при помощи реляционных. Очевидный метод здесь состоит в том, чтобы включить в каждый файл данных такие поля, как: InheritsFrom, InheritedBy и InstanceOf (наследу- ет_из, имеет_наследников и является_экземпляром). Это были бы ’’мета-поля” файлов данных, в которых хранилась бы скорее информация о понятийной структуре самой файловой системы, а не содержащаяся в ней информация из реального мира. По аналогии с имитацией иерархической базы данных реальную иерархию объектов можно было бы построить из классов системы объектов. В конце концов для каждой записи базы данных может быть создан объект, но для большинства приложений это могло бы показаться расточительной тратой ресурсов — хранить объект, пока в нем не отпадет необходимость. Поскольку объекты созда¬ ются, они могли бы организовать чтение соответствующих записей в наборы своих переменных. В идеале подобная система могла бы функционировать так, как будто для иерархической базы данных обеспечивается произвольный доступ. В действительности, здесь потребовались бы некоторые компромиссы, обусловленные соотно¬ шением между существующими требованиями приложений и тем, что можно реализовать на практике. ■ КЛАССЫ WINTRIEVE Object Collection IndexedCollection ByteCollection Struct MemoryObject Handle KeyedCollection Dictionary OrderedDictionary CStruct IsamCStruct SubStruct CType IsamRecord UserType FieldInfo
336 Вопросы проектирования в объектно-ориентированной среде Windows IsamDesc IsamDict IsamKey IsamKeyParser IsamFile RelFile IsamManager WindowsObject Window IsamLink lsamCStruct IsamCStruct подобен объектам CStruct, но его данными служит объект Handle. Память последнего выделяется из неразмещенной (нижней) части глобальной кучи. lsamRecord Объект lsamRecord также является специальным видом CStruct, который определяет внутреннее строение записей для доступа к файлам Isam Актора. IsamDict Класс IsamDict предоставляет протокол для доступа и работы со структурами словаря. IsamKey Класс IsamKey обеспечивает протокол для создания и поиска ключей индекса Isam. lsamLink Класс lsamLink предоставляет протокол для управления подклю¬ чением сервера ISAM Server. Использование объекта lsamLink обычно требуется в начале сеанса, при посылке требований и завершении сеанса с использованием ISAM Server. lsamLink про¬ исходит из класса Window, поскольку связь между приложениями в MS-Windows в действительности выполняется через окна. Де¬ скрипторы окон используются для того, чтобы уникально иденти¬ фицировать сообщающиеся между собой абоненты. Если вам потребуется для обращения к ISAM Server узнать, как должно заполняться то или иное поле в queryBlock, обращайтесь к руко¬ водству ”ISAM Server Protocol Specification”. IsamFile Класс IsamFile предоставляет протокол для управление отдельны¬ ми файлами ISAM.
Базисные классы, используемые в Wintrieve 337 RelFile Класс RelFile предоставляет протокол для связывания полей за¬ писи родительского RelFile-объекта с ключевыми полями подчи¬ ненного IsamFile-класса или его подчиненными объектами. lsamManager Класс lsamManager предоставляет базисный интерфейс сервера Isam Server. Все обращения к ISAM Server осуществляются через объект lsamManager. lsamManager предоставляет протокол для управления сеансом, транзакциями, журнализацией и доступом к файлам. lsamDesc Класс lsamDesc — класс поддержки, который предоставляет про¬ токол для управления дескрипторами записей ISAM. lsamKeyParser IsamKeyParser — это класс поддержки для класса IsamKey. Он предоставляет протокол для разбора ключевых атрибутов и клю¬ чевых разделов дескрипторов. ■ БАЗИСНЫЕ КЛАССЫ, ИСПОЛЬЗУЕМЫЕ В WINTRIEVE CStruct — это класс для поддержки обращений к структурам языка Си. CStruct содержит две экземплярные переменные: сло¬ варь характеристик полей и двоичные данные. CStruct полезен везде, где требуется переход от объектов Актора к двоичным данным и обратно, как, например, при взаимодействии с ОС, файлами на дисках или средствами управления окнами. Для реализации своих объектов CStruct использует еще четыре класса: FieldInfo, CType, UserType и SubStruct. Класс FieldInfo хранит информацию о полях в объектах со Си- структурой. Объекты CType описывают типы языка Си для Си- структур и организуют трансляцию из формата Актора в двоичный формат Struct. Экземпляр класса CType необходим для каждого типа данных в языке Си, который будет использоваться как поле а CStruct-объектах. Файл CTYPES.ACT содержит некоторые про¬ стые определения CType. Класс UserType поддерживает типы, определяемые пользователем для структур Актора CStruct. Класс Substruct предоставляет объекты CStruct, которые являются вло¬ женными полями других структур. Объекты Struct имеют фиксированный размер, они являются индексированными коллекциями слов (два байта) или длинных слов (четыре байта). Эти объекты полезны для обмена информа¬ цией с MS-Windows и другими языками программирования, Все классы геометрических объектов в Акторе, за исключением Point, являются потомками Struct. К данным, помещающимся внутри
338 Вопросы проектирования в объектно-ориентированной среде Windows объектов Struct, доступ осуществляется посредством указания смещения в байтах, например, ”слово, расположенное в байте со смещением 3”. Класс MemoryObject является абстрактным роди¬ тельским классом для описания блоков ”неакторовской” памяти. Класс Handle описывает объекты памяти, выделенные системой управления глобальной памятью. ■ ОПИСАНИЯ КЛАССА WINTRIEVE OrderedDictionary /* Упорядоченный словарь */ Исходный файл: ORDEREDD.CLS Наследует из: Dictionary Переменные экземпляров: orderKeys Коллекция для хранения ключей в упорядоченном виде Методы объектов: addAssoc assocDo do grow init keys keysDo Добавляет связь (Association) в словарь Находит блок для каждой полученной связи Перенумеровывает все элементы в OrderedCollection Копирует элементы в коллекцию большего размера и помещает ее на место старой коллекции Инициализирует KeyedCollection, устанавливая в экземплярную переменную tally 0 Возвращает ключи в правильном порядке Находит один аргументный блок через ключи в словаре Заменяет текущий элемент или создает новый. Для этого класса метод put во всем, кроме порядка аргументов, идентичен методу add Удаляет из словаря элемент с указанным ключом. Если для указанного ключа aKey соответствующего элемента нет, то выполняется О-аргументный блок. Данный метод возвращает удаленный ключ или значение блока setCompareBlock Выполняет пересортировку в соответствии с newCompareBlock и возвращает self setOrderClass Задает класс для упорядочивания ключей put removeUsing ■ ОПИСАНИЯ КЛАССА SAMPLE BookWindow Исходный файл: BOOKWIND.CLS
Описания класса Sample 339 Наследует из: TextWindow Переменные экземпляров: keyDict bookDB bookTable Методы объектов: changeIndex closeDB ommand createMenu deleteRec getISBN getTitle init insertRec nextRee openDB prevRec printRecord recDlg searchRec shouldClose updateRec validateInput Словарь ключей Система управления ISAM (ISAM manager) book file для ISAM В выбранном индексе выполняет сортировку в соответствии с новым порядком и выдает первую запись Закрывает базу данных Обрабатывает события меню. Аргумент wp задает выбранное ID для меню. Берет символ сообщения из объекта-меню Задает полоску меню Уничтожает текущую запись Организует обработку ввода в диалоге для получения номера ISBN. Возвращает номер ISBN или nil, если пользователь ответил отказом Организует обработку ввода в диалоге для получения заголовка. Возвращает заголовок или nil, если пользователь ответил отказом Инициализирует Book Window. Создает меню и ”About” для управления меню Вставляет запись Читает следующую запись и выдает ее Начинает сеанс с ISAM manager Читает предыдущую запись и выдает ее Выдает текущую запись в окно Строит диалог для записей Выполняет поиск записи, используя имеющийся порядок в индексе Закрывая окно закрывает и базу данных Модифицирует запись Проверяет ввод от диалога записи. Если все в порядке, то возвращает словарь значений полей ввода. Если в одном из полей имеется ошибка, то выдает окно ошибок и затем возвращает nil BookDlg Исходный файл: Наследует из: BOOKDLG.CLS Window Переменные экземпляров: isbn Элемент управления для поля редактирования title Элемент управления для поля редактирования
340 Вопросы проектирования в объектно-ориентированной среде Windows pubID quantity price isbnVal titleVal pubIDVal quantityVal priceVal ok bOK bCancel bookFile op Методы объектов: command initButtons initEdits isbn price pubID quantity setIsbn setPrice setPubID setQuantity setTitle shouldClose title # Элемент управления для поля редактирования Элемент управления для поля редактирования Элемент управления для поля редактирования Значение поля ввода Значение поля ввода Значение поля ввода Значение поля ввода Значение поля ввода Флажок для информирования о согласии пользователя Элемент управления — кнопка OK (о’кеу) Элемент управления — кнопка Cancel (отказ) Управляет командными событиями. Проверяет, какая кнопка — theOK или CANCEL — нажата, и предпринимает соответствующие действия. Если это OK, то читает значения из полей ввода Создает кнопки OK и CANCEL Создает поля ввода Возвращает значение поля isbn Возвращает значение поля price Возвращает значение поля pubID Возвращает значение поля quantity Задает выдаваемое значение поля ISBN Задает выдаваемое значение поля price Задает выдаваемое значение поля pubID Задает выдаваемое значение поля quantity Задает выдаваемое значение поля title Посылает обратно к родителю сообщение о том, что получатель собирается закрываться Возвращает значение поля title
■ УКАЗАТЕЛЬ Accessories, группа 10 ActorApp, класс 180, 196 Always Warn, опция 19 AppView, класс 85, 86 Arrange All, опция 22 Array, класс 66, 67, 147, 148 AsciiFiler, класс 66, 67 AVL, класс 67 BalNode, класс 66, 67, 69 Buffer, класс 79 С, язык 44, 52, 55, 75—77, 117, 141, 156, 275, 324, 332 С++, язык 52—64, 314, 333 CaJculator, утилита 13—15 Caiendar, утилита 12, 14 Cardfile, утилита 14 Codeview, отладчик 114 Collapse branch, опция 11 Collection, класс 68, 79, 146, 147 Container, класс 79, 146 Control Panel, утилитаЮ, 12, 18 Ctalk, язык 6, 75—84, 89, 91 Detect Idle Time, опция 20, 21 EMM386.SYS, драйвер памяти 15 Exeel, электронная таблица 5, 8, 15, 16, 24, 27, 34 Exclusive in Foreground, опция 19 ExecWindow, функция 193—196 Expand All, опция 11 Expand Branches, опция 11 Expand One Level, опция 11 File Manager, утилита 9, 10—12 Games, группа 10 Heapwalker, отладочное средство 112, 117, 325 HIMEM.SYS, драйвер памяти 15 Hyperscript, язык 29, 30 Idle, опция 19 Inspector, отладочное средство 133 Macro Recorder, утилита 12, 17 Main, группа 10 Minimum Time-Slice, опция 19 Newer Warn, опция 19 NewWave, среда 22—24, 312, 313 Non-Windows Applications 10 Notifier, класс 85, 86 Objective-C, язык 6, 64—75 Paintbrush, графическая утилита 12, 15, 16 Print Manager, утилита 9, 10, 16, 17, 18 PrintScreen, ’’горячая” клавиша 35 Program Manager, утилита 9, 10, 35 Run, команда 10 SDK Paint, утилита 109 Setup, программа задания конфигурации 16 Shaker, отладочное средство 113—114 Software Development Kit (SDK), пакет разработчика 107—118, 323 Spy, отладочное средство 111, 117 Stream, класс 79 Swap, отладочное средство 113 Switch to, опция 10 Symdeb, отладчик 114 Toolbook, среда 24—28 VGA, видеоадаптер 13 Video New Wave, программа 24 VIEWS, среда программирования 6, 314—315 Wingz, электронная таблица 28—35 WIN.INI, инициализационный файл 7, U WINMEM32.DLL, библиотека работы с памятью 323—324 Word for Windows, текстовый процессор 8, 15, 21, 22
342 Указатель Zoomin, утилита 110, 111 Zortech С++, компилятор 60—62 Zortech Tools, набор инструментов 63 Активные данные (Active Data) 41 Актор (Actor) 6, 71, 74, 130—171 Анимация 25, 26, 231—232 Битовая карта 226, 250 Броузер 46, 51 Виртуальные классы 57—58 Виртуальные функции 57—58 Дата 159—164 Деструктор 58—59 Диалоговое окно 101—102 Динамическийдиалог 138, 187—189 Динамическое меню 138, 186—187 Динамический обмен данными (Dynamic Data Exchange DDE) 16, 26, 106 Динамически подключаемая библиотека (Dynamic Link Library DLL) 27, 35, 106, 323—324 Иерархия классов 43, 66—68 Инициализационный файл 11, 19—20 Инкапсуляция 44, 45, 54 Интерфейс с графическим устройством (Graphics Device Interface GDI) 175 Интерфейс с многими документами (Multiple Document Interface MDI) 274—279 Информационный контекст (Information context) 104 Карман (Clipboard) 10, 12—13, 16, 35—36 Класс 42—49, 52—53 Коллекция 69—71 Командная строка DOS (DOS Prompt) 10 Комбинированное окошко (Combo box) 102—103, 190—191 Компилятор ресурсов (Resource compiler) 107—108 Конструктор 58—59 Контейнер 23 Контекст устройства (device context) 104 Линия прокрутки (scrollbar) 9, 101 Меню 88—89, 100, 138—139, 186—187, 280—282 Метакласс 44 Метафайл 104—105 Метод 43 Многозадачность 9—10 Наследование (Inheritance) 42 Объектно-ориентированная графика 217—272 Окошко списков (list box) 102, 189—190 Отладка 61—63, 108, 111 Палитры 260 Перегрузка (Overloading) 43, 55—57 Переключение задач (Task switch) 35 Переменные классов 45, 151 Перехват экрана (Screen capture) 13, 16 Пиктограммы 253 Подменю (Popup menus) 274 Порт ввода-вывода 19 Принтер 12, 24 Процессор заголовков (Outline processor) 22 Профайлер 110, 111 Реальный режим (Real mode) 9 Редактор диалогов (Dialog Editor) 93, 109 пиктограмм (Icon Editor) 15 шрифтов (Font editor) 18, 93, 110 Смолток (SmallTalk) 44, 46, 51, 55, 65, 66, 68, 71, 73, 74, 75, 76, 140, 141, 143, 275 Создание экземпляров 42—44 Сообщение 96—97 Список задач (Task List) 9 Стандартный режим (Standard mode) 9 Стиль интерфейса пользователя 116 Суперкласс 42, 43, 48 Фон экрана 36 Функции Windows 94—98, 118—125 Часы (Clock) 12, 164—166 Усовершенствованный 386 режим (386 Enchanced mode) 9, 13, 323 Управление памятью (Memory managment) 7, 21, 305, 320—331 Управляющие элементы (Controls) 99, 100, 137—138 Электронная кнопка (Button) 101, 182—184, 315 Электронная таблица (Spreadsheet) 310—311
■ ОГЛАВЛЕНИЕ Предисловие. Зачем нужно объектно-ориентированное программирование для Windows? 5 Глава 1. Введение в Windows 3.0 8 Что нового в версии 3.0 9 Многозадачность — Менеджер программ (Program Manager) 10 Основная группа (Main Group) — Менеджер файлов CFtie Manager) — Карман (Clipboard) 12 Перехват экрана 13 Калькулятор — Работа с числовыми данными 15 Запись макро СМясго Recorder) 17 Управление печатью (Printer Manager) — Панель управления (Control Panel) 18 Управление усовершенствованным 386 режимом — Управление многозадачностью 19 Инициализационные файлы — Редактор .РН*1 файлов СРП? Editor) 20 Клавиши активизации, определяемые прикладной программой 21 Управление памятью — Microsoft Word for Windows — Работа с заголовками 22 NewWave * — Office Tools 23 Toolbook 24 Wingz: электронная таблица нового поколения 28 Сеанс работы с Windows 3.0 35 Глава 2. Что такое объектноориентированное программирование? 37 Введение — Парадигмы программирования — Объектноориентированный подход 38 Метафоры объектно-ориентированного программирования 40 Активные данные 41 Передача сообщений — Классы: порождение экземпляров и наследование 42 Типы объектноориентированных систем 44 Как работают объектноориентированные системы? 45 Использование объектноориентированных систем 46 Почему применяют объектноориентированное программирование? . . . — Некоторые выводы 47 Дополнительные заботы программиста 49 Глава 3. Объектноориентированные расширения Снс языки и инструментальные средства 52 Обзор средств Си++ 2.0 — Структуры и классы 53 Множественное наследование * 54 Функции-члены — Регулируемая степень инкапсуляции — Функциц-друзья — Перегрузка операторов и функций 55 Прототипы 56 Виртуальные функции и классы 57 Конструкторы и деструкторы 58
344 Оглавление Компилятор фирмы Zortech 60 Модели памяти — Карта памяти программ 61 Отладчик для Си++ — Классы библиотеки Zortech Tools 63 Заключительные замечания о Си++ — Язык Objective-C 64 Иерархия классов 66 Коллекции 69 Графика 71 Символьная отладка 73 Обсуждение — CtaUc 75 Синтаксис CtaUc 77 Базовые классы Ctalk 78 Создание исполняемых программ 81 Заключительные замечания о CtaDc — VIEWS: объектно-ориентированный инструмент разработки Си-программ для Windows 82 Броузер Си++ системы VD3WS 83 Модель MVC пользовательского интерфейса 84 Класс Appview 85 Назначение окон в системе VLEWS 86 Класс View 87 Создание диалогов с помощью генераторов интерфейсов 88 Создание меню — Классы редакторов текстов 89 Таймер 90 Коммуникационный класс — Графика — Ускорители приложений . . . . 91 Заключительные замечания о VffiWS . . . . — Глава 4. Разработка программ для Windows 3.0 92 Что нового появилось в SDK Windows 3.0? : — Как работает система Windows 93 Функции, которые создают объекты 94 Классы окон . . — Сообщения 96 Функции окон 97 Контекст дисплея 98 Элементы оконных пользовательских интерфейсов — Управляющие элементы, перерисовываемые владельцем 99 Пиктограммы — Меню . . . 100 Управляющие элементы — Кнопки 101 ScroU Bars (линии прокрутки) — Диалоги — Окошки списков 102 Комбинированные окошки — Всплывающие меню 103 Интерфейс с графическим устройством — Интерфейс со многими документами C^DI — Multi-Document biterface) 105 Динамически подключаемые библиотеки (DLL — Dynamic Link Libraries) 106 Динамический обмен данными (DDE — Dynamic Data Exchange) . . . . — Машинно-независимая цветная графика 107 Система построения справочной информации (Help) — Компилятор ресурсов 107
Оглавление 345 Редакторы ресурсов и инструменты 108 Редактор диалогов 109 Утилита SDKPaint — Редактор шрифтов FontEdit 110 Лупа — Профайлер — Spy (мониторинг сообщений) 111 HeapwaUcer, (мониторинг кучи) 112 Swap (обмен) 113 Shaker (миксер для кучи) — CodeView for Windows 114 Отладчик Symdeb для реального режима — Обеспечение совместимости между версиями 2.0 и 3.0 115 Структура прикладных программ для Windows — Стиль пользовательского интерфейса Windows 116 Описания ресурсов — Заключение — Управляющие сообщения редактирования 118 Новые функции версии 3.0 — Новые сообщения Windows 3.0 125 Новые структуры данных Windows 3.0 129 Глава 5. Введение в систему программирования Актор 130 Язык Актор, версия 3.0 . — Среда программирования Актор — Броузер Актора 131 Инспекторы 133 Классы Актора 134 Классы MS-Windows — Элементы управления 137 Динамические меню и диалоги 138 Callbacks . . . . .^ 139 Списковые структуры — Программирование на Акторе 140 Основы синтаксиса Актора — Немного музыки? 143 Программные блоки — Форматы файлов — Тренировки в языке Актор — Сеанс с классом Number . . . 144 Строки . . . . 145 Структуры управления — Циклы — Коллекции 146 Массивы 147 Другие коллекции 148 Упорядоченные коллекции 149 Словари — Текстовые коллекции 150 Очереди — Переменные класса . 151 Вызов библиотек с динамическим связыванием 152 Представление знаний — Отладка 154 Профайлер — Выполнение внешних программ — ’’Распечатывание” приложений 155 Класс Application — Добавление примитивов в Актор 156 Измерение времени 158 Работа с датами и временем 159
346 Оглавление Что такое время? 164 Цифровые часы — Описания классов для работы со временем 166 Дополнительный пример программ 169 Глава 6. Объектноориентированный пользовательский интерфейс для Windows 172 Создание окон — Текстовые окна 175 EditWindows 177 FileEditor 178 Описания класса — Класс Actorapp 180 Клавиши, диалоги и другие элементы управления 182 Создание кнопок — Создание файловых диалогов 184 Рабочие области 185 Класс TextfiIe — Создание динамических меню 186 Создание динамических диалогов 187 Внережимные диалоги 188 Обычный динамический диалог 189 Создание списковых окон — Создание комбинированных окошек 190 Создание полосок меню главного окна 191 Простой контроллер исполнения 193 Некоторые извлечения из примера ресурсного файла 196 Директории Dos 200 Глава 7. Объектноориентированное графическое программирование для Windows 217 Встроенные графические классы — Класс Point 218 Scribble (Каракули) — Описание класса Scribble 220 Прямоугольники — Прямоугольники с закругленными краями 222 Эллипсы — Многоугольники — Цветные битовые карты 226 Трехмерная точка 227 Диаграммы 228 Анимация 231 Некоторые итоги 232 Описания графических классов 233 Глава 8. Программирование при помощи ObjectGraphics 241 Обзор ObjectGraphics 242 Демонстрационный пример SampleDraw — Добавление меню и новых форм в SampleDraw 243 ObjectDraw 245 Расширенный Актор 248 Фильтры платформ 249 Класс Color 250 Битовые карты — Прямоугольники — Формы 251 Графические пространства 252 Пиктограммы ^ 253 Полиформы и ломаные линии 254 Кривые — Рисование треугольников 255 Рисунки (Pictures) 258
Оглавление 347 Области (Region) 259 Палитры 260 Группировки — Лрименение инструментов формирования изображения 262 Полимарки 266 Иерархия класса ObjectGraphics 268 Расширения класса Window в ObjectGraphics 270 Глава 9. Пример приложения в Windows 3.0 273 Краткий обзор Executive Control — Мультидокументный интерфейс (MDI) 275 MDI-поддержка в Акторе — MDIFrameWindow 276 MDIFileWindow 277 Иерархия классов MDI 278 Управление большой и сложной системой меню 280 Иерархические меню — Комбинирование статических и динамических меню 281 Динамические файловые диалоги 282 Факторизация командных методов — Глава 10. Вопросы проектирования в объектно-ориентированной среде Windows 305 Дизайн для пользователей — Что же реально делает компьютер? 307 Разделение процедур и протоколов — Проектирование классов 308 Экземпляры дублирующихся приложений 309 Объектно-ориентированное проектирование графического пользовательского интерфейса — Пример проектирования объектно-ориентированной электронной таблицы 310 Открытые цепочки действий 311 Факторизация командных методов 313 Объектно-ориентированное проектирование для MS-Windows — Иерархические меню 315 Управление большой и сложной системой меню 316 CommonView — Классы для изображений в C+4- 317 Классы CommonView 318 Проектирование управления памятью в Windows 320 Типы хранения данных 321 Сбрасываемая память 322 Управление памятью повышенной эффективности — Стандартный режим — Усовершенствованный режим 386-го процессора 323 Несколько правил ’’хорошего тона” — оиблиотека WINMEM32.DLL — Управление памятью в Акторе 324 Своппинг статической памяти 326 Классы памяти Актора 327 Описания классов для работы с памятью — Проектирование индексно-последовательной базы данных 332 Wintrieve — краткий обзор — Объектно-ориентированные системы реляционных баз данных . . . . 334 Разработка объектно-ориентированных баз данных при помощи Wintrieve — Классы Wintrieve 335 Базисные классы, используемые в Wintrieve 337 Описания класса Wintrieve 338 Описания класса Sample — Указатель 341
Научное издание Эрнест Р. Телло Объектно-ориентированное программирование в среде Windows Рецензенты: И. С. Кондратьев, В. А. Семичев Ответственный за выпуск: К. В. Коробов Редактор: JI. В. Речицкая Технический редактор: А. А. Кондратьева Дизайн переплета, рисунки: И. С. Кондратьев Оригинал-макет книги подготовлен в системе Ventura Publisher 2.0 А. А Кондратьевой Подписано в печать 09.03.93 г. Формат 60x88/16. Бумага офс. № 2. Гарнитура школьная. Печать офсетная. Усл. печ. л. 21,56. Усл. кр.-отт. 21,56. Уч.-изд. л. 19,26. Тираж 20 000 экз. Заказ № 857. Издательство «Высшая школа» 101430, Москва, Неглинная, 29/14 Издательство Наука-Уайли 117864, Москва, ул. Профсоюзная, 90 Акционерное общество AKME 101000, Москва, ул. Чернышевского, 7 Набрано на персональных компьютерах фирмы AKME Отпечатано Московской типографией № 8 Министерства печати и информации РФ 101898, Москва, Центр, Хохловский пер., 7
Акционерное общество AKME предлагает полный спектр издательских услуг: - разработку фирменного стиля, логотипов эмблем, товарных зна¬ ков, макетов рекламы, буклетов, бланочной продукции; - компьютерный набор текстов, верстку и изготовление оригинал- макетов изданий любой сложности, в том числе c иллюстрациями и компьютерной графикой; - изготовление макетов акций, сертификатов акций, депозитных и сберегательных сертификатов, облигаций, векселей, других ценных бумаг c использованием графики высокой сложности; - полиграфические услуги; - изготовление рекламной видеопродукции средствами компьютер¬ ной графики; - консультации, подготовку договоров и ведение переговоров о при- обретении авторских прав на зарубежные издания, а также представительство ваших интересов при уступке авторских прав на издания и художественное оформление к ним; - содействие в организации компьютерных издательских систем, широкий выбор профессиональных шрифтов и шрифтовых спецэф¬ фектов. 101000 Москва, ул. Чернышевского, 7 Телефоны: (095)162-9751 (095)287-2685 Тел./факс: (095)286-3463 E-maii (RELCOM/INTERNET): koko@akme msk.su
demos demos + Aps сом - дилер нр Официальный дилер Hewlett Packard предлагает компьютеры, лазерные принтеры, сканеры, плоттеры и другое оборудование фирмы со скидкой! 0 486DX-33/4-32Mb RAM/100-660Mb HDD/SVGA... 0 NoteBook-386SX-25/2-5Mb/40-80Mb/VGA/2,3 кг. 0 Портативный струйный принтер BJ-10 EX, вес - 1,8 кг, лазерное качество, русские шрифты, аккумулятор. 0 Телефонная станция для офиса 6/16, подключение телефон¬ ных аппаратов любых типов. 0 Модем Discovery 2400CM/D MNP-5, 2400 bps, аттестат Мин¬ связи РФ. Гарантия - 2 года. 0 Модемы и факс-модемы фирмы Datatronics. 0 Высокоскоростные модемы: Telebit Т-3500 70000 bps/V32b/ V42b/Turbo PEP; ZyXel U1496S/E до 57600 bps/V23/V33/ V32b/V42b/V27ter/V29/Vl7/G3 Fax... demos 113035 Москва, Овчинниковская наб., 6 Телефоны: (095) 231-6395, 233-0670 Fax: E-mail: (095) 233-5016 info@hq.demos.su
119136 г.Москва, а/я 20 тел.: 142-77-12
Акционерное общество АЙНА предлагает: - руководителям большого и малого бизнеса.желающим всегда иметь под рукой оперативную аналитичес- куюинформациюофинансахпредприятияигибкуюсистемусоставлениябухгалтерскойотчетности; пред- приятиямвсехформсобственности.задачиучетанакоторыхдостаточнотрудоемки; главнымбухгалтерам, желающим иметь простую в эксплуатации, не требующую специальных знаний в области компьютеров, надежную в отношении защиты данных и “прозрачную” при аналитической работе Информационно-анали- тическую систему (ИАС) CORPAC - Corporate Accounting. Специалисты А/0 “Айна" имеют большой практический опыт в области автоматизации бухгалтерских задач, прекрасно владеют не только языками программирования и базами данных, но и технологией обра¬ ботки бухгалтерских документов. В состав системы CORPAC входятследующие модули: - SALARY*-pac4eT заработной платы; - ACCOUNTS - бухгалтерский учет: - учет кассовых и банковских операций; - учет движения по счетам (оперативная информация о состоянии составления счетов бухгалтерского баланса); - учет валютных поступлений (с оформлением необходимых документов); - учет основных средств; - учет малоценных и быстроизнашивающихся материалов; - учет материальных ценностей на складе; - автоматизированная выписка и контроль исполнения платежных поручений; - составление сводного баланса. Модули системы CORPAC поставляются в любой необходимой комплектации с гибкой настройкой на специ¬ фику решаемых задач по ценам, дифференцированным в зависимости от комплектации. Стоимость системы CORPAC в полтора-два раза ниже аналогичных продуктов других фирм и приемлема даже для предприятий малого бизнеса. Специалисты А/О “Айна” готовы не только поставить указанные комплексы программ, но и квалифициро¬ ванно обучить Ваш персонал работе с персональным компьютером и прикладными программами. АЮ “Айна” имеет значительный практический опытв деле внедрения автоматизацииучета на предпри¬ ятиях сферы услуг, в частности - в задачах автоматизации учета разветвленных гостиничных комплексов. В случае отсутствия у Вас вычислительной техники, мы готовы поставить Вам необходимый комплекс технических средств и выполнить работы по созданиюлокальной сети “под ключ”. А/О “Айна” готово также предложить сервисное и техническое обслуживание всего комплекса технических средств. Если Вас заинтересовали наши предложения, будем рады сотрудничать с Вами. Контактные телефоны: (095)427-3911 с 9-00 до 18-00 (095)213-8588 после19-00 Факс: (095)339-5911 (для А/0 “Айна”) Ранняяверсия системы SALARYтестировалась в софтлабораториижурнала *КОМПЬЮТЕР”и описача sN2(5) за 1991 г. на с. 47-48.