Текст
                    Юрий Щупак

МЛп32 АР1

Разработка приложений
ДЛЯ ТУ|П(10И/8

Н Базовые концепции

± Многозадачность

Н Графический интерфейс пользователя

Т Библиотеки динамической компоновки

ПИТЕР*

БИБЛИОТЕКА ПРОГРАММИСТА Юрий Щупак 1№п32 АР1 Разработка приложений для Мтбошз С^ППТЕР Москва - Санкт-Петербург - Нижний Новгород - Воронеж Ростов-на-Дону - Екатеринбург - Самара - Новосибирск Киев - Харьков - Минск 2008
Щупак Юрий Абрамович У\Лп32 АР1. Разработка приложений для ОТтс1ош5 Серия «Библиотека программиста» Заведующий редакцией Руководитель проекта Ведущий редактор Корректор Верстка А. Сандрыкин П. Маннинен Ю. Сергиенко Е. Каюрова С Раманов ББК 32.973.2-018.2 УДК 004.451 Щупак Ю. А. Щ95 №т32 АР1. Разработка приложений для УУ!гк1о^лга. — СПб.: Питер, 2008. — 592 с.: ил. 18ВМ 978-5-388-00301-0 В этой книге изложены основные концепции и приемы программирования для на языке С/С++ с применением ХУт32 АР! Книга ориентирована на широкий круг читателей: аг начинающих программистов, студентов ву- зов, аспирантов и преподавателей до профессионалов в области программирования, владеющих язы- ком С++, но не имеющих опыта разработки приложении для У/тс1оу/8. Практика показывает, что если программисты начинают разработку проектов сразу с применением библиотек классов, подобных МЕС или ТОпбош Гогтв, не имея при этом опыта работы с ЧУш32 АР! то они сталкиваются с серьезными проблемами, как только дело доходит до создания реальных при- ложении. Напротив, опыт программирования с \Ут32 АР1 позволяет осваивать более высокие техно- логии гораздо гармоничней и продуктивней. Владение базовыми знаниями \Ут32 АР1 является надежной основой для вашего профессиональ- ного роста в сфере программирования. © ООО «Питер Пресс», 2008 Все права защищены. Никакая часть данной книги не может быть воспроизведена в какой бы то ни было форме без письменного разрешения владельцев авторских прав. Информация, содержащаяся в данной книге, получена из источников, рассматриваемых издательством как на- дежные. Тем не менее, имея в виду возможные человеческие или технические ошибки, издательство не может гарантировать абсолютную точность и полноту приводимых сведений и не несет ответственности за возмож- ные ошибки, связанные с использованием книги. 13ВЫ 978-5-388-00301-0 ООО «Питер Пресс», 198206, Санкт-Петербург, Петергофское шоссе, д. 73, лит. А29. Налоговая льгота — общероссийский классификатор продукции ОК 005-93, том 2; 95 3005 — литература учебная. Подписано в печать 28.05.08. Формат 70x100/16. Усл. п. л. 47,73. Тираж 2500. Заказ Отпечатано по технологии С!Р в ОАО «Печатный двор» им. А. М. Горького. 197110, Санкт-Петербург, Чкаловский пр., 15.
Краткое содержание Предисловие.........................................13 Глава 1. «Не11о, ЛЛЛогШ!», или Первые шаги к пониманию основных концепций УУтЛоотз............16 Глава 2. 601 — графический интерфейс устройства. Рисование линий, фигур, текста.........58 Глава 3. 601. Палитры, растры, метафайлы...........159 Глава 4. Средства ввода............................213 Глава 5. Ресурсы УУтс1оуу5-приложения..............242 Глава 6. Меню и быстрые клавиши....................272 Глава 7. Диалоговые окна...........................304 Глава 8. Элементы управления общего пользования...379 Глава 9. Многозадачность...........................441 Глава 10. Таймеры и время..........................485 Глава 11. Библиотеки динамической компоновки 01_1_.515 Глава 12. Специальные приложения...................539 Приложение 1. Интегрированная среда УЕзиа! С++ 6.0.563 Приложение 2. Интегрированная среда У!зиа1 51ис1ю.№Т.. 577 Приложение 3. Работа с утилитой 5ру++..............584 Литература.........................................587 Алфавитный указатель...............................588
Содержание Предисловие......................................................13 Кому адресована эта книга........................................14 Как работать с книгой............................................14 От издательства..................................................15 Глава 1. «Не11о, ЛЛЛог1с1!», или Первые шаги к пониманию основных концепций УУ!пс1ош5..................16 Базовые концепции................................................16 Графический интерфейс пользователя............................16 Многозадачность...............................................17 Управление памятью............................................18 Независимость от аппаратных средств...........................19 Вызовы функций и ОН...........................................19 Объектно-ориентированное программирование.....................20 Типы данных \ЛЛп32............................................20 Архитектура, управляемая событиями............................21 Оконная процедура.............................................22 Оконные классы................................................23 Цикл обработки сообщений......................................25 Наипростейшая программа для \Мпс1о\л/5...........................25 Программа «Не11о, \люг1с1!» — первый вариант.....................30 Файл исходного текста программы...............................31 Регистрация класса окна.......................................32 Создание окна.................................................35 Использование функции Сгеабе\Л/1пс1о\л/Ех.....................38 Отображение окна на экране....................................39 Обработка сообщений...........................................40 Оконная процедура.............................................42
Программа «Не11о, \л/ог1с1!» — второй вариант....................46 Функции поддержки окон...........................................49 Часто используемые сообщения.....................................50 Модификация характеристик окна...................................51 Особенности программирования для \Л/!пс1о\л/5....................52 Синхронные и асинхронные сообщения............................53 Посылка сообщений из приложения...............................54 Использование глобальных или статических переменных...........55 Получение дескриптора экземпляра приложения...................55 Предотвращение зависания приложения в случае медленной обработки отдельных событий........................56 Использование утилиты 5ру++...................................57 Глава 2. 601 — графический интерфейс устройства. Рисование линий, фигур, текста...................................58 Контекст устройства..............................................59 Типы контекстов устройства....................................59 Регионы \Л/1Пс1о\л/5. Отсечение...............................60 Контекст дисплея..............................................60 Использование сообщения \Л/М_РА11\1Т..........................63 Контекст принтера.............................................65 Контекст в памяти (совместимый контекст)......................65 Метафайловый контекст.........................................66 Информационный контекст.......................................66 Системы координат и преобразования...............................66 Экранные, оконные и клиентские координаты.....................66 Типы координатных систем......................................67 Физическая система координат..................................68 Система координат устройства..................................68 Страничная система координат и режимы отображения.............69 Мировая система координат.....................................72 Получение информации о возможностях устройства...................74 Управление цветом. Вывод пиксела.................................76 Цветовое пространство Н1_5....................................76 Цветовое пространство НСВ.....................................76 Определение цвета при работе с палитрой.......................77 Вывод пикселов................................................78
Атрибуты контекста устройства, влияющие на рисование............78 Режим рисования. Бинарные растровые операции.................79 Режим смешивания фона и цвет фона графического элемента......81 Общие операции с графическими объектами.........................81 Линии и кривые..................................................82 Рисование отрезков...........................................83 Дуги.........................................................84 Кривые Безье.................................................86 Перья...........................................................87 Стандартные перья............................................88 Простые перья................................................89 Расширенные перья............................................93 Кисти...........................................................96 Стандартные кисти............................................97 Пользовательские кисти.......................................97 Замкнутые фигуры...............................................101 Прямоугольники..............................................101 Эллипсы, сегменты, секторы и закругленные прямоугольники....105 Многоугольники..............................................107 Регионы и отсечение............................................109 Создание регионов...........................................109 Операции с объектами регионов...............................111 Прорисовка регионов.........................................111 Отсечение...................................................112 Отображение текста.............................................113 Наборы символов и кодировки.................................114 11пюос1е....................................................117 Шрифты......................................................118 Вывод текста................................................129 Полосы прокрутки и вывод текста.............................141 Примеры программ...............................................146 Просмотрщик текстовых файлов................................146 Вывод временной диаграммы напряжения переменного электрического тока..............................153 Глава 3. 601. Палитры, растры, метафайлы.......................159 Палитры........................................................159 Основные принципы управления палитрами......................159
Системная палитра...........................................161 Логическая палитра..........................................166 Растры........................................................174 Аппаратно-независимые растры................................175 Аппаратно-зависимые растры..................................193 О1В-секции..................................................200 Тернарные растровые операции................................202 Метафайлы.....................................................206 Создание метафайла..........................................207 Воспроизведение метафайла...................................210 Глава 4. Средства ввода.......................................213 Клавиатура....................................................213 Фокус ввода.................................................214 Клавиши и символы...........................................215 Аппаратные сообщения........................................215 Символьные сообщения........................................217 Работа с кареткой..........................................219 Примитивный текстовый редактор..............................220 Мышь..........................................................225 Терминология, связанная с мышью.............................226 Сообщения мыши..............................................226 Обработка двойного щелчка...................................227 Обработка сообщений от колеса мыши..........................227 Рисуем мышью................................................228 Эластичные прямоугольники...................................234 Улучшенное приложение для просмотра текстовых файлов........237 Глава 5. Ресурсы УУ!п<1(пм5-приложения........................242 Редакторы ресурсов............................................243 Пиктограммы...................................................243 Создание пиктограммы с помощью графического редактора.......246 Импорт существующей пиктограммы.............................250 Просмотр и редактирование ресурсов приложения...............251 Использование ресурса в приложении..........................252 Курсоры.......................................................256 Растровые образы..............................................260 Ресурсы, определяемые программистом...........................263
Доступ к данным в ресурсе...................................263 Воспроизведение звуковых файлов................................264 Таблицы строк..................................................268 Глава 6. Меню и быстрые клавиши................................272 Организация и виды меню........................................272 Типы пунктов меню...........................................273 Системное меню..............................................274 Клавиатурный интерфейс меню.................................273 Статус пунктов меню.........................................275 Отметка пунктов меню........................................275 Пункт меню, применяемый по умолчанию........................276 Определение меню в виде ресурса................................276 Шаблон меню.................................................276 Вызов редактора меню........................................277 Атрибуты пункта меню........................................278 Уровни меню.................................................279 Процедура определения пункта для меню 1-го уровня...........279 Процедура определения меню нулевого уровня..................280 Добавление меню к окну приложения..............................280 Внесение изменений в меню......................................281 Функция СЬескМепиИет........................................282 Функция СИескМепиРасИоКет...................................282 Функция ЕпаЫеМепийет........................................283 Функция МосИГуМепи..........................................283 Функции для получения дескриптора меню......................284 Сообщения меню.................................................284 Приложение МепиОето1...........................................286 Работа с контекстным меню......................................293 Определение шаблона контекстного меню.......................293 Загрузка меню...............................................293 Вызов меню..................................................293 Приложение МепиОето2........................................294 Быстрые клавиши................................................299 Модификация определения ресурса меню........................299 Таблица быстрых клавиш......................................300 Загрузка таблицы быстрых клавиш.............................301
Модификация цикла обработки сообщений......................301 Приложение МетЮетоЗ........................................302 Глава 7. Диалоговые окна......................................304 Типы диалоговых окон..........................................304 Элементы управления в диалоговом окне.........................305 Создание и обработка диалогового окна.........................307 Шаблон диалогового окна....................................307 Шаблонная система единиц...................................308 Модальный диалог..............................................308 Вызов и использование редактора диалоговых окон............309 Добавление элемента управления Рисунок.....................311 Добавление элементов управления Надпись....................312 Выравнивание элементов управления на форме диалога.........314 Определение диалоговой процедуры и вызов диалога...........316 Изменение атрибутов элемента управления....................320 Использование других элементов управления.....................323 Кнопки.....................................................323 Кнопка Оипег с1га\л/.......................................325 Флажки.....................................................331 Переключатели..............................................333 Групповая рамка............................................333 Пример использования групповой рамки, флажков и переключателей .. 334 Клавиатурный интерфейс и порядок обхода элементов управления .... 340 Окно редактирования........................................341 Список.....................................................344 Комбинированный список.....................................356 Немодальный диалог............................................366 Различия между модальными и немодальными окнами диалога....366 Пример использования немодального окна диалога.............367 Окно сообщений................................................372 Диалоговые окна общего пользования............................374 Глава 8. Элементы управления общего пользования...............379 Основы применения.............................................380 Инициализация библиотеки...................................380 Создание элементов управления общего пользования...........382 Стили элементов управления общего пользования..............383
Обмен сообщениями.............................................384 Элементы управления главного окна...............................385 Панель инструментов...........................................385 Окно подсказки................................................406 Замена класса К\Л/пс1 на класс ЮЛ/пс1Ех.......................408 Строка состояния..............................................413 Другие элементы управления......................................423 Индикатор процесса............................................423 Регулятор.....................................................428 Счетчик и поле с прокруткой...................................435 Создание счетчика.............................................435 Глава 9. Многозадачность........................................441 Объекты ядра....................................................441 Процессы и потоки...............................................443 Планирование потоков............................................444 Классы приоритетов процесса и приоритеты потоков..............445 Управление процессами...........................................447 Использование функции Сгеа1еРгосезз...........................447 Завершение процесса...........................................448 Запуск обособленных дочерних процессов........................449 Управление потоками.............................................451 Функция Сгеа!еТИгеас1.........................................451 Функция 51еер.................................................452 Пример многопоточного приложения..............................452 Взаимодействие потоков через глобальную переменную............456 Синхронизация...................................................459 Атомарный доступ и семейство 1п!ег1оскес1-функций.............459 Критические секции............................................460 Май-функции...................................................461 События.......................................................464 Семафоры......................................................466 Мьютексы......................................................467 Обмен данными между процессами..................................469 Виртуальная память. Адресное пространство процесса............470 Файлы данных, проецируемые в память...........................472 Использование проекции файла для реализации разделяемой памяти .. 472
Модель «клиент-сервер».......................................473 Обмен данными с помощью сообщения \Л/М_СОРУОАТА..............474 Приложение ЗегуегАрр.........................................475 Приложение СНеп1Арр..........................................479 Не забывайте освобождать ресурсы................................483 Когда многопоточность реально полезна?..........................484 Глава 10. Таймеры и время.......................................486 Время \Л/| пс1о\л/5.............................................486 Системное время.................................................487 Измерение малых временных интервалов............................488 Использование счетчика монитора производительности...........488 Использование команды РОТ5С..................................489 Программирование задержек в исполнении кода.....................497 Использование функции 51еер..................................498 Использование метода 1Юе1ау класса КТ|тег....................501 Класс ОПтег..................................................502 Стандартный таймер..............................................504 Первый способ использования стандартных таймеров.............504 Второй способ использования стандартных таймеров.............508 Мультимедийный таймер...........................................509 Функции йте5е1Еуеп1 и бтеКШЕуеп!.............................509 Тестирование мультимедийного таймера.........................511 Глава 11. Библиотеки динамической компоновки 01.1...............515 ОН и адресное пространство процесса.............................514 Создание собственной ОН.........................................517 Вызов функций из ОН.............................................519 Неявная загруза ОН...........................................519 Явная загрузка ОН............................................521 Отложенная загрузка ОН.......................................523 Загрузка ресурсов из ОН.........................................525 Функция входа/выхода............................................527 Локальная память потока (ТЬ5)...................................529 Динамическая Т1_3............................................531 Статическая Т1_5.............................................538
Глава 12. Специальные приложения............................539 Анимация....................................................539 Приложение со стандартным таймером.......................539 Двойная буферизация......................................543 Рисование в реальном времени................................547 Требования к приемнику информации от метеорадиолокатора..547 Разработка модели программного имитатора.................548 Приложение 1. Интегрированная среда АИвиа! С++ 6.0..........563 Запуск ГОЕ. Типы приложений.................................563 Создание нового проекта..................................565 Добавление к проекту файлов с исходным кодом.............566 Многофайловые проекты....................................568 Компиляция, компоновка и выполнение проекта..............568 Конфигурация проекта.....................................569 Как закончить работу над проектом........................569 Как открыть проект, над которым вы ранее работали.........................569 Встроенная справочная система............................570 Работа с отладчиком......................................570 Некоторые полезные инструменты...........................574 Приложение 2. Интегрированная среда У15иа1 51ис1ю.МЕТ.... 577 Создание нового проекта.....................................578 Добавление к проекту нового файла...........................580 Компиляция, сборка и выполнение.............................582 Работа с редакторами ресурсов...............................583 Приложение 3. Работа с утилитой 5ру++.......................584 Список литературы...........................................587 Алфавитный указатель........................................588
Предисловие АР1 — это аббревиатура названия АррИсайоп Рго^гаттт^ Тгйег/асе (интерфейс при- кладного программирования). АР1 представляет собой совокупность функций и ин- струментов, позволяющих программисту создавать приложения (программы), ра- ботающие в некоторой среде. \У1п32 АР1 — это набор функций для создания программ, работающих под управ- лением МхсгозоЙ \Упн1о\\ъ 98, \Упк1о\\ъ МТ или \Упк1о\\ъ 2000. Все функции этого набора являются 32-битными, что отражено в названии интерфейса. При написании прикладных программ для АУшсЕпуз программистам приходится пользоваться огромным объемом документации, который изложен в справочной си- стеме М8ВМ, содержащей очень мало примеров и представленной только на анг- лийском языке. Часто приходится тратить много времени, чтобы понять, как рабо- тает та или иная функция, и какова область ее применения. Особенно серьезные проблемы возникают у разработчиков, имеющих поверхностные представления о принципах функционирования операционной системы и о механизмах ее взаимо- действия с приложениями. В данной книге изложены основные концепции и приемы программирования для \Упк1о\\ъ на языке С/С++ с применением АУш32 АР1. Существенное внимание в ней уделяется вопросам взаимодействия операционной системы и приложений. Даны основы рисования и копирования изображений, а также использования \Упк1о\\ъ- ресурсов. Рассмотрено создание графического интерфейса пользователя, включаю- щего меню, панели инструментов, диалоговые окна и элементы управления. Также излагаются основы построения многопоточных приложений и библиотек динами- ческой компоновки ВЬЕ. Рассмотрены специальные вопросы рисования: анимация, технология рисования в реальном времени. Показано использование классов С/С++ для применения концепций объектно-ориентированного программирования при создании АУхпбо^з-приложений. Владение базовыми знаниями интерфейса прикладного программирования су- щественно облегчает изучение технологий программирования на более высоком уров- не, например, с использованием библиотеки классов МЕС (МхсгозоЙ ЕоппбаНоп Скззез) или библиотеки классов АУшсЕпуз Еогшз. Дело в том, что реализация как МЕС, так и АУшсЕпуз Еогтз, основана на вызове функций \Упк1о\\ъ АРЕ С точки зрения архитектуры эти интерфейсы представляют собой надстройку над АУш32 АР1 и предназначены для облегчения программирова- ния АУшсЕл^з-приложений. Однако практика показывает, что для программистов, начинающих писать программы сразу на уровне МЕС или АУшсЕпуз Еогтз, возника- ют серьезные проблемы, как только дело доходит до создания реальных приложе-
ний. И, наоборот, если вы сначала приобретаете опыт программирования с \Уш32 АР1, освоение более высоких технологий проходит гораздо гармоничней и про- дуктивней. Есть и другой аспект, немаловажный для профессионального программирова- ния. Очевидно, что МЕС и АУшсЕпуз Еогтз не только повышают производитель- ность труда программиста, но и обладают меньшей гибкостью по сравнению с ин- терфейсом более низкого уровня. Поэтому когда к эффективности реализации приложения предъявляются повышенные требования (например, по затратам па- мяти и быстродействию), более целесообразным является кодирование на уровне АУш32 АРЕ На содержание книги повлияли: □ информация, почерпнутая автором из книг таких мэтров АУтс1о\\5-программи- ро вания как Чарльз Петцольд (Сйаг1ез Ре1хо1с1) [1], Джеффри Рихтер 0ейтеу Ккййег) [5] и Фень Юань (Ееп^пап) [6]; □ опыт практического программирования автора в ООО «Контур-НИИРС»; □ опыт проведения автором семинаров по программированию для АУшбом^з в Санкт-Петербургском государственном университете информационных тех- нологий, механики и оптики. Данная книга является вторым изданием, исправленным и дополненным, кни- ги «\У1п32 АРЕ Эффективная разработка приложений», опубликованной издатель- ством «Питер» в 2006 г. В новой редакции книги добавлена глава, посвященная разработке ВЕЕ. Кому адресована эта книга Книга ориентирована на широкий круг читателей. Она будет полезна начина- ющим программистам, студентам вузов, аспирантам и преподавателям, а также более опытным коллегам, владеющим языком С++, но не имеющим опыта разра- ботки приложений для АУшсЕпуз. Предполагается, что читатель знаком с языком С/С++ и умеет если не писать, то хотя бы читать и понимать программы, написанные на этом языке. Если это не так, то придется на время отложить эту книжку в сторону, пока вы не ликвидируе- те этот досадный пробел в вашем образовании. Но не уходите надолго — книга будет вас ждать...:) Как работать с книгой Эта книга должна дать читателю базовые знания по программированию на С/С++ с использованием АУш32 АРЕ Конечно, для их усвоения недостаточно про- сто прочитать изложенный здесь материал. Необходимо активное и глубокое по- гружение в среду \Уш32 АРЕ то есть выполнение большинства программных при- меров на вашем компьютере. Все приведенные в книге примеры протестированы на компьютере с операци- онной системой М1СГО8ОЙ АУшсЕпуз 2000 Рго&ззюпа! в среде МхсгозоЙ Ухзпа! 81псйо 6.0. Вы можете использовать любую из систем АУшсЕпуз 98, АУтсЕпуз ?<Т/2000 или
\Упк1о\\ъ ХР. Желательно компилировать приведенные примеры программ в Ухзпа! 81пс1ю 6.0, выполняя указания по построению проектов, приведенные в Прило- жении 1. Можно также компилировать проекты и в среде МхсгозоЙ Ухзпа! 81псйо.ХЕТ, но при этом надо точно выполнять требования к типу проекта, которые приведены в Приложении 2. Указанные выше рекомендации по работе с книгой относятся к начинающим программистам. Более опытные программисты могут пользоваться книгой как спра- вочником, обращаясь к тем разделам, которые содержат интересующую их инфор- мацию. Программные примеры, рассмотренные в книге, содержат несколько классов и утилит общего назначения, которые вы можете применять и в своих приложени- ях. Имена этих классов начинаются с буквы английского алфавита «К», чтобы не было путаницы с классами библиотеки МЕС. С целью сокращения объема книги во многих листингах программ повторяю- щиеся фрагменты кода опущены, но делается ссылка на предшествующий листинг, содержащий этот фрагмент. Если у вас возникнут какие-то вопросы в связи с эти- ми ссылками, вы всегда сможете найти полные тексты программ в комплекте фай- лов к данной книге, размещенном на сайте издательства «Питер». В рамках одной книги невозможно осветить все аспекты программирования для \Упн1о\\ъ. Однако автор надеется, что после освоения базовых знаний вам будет гораздо легче разобраться с другими разделами программирования для АУтсЕпуз. Автор будет признателен всем читателям за замечания, вопросы и пожелания, относящиеся к содержанию книги, которые можно присылать по адресу $Нирак@та1’1.ги. От издательства Ваши замечания, предложения и вопросы отправляйте по адресу электронной по- чты сотр@рй:ег.сот (издательство «Питер», компьютерная редакция). Мы будем рады узнать ваше мнение! Все исходные тексты, приведенные в книге, вы можете найти по адресу ННр:// \л/\л/\л/.рП:ег.сот/с1о\л/п1оас1. Подробную информацию о наших книгах вы найдете на веб-сайте издатель- ства: Н1±р://уууууу.р1Чег.сот.
«Не11о, УУог1сП», или Первые шаги к пониманию основных концепций УУтс1ош8 Читатель, скорее всего, уже имеет опыт пользователя той или иной версии опера- ционной системы \Упк1о\\ъ. Трудно предположить, что есть пользователь, который ни разу не работал с текстовым редактором МхсгозоЙ АУогс! или с другим прило- жением пакета МхсгозоЙ Ойгсе. Переход в категорию программиста требует, од- нако, более близкого знакомства с внутренним устройством АУтйомга. Поэтому, как бы ни хотелось нам поскорее откомпилировать код нашей первой программы, все же сначала придется поговорить об общих принципах функционирования опе- рационных систем семейства АУхпйом^з. Базовые концепции Операционная система АУтйомга по сравнению с операционными системами типа М8-ВО8 обладает серьезными преимуществами и для пользователей, и для про- граммистов. Среди этих преимуществ обычно выделяют: □ графический интерфейс пользователя; □ многозадачность; □ управление памятью; □ независимость от аппаратных средств. Графический интерфейс пользователя Сгарк1са11/зег 1п1:ег1'асе} (СП!) дает возможность пользователям работать с при- ложениями максимально удобным способом. Каждое приложение представлено на экране дисплея своим окном, которое выглядит как прямоугольная рабочая область с набором стандартных элементов управления. Окно идентифицируется своим заголовком, имеет кнопки минимизации и максимизации размеров, а так- же кнопку завершения приложения. Под заголовком обычно находится строка меню для выбора различных команд или режимов работы. Всплывающие окна 1 Графический интерфейс пользователя.
диалога, вызываемые командами меню, также содержат привычные для пользо- вателя элементы управления, такие как текстовые поля ввода информации, от- крывающиеся списки и кнопки различных типов. Большинство программ для \У1пс1о\\ъ поддерживают работу и с клавиатурой, и с мышью. Стандартизация графического интерфейса имеет очень большое значение для пользователя, потому что одинаковый интерфейс экономит его время и упрощает изучение новых приложений. С точки зрения программиста, стандартный вид ин- терфейса обеспечивается использованием подпрограмм, встроенных непосред- ственно в \Упк1о\\у что также приводит к существенной экономии времени при написании новых программ. Многозадачность Многозадачные операционные системы позволяют пользователю одновремен- но работать с несколькими приложениями или несколькими копиями одного приложения. Например, пользователь может открыть УНпа! 81исйо 6.0 и наби- рать в текстовом редакторе код одной из программ, приведенных в этой книге, одновременно слушая приятную музыку, воспроизводимую приложением «Про- игрыватель \Упн1о\\ъ Месйа». В это же время программа ехр1огег.ехе может зани- маться поиском необходимого файла по всему дисковому пространству компь- ютера. В первых версиях АУтскпуз была реализована так называемая кооперативная (или невытесняющая) многозадачность, когда приложения должны были сами отдавать управление операционной системе, чтобы дать возможность работать другим программам. Такая стратегия имела существенный недостаток, ведь лю- бое некорректно работающее приложение могло «подвесить» операционную сис- тему. Но уже в \Упк1о\\ъ 95 была введена вытесняющая многозадачность. Опера- ционная система автоматически переключается с одной задачи на другую, не ожидая, пока выполняемая программа освободит управление процессором. Многозадачность осуществляется в АУтйомга при помощи процессов и потоков. Любое приложение АУшбом^з после запуска реализуется как процесс (ргосезз). Гру- бо говоря, процесс можно представить как совокупность программного кода и вы- деленных для его исполнения системных ресурсов. При инициализации процесса система всегда создает первичный (основной) поток (Лгеа<1), который исполняет код программы, манипулируя данными в адресном пространстве процесса. Из основного потока при необходимости могут быть запущены один или не- сколько вторичных потоков, которые выполняются одновременно с основным по- током. На самом деле истинный параллелизм возможен только при исполнении программы на многопроцессорной компьютерной системе, когда есть возможность распределить потоки между разными процессорами. В случае обычного однопро- цессорного компьютера операционная система выделяет по очереди некоторый квант времени каждому потоку. Но каковы соотношения между потоками и окнами? Дело в том, что окно все- гда принадлежит некоторому потоку. Поток может быть владельцем одного или нескольких окон, а может быть и вовсе безоконным. Например, если вторичный поток создан для приема данных из СОМ-порта, то он вполне может обойтись без своего окна.
Наконец, сами окна, принадлежащие потоку, находятся в некоторых иерар- хических взаимоотношениях. Одно окно является окном верхнего уровня (1ор- 1есе1 шпФоте)1, другие окна называются дочерними (скИН шпскхш). Дочерние окна подчиняются своим родительским окнам (рагепк ттпскуш). Рисунок 1.1 иллюст- рирует эти взаимоотношения. Рис. 1.1. Соотношения между процессами, потоками и окнами Если на экране находится несколько окон, то в каждый момент времени толь- ко одно из них может быть активным. Активное окно отличается более ярким фоном на строке заголовка, в то время как пассивные окна имеют более тусклый фон заголовка. Говорят, что активное окно имеет фокус ввода. Это означает, что любая информация, вводимая пользователем при помощи клавиатуры или мыши, будет перенаправляться операционной системой именно данному окну. Управление памятью Память — это один из важнейших разделяемых ресурсов в операционной системе. Если одновременно запущены несколько приложений, то они должны разделять память, не выходя за пределы выделенного адресного пространства. Так как одни программы запускаются, а другие завершаются, то память фрагментируется. Си- стема должна уметь объединять свободное пространство памяти, перемещая бло- ки кода и данных. Система АУтсЕпуз обеспечивает достаточно большую гибкость в управлении памятью. Если объем доступной памяти меньше объема исполняемого файла, то система может загружать исполняемый файл по частям, удаляя из памяти отработавшие фрагменты. Если пользователь запустил несколько копий, кото- 1 Окно верхнего уровня не имеет родительского окна.
рые также называют отдельными экземплярами приложения, то система разме- щает в памяти только одну копию исполняемого кода, которая используется этими экземплярами совместно. Программы, запущенные в Ш1пс1о\\-з, могут ис- пользовать также функции из других файлов, которые называются библиотека- ми динамической компоновки — ОН (бупатгс Ипк ИЬгапез). Система АУшсЕлуз под- держивает механизм связи программ во время их работы с функциями из ОН. Даже сама операционная система АУтскпуз, по существу, является набором ди- намически подключаемых библиотек. Механизмы управления памятью непрерывно совершенствовались по мере развития \У1пс1о\\-з. В данный момент АУш32 АР1 реализован для 32-разрядной операционной системы с плоским адресным пространством. Независимость от аппаратных средств Еще одним преимуществом \У1пс1о\уз является независимость от используемой платформы. У программ, написанных для АУшсЬауз, нет прямого доступа к аппаратной части таких устройств отображения информации, как, например, экран или принтер. Вместо этого они вызывают функции графической подсисте- мы АУш32 АР1, называемой графическим интерфейсом устройства (СйарЫсз Ве- ухсе 1п1егГасе, СВ1). Функции СВ1 реализуют основные графические команды при помощи обра- щения к программным драйверам соответствующих аппаратных устройств. Одна и та же команда (например, ИпеТо — нарисовать линию) может иметь различную реализацию в разных драйверах. Эта реализация скрыта от программиста, исполь- зующего \У1п32 АР1, что упрощает разработку приложений. Таким образом, приложения, написанные с использованием \Уш32 АР1, будут работать с любым типом дисплея и любым типом принтера, для которых имеется в наличии драйвер АУшсЬауз. То же самое относится и к устройствам ввода дан- ных — клавиатуре, манипулятору «мышь» и т. д. Такая независимость АУшсклуз от аппаратных средств достигается благодаря указанию требований, которым должна удовлетворять аппаратура, в совокупности с 8ВК (ЗоЙдуаге Веуе1оршеп1 Кй — набор разработки программ) и/или ВПК (Впуег Веуе1оршеп1 Кй — набор разработки драйверов устройств). Разработчики нового оборудования поставля- ют его вместе с программными драйверами, которые обязаны удовлетворять этим требованиям. Вызовы функций и ОЬЬ \У1п32 АР1 поддерживает вызовы свыше двух тысяч функций, которые можно использовать в приложениях. Все основные функции \Ушс1оауз объявлены в заго- ловочных файлах. Главным заголовочным файлом является \лл пдоууз. Н. В этом файле содержится множество ссылок на другие заголовочные файлы. Вызовы функций АУ1п32 АР1 в программе осуществляются аналогично вы- зовам библиотечных функций С/С++. Основное различие заключается в том, что компоновщик связывает код библиотечных функций С/С++ с кодом про- граммы на этапе компоновки (статическое связывание), в то время как для функций \У1пс1оауз это связывание откладывается и осуществляется только
на этапе выполнения программы {динамическое связывание'). Библиотеки ди- намической компоновки (НББ) содержатся в файлах с расширением .с111. Боль- шая часть этих библиотек расположена в подкаталоге 5У5ТЕМ каталога уста- новки Ш1пс1о\\ъ. Операционная система позволяет компилятору использовать библиотеки им- порта, поставляемые в составе используемой среды программирования. Библиоте- ки импорта содержат имена всех функций из динамически подключаемых библио- тек, а также ссылки на них. Используя эту информацию, компоновщик размещает в исполняемом файле таблицу, по которой в процессе загрузки программы настра- иваются адреса вызываемых функций АУш32 АР1. Объектно-ориентированное программирование Хотя формально операционная система АУтйомга не является объектно-ориенти- рованной системой, тем не менее, в ней реализована именно объектно-ориентиро- ванная идеология. Это наиболее очевидно для базового объекта, с которым имеют дело и операционная система, и программист, и пользователь приложения, — то есть для окна. Как уже говорилось ранее, окно выглядит как прямоугольная область на эк- ране. Окно получает информацию от клавиатуры или мыши пользователя и вы- водит графическую информацию на экран. Пользователь рассматривает окна в качестве объектов и непосредственно взаимодействует с ними, перемещая их по экрану, выбирая команды меню, нажимая различные кнопки и передвигая бегу- нок на полосах прокрутки. Программист тоже рассматривает окна в качестве объектов, которые получа- ют от пользователя информацию в виде оконных сообщений. Кроме этого окно обменивается сообщениями с другими окнами. Понимание этих сообщений — ключ к осмысленному программированию в среде АУтйомга. Можно привести и другие примеры объектов в АУтйомга, к которым относятся, например, многочисленные графические объекты, используемые для рисования, такие как перья, кисти, шрифты, палитры и многие другие объекты. Независимо от своего типа, любой объект в АУтйомга идентифицируется сво- им дескриптором, или описателем. Оба этих названия являются переводом анг- лийского термина НапсИе^. Дескриптор — это своего рода ссылка на объект. Все взаимоотношения программного кода с объектом осуществляются только через его дескриптор. Система АУтскпуз тщательно скрывает свои внутренние секреты и не допускает прямого доступа к внутренним структурам объекта. Типы данных УУ!п32 Программиста, пишущего на С/С++, первое знакомство с программой для пс1 о\\ 5 поражает обилием типов данных. На самом деле все они определены посредством директив #с1еГ1’пе или #1урес1еГ в заголовочных файлах АУт32. В табл. 1.1 приведены некоторые наиболее часто встречающиеся типы данных Ш1пс1о\\ъ. 1 В среде программистов можно встретить еще один вариант «перевода» — хэндл.
Таблица 1.1. Некоторые часто используемые типы \Л/1п32 Тип данных Описание вооь ВУТЕ □У/У/ОРБ НАЫОЬЕ НВ1ТМАР НВН118Н НС1Ж8ОН НОС НЕО1МТ Н1СОЫ Н1М8ТА1МСЕ НМЕ1М11 НРЕЫ н\лл\ю 11ЧТ 1_О1\1С БРАРАМ БРС8ТИ БРСТ8ТИ БРСУ/У/8ТР 1_Р8ТР БРТ8ТИ БРУЛ/8ТИ 1_ИЕ8111_Т мин ТСНАИ Булевский тип (эквивалентный Ьоо1) Байт (8-битное целое без знака) 32-битное целое без знака Дескриптор объекта Дескриптор битмэпа (растрового изображения) Дескриптор кисти Дескриптор курсора Дескриптор контекста устройства Дескриптор шрифта Дескриптор иконки (пиктограммы) Дескриптор экземпляра приложения Дескриптор меню Дескриптор пера Дескриптор окна 32-битное целое со знаком 32-битное целое со знаком Тип, используемый для описания 1Рагат, четвертого параметра оконной процедуры Указатель на константную С-строку1 БРСУЛ/8ТР, если определен макрос 111М1СООЕ, и БРС8ТР в противном случае Указатель на константную 11п1сос1е-строку2 Указатель на С-строку БРУЛ/8ТР, если определен макрос 111М1СООЕ, и 1_Р5Тк в противном случае Указатель на 11пюос1е-строку Значение типа 1_О1\1С, возвращаемое оконной процедурой ((уоИ*) 0) УЛ/сНаМ: (11п1сос1е-символ), если определен макрос 111М1СООЕ, и сНаг в противном случае 1111МТ УЛ/РАРАМ 32-битное целое без знака Тип, используемый для описания \л/Рагат, третьего параметра оконной процедуры Архитектура, управляемая событиями В основе взаимодействия программы с внешним миром и с операционной систе- мой лежит концепция сообщений. С точки зрения приложения, сообщение является уведомлением о том, что про- изошло некоторое событие, которое может требовать, а может и не требовать 1 С-строка обязана завершаться нулевым байтом. В каждом байте содержится один символ в соответ- ствии с кодировкой АИ81. Далее, если это не оговаривается особо, под строками будут подразуме- ваться именно С-строки. 2 [/шсоб/е-строка также завершается нулевым байтом, но для хранения каждого символа использует- ся два байта. Чтобы использовать в проекте кодировку 13МСОВЕ, необходимо выполнять ряд пра- вил, которые приводятся в разделе «Отображение текста» в главе 2.
выполнения определенных действий. Это событие может быть следствием дей- ствий пользователя, например перемещения курсора или щелчка кнопкой мыши, изменения размеров окна или выбора пункта меню. Кроме того, событие может генерироваться приложением, а также операционной системой. Сообщение — это структура данных, содержащая следующие элементы: □ дескриптор окна, которому адресовано сообщение; □ код (номер) сообщения; □ дополнительную информацию, зависящую от кода сообщения. Не следует забывать, что АУтскпуз — многозадачная операционная среда. Со- общения от внешних источников, например от клавиатуры, адресуются в каждый конкретный момент времени только одному из работающих приложений, а именно — активному окну. Но каким же образом АУшсЬм^ играет роль диспет- чера сообщений? Для этого с момента старта операционная система создает в па- мяти глобальный объект, называемый системной очередью сообщений. Все сооб- щения, генерируемые как аппаратурой, так и приложениями, помещаются в эту очередь. АУтскпуз периодически опрашивает эту очередь и, если она не пуста, по- сылает очередное сообщение нужному адресату, определяемому при помощи дес- криптора окна. Сообщения, получаемые приложением, могут поступать асинхронно из разных источников. Например, приложение может работать с системным таймером, посы- лающим ему сообщения с заданным интервалом, и одновременно оно должно быть готовым в любой момент получить любое сообщение от операционной системы. Чтобы не допустить потери сообщений, АУтсЬм^з одновременно с запуском прило- жения создает глобальный объект, называемый очередью сообщений приложения. Время жизни этого объекта совпадает с временем жизни приложения. Таким образом, путь следования сообщений, вызванных аппаратными собы- тиями, можно представить так: аппаратное событие системная очередь сообщений очередь сообщений приложения. Оконная процедура Для понимания механизма обработки приложением поступающих к нему сооб- щений сначала нужно объяснить, что такое «оконная процедура». Оконная процедура — это «функция обратного вызова», предназначенная для обработки сообщений, адресованных любому окну того «оконного класса», в ко- тором содержится ссылка на данную процедуру. Если вы прочли только что сделанное определение, не споткнувшись ни на од- ном из терминов, значит, вы — не новичок в программировании для АУшботуз. Для тех же, кто видит эти термины впервые, нужны дополнительные разъяснения. Начнем с понятия «функция обратного вызова». Так называют функции, ко- торые вызывает сама операционная система. Поэтому в коде приложения вы не найдете прямого вызова такой функции. Компилятор узнает функцию обратного вызова по спецификатору САПВАСК. Оконная процедура обычно имеет заголовок со стандартным синтаксисом: 1_КЕ51Л_Т САПВАСК Имя_функции(НШО Шпс1, 1Л1\1Т иМзд, НРАВАМ мРагат, ЬРАВАМ 1Рагат)
В этом определении 1_КЕ$1Л_Т — тип возвращаемого значения (см. табл. 1.1), Н\Л/пс1 — дескриптор окна, которому адресовано сообщение, иМзд — код сообщения, мРагат и 1Рагат — параметры сообщения. Имя функции может быть произволь- ным, но для главного окна приложения обычно используется имя \Л/пдРгос. В теле функции после объявления необходимых локальных переменных обыч- но размещается оператор 5\л/11:сН, внутри которого и происходит обработка нуж- ных сообщений. Каждому коду сообщения в АУтскпуз сопоставлен уникальный символичес- кий идентификатор. Все системные идентификаторы определены при помощи директивы #с1еГ1пе в заголовочном файле \ллп изег. Н. Это облегчает чтение и пони- мание программ. Чаще всего приложение обрабатывает оконные сообщения {се1пс1осе тезза^ез), начинающиеся с префикса ММ_, например: ММ_РА1МТ, ММ_$12Е, ММ_М0\/Е и многие другие. Другие типы сообщений могут поступать от элементов управления, на- пример: сообщения с префиксом ВМ_ поступают от кнопок, сообщения с префик- сом ЕМ_ — от текстовых полей, сообщения с префиксом 1_В_ — от списков. При необходимости разработчик может определить в приложении и потом использовать собственные коды сообщений. Обмен такими сообщениями возмо- жен, конечно, только между окнами данного приложения. Иные приложения не смогут обрабатывать пользовательские сообщения. Теперь о новом термине «оконный класс». На самом деле все окна создаются на базе того или иного оконного класса. Оконный класс играет роль типа для данного окна. Оконные классы Оконный класс (^тпскхм с1азз), или класс окна — это структура, определяющая основные характеристики окна. К ним относятся стиль окна и связанные с ок- ном ресурсы, такие как пиктограмма, курсор, меню и кисть для закрашивания фона. Кроме того, одно из полей структуры содержит адрес оконной процедуры, предназначенной для обработки сообщений, получаемых любым окном данного класса. Ссылка на оконный класс передается функции Сгеа1е\Лй пс1о\л/, вызываемой для создания окна. Использование класса окна позволяет создавать множество окон на основе одного и того же оконного класса и, следовательно, использовать одну и ту же оконную процедуру. Например, все кнопки в программах АУтскпуз созданы на основе оконного класса В11ТТОМ. Оконная процедура этого класса, расположенная в динамически подключаемой библиотеке, управляет обработкой сообщений для всех кнопок всех окон. Аналогичные системные классы имеются и для других эле- ментов управления, таких как, например, списки и поля редактирования. В со- вокупности эти классы называются предопределенными или стандартными окон- ными классами. \У1пс1о\\ъ содержит предопределенный оконный класс также и для диалого- вых окон, играющих важную роль в графическом интерфейсе пользователя. Для главного окна приложения обычно создается собственный класс окна, учитывающий индивидуальные требования к программе.
ВНИМАНИЕ---------------------------------------------------------------------- Не следует путать понятие оконного класса УУ/пс/ожс понятием класса С++ или, в частности, с понятием класса в библиотеке МЕС. Дело в том, что исторически концепция оконных классов опередила по времени использование в \Л/1пс1о\л/5 объектно-ориентированных языков. Но как приложение узнает, что к нему пришло сообщение? Об этом рассказы- вается в следующем разделе. Цикл обработки сообщений Непременным компонентом всех АУтбом^-приложений является цикл обра- ботки сообщений. У приложения всегда есть главная функция п Ма1 п. Обыч- но она содержит вызовы функций для инициализации и создания окон, после чего следует цикл обработки сообщений и необходимый код для закрытия при- ложения. Что происходит в цикле обработки сообщений? Как известно, все сообщения, адресованные приложению, \У1пс1о\\ъ записывает в очередь сообщений приложе- ния. Извлечение сообщения из этой очереди осуществляет функция беВЧеззаде. Если очередное сообщение имеет код ММ_СЦ11Т, то происходит выход из цикла, после чего приложение завершает свою работу. Если очередное сообщение не является сообщением ММ_СЦ11Т, то оно передает- ся функции О1’$ра1:сНМе$$аде, которая возвращает сообщение обратно в АУшсЬм^. \У1пс1о\\ъ отправляет сообщение для его обработки соответствующей оконной процедуре — иными словами, \У1пс1о\\ъ вызывает оконную процедуру. После воз- врата из оконной процедуры АУшсЬм^ передает управление оператору, который расположен после О1’$ра1:сНМе$$аде, и работа цикла продолжается. Все... Почва вспахана, взборонена, унавожена, увлажнена... Осталось опустить в нее первое зернышко... Наипростейшая программа для УУтскллге Если вы еще ни разу не компилировали программу в интегрированной среде МхсгозоЙ Ухзпа! С++ 6.0, то оставьте, пожалуйста, закладку на этой странице и об- ратитесь к приложению 1, где изложена технология создания нового проекта, до- бавления к нему файлов с исходным кодом, компиляции, выполнения и отлад- ки проекта. ОК! Вы уже вернулись?.. Тогда продолжим. Создайте новый проект типа \Л/1пЗ2 АррПсаВ’оп с именем НеИоЕготМздВох. Добавьте к проекту новый файл с именем НеИоЕготМздВох.срр и введите в него следующий код: #1пс1ис1е <м1 пс1о\л/8. И> Ш ШИАР1 ШпМа1п(Н1М8ТАМСЕ ЩпзШсе, Н1М5ТАМСЕ НРгеу1пз^апсе, ЕРЗТР 1рСтс1Е1пе, 1гД пСгткОом) { МеззадеВох(ШЕЕ, "Не11о, 1л11п32 шг!с1!", "Не11о Ггот Меззаде Вох", МВ_ОК); ге^игп 0; }
Откомпилируйте проект. В случае появления ошибок компиляции проверьте, не был ли искажен исходный код при вводе. Если компиляция завершилась ус- пешно, запустите программу на выполнение. Вы должны получить результат, по- казанный на рис. 1.2. Рис. 1.2. Окно программы НеПоРготМздВох Что же можно делать с этим приложением? Прочитать радостное сообщение Не11оЛ \Л/1’п32 \л/ог1сП. Это раз. Перемещать окно по экрану, ухватившись мышью за его заголовок. Это два. Щелкнуть левой кнопкой мыши на кнопке ОК. Это три. Еще можно щелкнуть мышью на кнопке закрытия окна. В двух последних случа- ях приложение завершает свою работу, а его окно исчезает с экрана. Не так уж и много возможностей, верно?.. Ну а что же вы хотите от програм- мы, содержащей всего семь строк кода и всего две инструкции в теле функции \Л/1пМа1п? Рассмотрим теперь код нашей миниатюрной программы. В первой строке на- ходится следующая инструкция: #1пс1ис1е <\л/1 пс1о\л/8. Н> Эта директива подключает к программе главный заголовочный файл АУтсЕпуз- приложений. Далее следует заголовок функции УУ1пМа1п: Ш 1л/1МАР1 1л11 пМа1 п(Н1М5ТАМСЕ МпзШсе, Н1М5ТАМСЕ ИРгеуТпзШсе, ЕРЗТЕ 1рСтсЕ1пе, Ш пСтсЕИом) Эта функция эквивалентна функции таш для ВО8 или ЕМХ, то есть с нее всегда начинается выполнение программы. Функция возвращает значение типа 1П1: и принимает следующие параметры: □ Н1М5ТАМСЕ Н1п51апсе —дескриптор, который АУхпбом^з присваивает запущенно- му приложению. □ Н1М8ТА1МСЕ НРгеу1п$1:апсе — в \Уш32 этот параметр не используется и поэтому всегда принимает нулевое значение1. □ ЕР8ТК [рСтсШпе — указатель на строку, в которую копируются аргументы прило- жения, если оно запущено в режиме командной строки. Запуск приложения в этом режиме возможен либо с помощью команды стартового меню Пуск ► Вы- полнить, либо из оболочки типа ХогСоп Сопнпапбег. При этом в командной стро- ке набирается имя приложения, а после пробела указываются аргументы, разде- ленные также символом пробела. Есть еще один способ запуска приложения в этом режиме — через ярлык ЕХЕ-файла. Если у вас создан такой ярлык (яв- ляющийся ссылкой на ЕХЕ-файл), то щелкните на пиктограмме ярлыка пра- вой кнопкой мыши и выберите в контекстном меню пункт Свойства. Затем в появившемся диалоговом окне Свойства укажите параметры командной стро- ки, введя их в текстовое поле ОЬ)’сс1. 1 Этот параметр остался для совместимости версий от АУтскпуз 3.x, где он использовался для пред- ставления дескриптора предыдущего экземпляра программы.
□ 1п± пСтс15Но\л/ — целое значение, которое может быть передано функции 8Но\л/\Л/1’пс1о\л/. Этот параметр будет рассмотрен позже. Спецификатор \Л/1МАР1 определяет соглашения о вызове, то есть принятый в АУш32 порядок передачи параметров при вызове функций. Для нашей дальней- шей работы этой информации достаточно, поэтому нужно просто запомнить, что в заголовке функции \Л/1пМа1П он должен быть всегда. Разобравшись с заголовком функции, перейдем к ее телу. Здесь имеется вызов функции МеззадеВох, которая, собственно, и выполняет всю содержательную ра- боту приложения. Ну а где же обещанный ранее цикл обработки сообщений? Где текст оконной процедуры?.. Раскроем секрет: написание такой лаконичной программы стало возможно только благодаря использованию функции МеззадеВох. Прежде чем объяснять этот феномен, ознакомимся с досье на функцию МеззадеВох, которое можно найти в справочной системе М8ВЖ Для этого нужно установить текстовый курсор на любом символе имени функции и нажать клави- шу Е1. В появившемся диалоговом окне Найденные разделы следует выбрать строку МеззадеВох и нажать кнопку Показать. На экран будет выведено описание функции МеззадеВох, из которого следует, что функция создает, отображает и обслуживает окно сообщений. Окно сообщений — это диалоговое окно, содержащее указанное программистом текстовое сообщение и одну или несколько кнопок. Функция имеет следующий прототип1 2: Ж МеззадеВохСНШО Шпс1, 1РСТЗТН 1рТехУ 1РСТЗТП 1рСарШп, 1Л1\1Т иТуре); Ее параметры интерпретируются следующим образом: □ Г|\Л/пс1 — дескриптор родительского окна. Он принимает значение МОП, если ро- дительского окна нет. □ 1рТех1: — указатель на строку, содержащую текст сообщения. □ [рСарВ’оп — указатель на строку, содержащую текст заголовка диалогового окна. □ иТуре — параметр содержит комбинацию флагов, задающих количество и типы кнопок в диалоговом окне, а также наличие заданной пиктограммы. Более подробно интерпретация параметров функции МеззадеВох рассматрива- ется в главе 7. Сейчас для нас важно лишь то, что эта функция создает диалоговое окно, относящееся к предопределенному в системе оконному классу диалоговых окон. А предопределенный класс диалоговых окон содержит свою собственную оконную процедуру, спрятанную в недрах АУшсЬауз. Кроме этого, выполняя функ- цию МеззадеВох, система создает невидимый для программиста цикл обработки сообщений, который обслуживает созданное окно диалога. 1 Мгсго$ор Оесе!орег ХеШогк (М8ОХ) — это набор онлайновых и оффлайновых служб, предназначен- ных для оказания помощи разработчику в написании приложений с использованием продуктов и технологий фирмы МкгозоЙ. Справочная система М8ОЫ (М8ОЫЫЬгагу), обычно устанавливае- мая на компьютере вместе со средой МкгозоЙ Уйиа! ЗПкНо 6.0, содержит обширную информацию по программированию, в том числе и описание структур данных и функций АУЙ132 АР1. В дальней- шем вместо термина Справочная система М8БЫ будет употребляться термин М8СЫ. 2 Прототип (или предварительное объявление функции) задает тип возвращаемого значения, имя функции и список передаваемых параметров.
Вот почему в программе нет ни оконной процедуры, ни цикла обработки со- общений, и она претендует на титул самой короткой программы для АУтскпуз. Но, как уже говорилось, утилитарной пользы от этой суперкороткой програм- мы не очень много. Зато есть польза, и немалая, — гносеологическая! Так, мы уже знаем, что такое \Л/1пМа1П и как интерпретируются ее параметры. Это раз. Мы уже умеем пользо- ваться функцией МеззадеВох. Это два. Анализ текста программы дает также повод поговорить о стиле написания АУшсклуз-программ, а точнее, о так называемой вен- герской нотации, которая была предложена венгерским программистом фирмы МхсгозоЙ Чарльзом Симони (Сйагкз 81шопу1). Суть этой нотации заключается в том, что имя переменной начинается с префикса, который содержит одну или несколько строчных букв. Этот префикс определяет тип переменной. За префик- сом следует само имя, которое может состоять как из прописных, так и из строч- ных букв, но первая буква имени — всегда прописная. Например, идентификатор пМуУалаЫе обозначает некоторую целую переменную. Основные префиксы вен- герской нотации приведены в табл. 1.2. Таблица 1.2. Префиксы имен переменных для указания типа данных Префикс Соответствующий тип данных Ь Ьу с С1\Л/ Гп Н I 1 вооь ВУТЕ сНаг О\Л/ОкО Функция Дескриптор 11ЧТ 1_О1\1С |р 1р52 П Дальний указатель Дальний указатель на строку, заканчивающуюся нулевым байтом зНоЦ или Р рГп Р52 ру 52 Указатель Указатель на функцию Указатель на строку, заканчивающуюся нулевым байтом Указатель на тип уо1с1 Строка, заканчивающаяся нулевым байтом и сшмт V УО1С1 \л/ \Л/ОНО X Короткое целое число, используемое в качестве координаты х У Короткое целое число, используемое в качестве координаты у Теперь, уважаемый читатель, вам должно быть понятно, откуда берутся и что означают такие имена переменных, как 1рТех1 или иТуре. Более того, вы можете также придерживаться этой системы обозначений, придумывая имена для ваших собственных переменных. Но на последнем мы не настаиваем, так как вопрос сти- ля программирования либо решается индивидуально на основе личных вкусов и предпочтений, либо регламентируется правилами той фирмы, в которой рабо- тает программист.
Прежде чем перейти к рассмотрению «нормальной» \\йпску№8-программы, сле- дует отметить, что в функциях АУш32 может использоваться одна из следующих систем координат: □ экранные координаты (зсгееп соогбтаёез); □ оконные координаты (тпскш) соогсИпа^ез)', □ координаты клиентской области (сИеп1 соогбтаёез). На рис. 1.3 показаны главное окно программы на экране монитора, а также взаимоотношения между этими системами координат. Пиктограмма Кнопка Кнопка Кнопка Кроме этого на рисунке показаны основные элементы главного окна програм- мы, на которые мы будем ссылаться в дальнейшем изложении. Обратите внимание на то, что в верхней части окна программы находится полоса заголовка окна. В левой части полосы заголовка всегда расположена ма- ленькая картинка — пиктограмма. Щелчок мышью на изображении пиктограм- мы вызывает появление всплывающего системного меню. Оно обеспечивает до- ступ к стандартным функциям для окна приложения, например Моуе (Переместить), 5ые (Размер), Мтш’тые (Свернуть), Мах1т1не (Развернуть), С1озе (Закрыть). После- дние три функции дублируются кнопками, расположенными в правой части полосы заголовка. Ниже заголовка окна обычно размещается полоса меню, хотя можно создавать приложения, в которых меню отсутствует.
Еще ниже находится клиентская область окна. Это главная область вывода приложения. Ответственность за управление клиентской областью окна лежит на приложении. Следует отметить, что иногда АУшсклуз-приложения создают на базе диалого- вого окна. В таких приложениях полоса заголовка окна может отсутствовать. Программа «Не11о, ЛЛЛог1с1!» — первый вариант Полноценная программа для АУш32 должна содержать как минимум две функ- ции: □ \Л/1пМа1П — главную функцию, в которой создается основное окно программы и запускается цикл обработки сообщений; □ \Л/пдРгос — оконную процедуру, обеспечивающую обработку сообщений для ос- новного окна программы. На некотором псевдоязыке каркас АУтсЕпуз-программы можно представить следующим образом: 1л/1пМа1п (список аргументов) { Подготовить и зарегистрировать класс окна с требуемыми характеристиками; Создать экземпляр окна зарегистрированного класса; Пока не произошло необходимое для выхода событие { Извлечь очередное сообщение из очереди сообщений; Передать его через Н1пс1о\л/8 оконной функции; } Возврат из программы; ИпОРгос (список аргументов) { Обработать полученное сообщение; Возврат; Не будем отступать от традиции и покажем, как написать приложение, кото- рое создает окно и выводит в нем строку «Не11о, АУог1с1!». Весь текст программы, приведенный в листинге 1.1, размещается в файле Не11о1.срр. Листинг 1.1. Проект Не11о1 ////////////////////////////////////////////////////////////////////// // Не11о1.срр #1пс1ыс1е <\л/1 пс1о\л/8. Н> СКЕ801Т САИВАСК Ипс1Ргос(НШВ, ЮТ, НРАВАМ, ВРАКАМ); //======================== 1пТ ЮАР1 Ш пМапп(Н1ИЗТАМСЕ Юзбапсе, Н1М8ТАМСЕ 1эРгеу1пзТапсе, ЕРЗТЕ 1рСтс1Ыпе, Ш пСтЮом) НИНО НМал п1л/пс1; сбаг зхС1аззИатеЕ] = "МуС1азз"; продолжение &
Листинг 1.1 {продолжение} М86 тзд; Ш0С1А55ЕХ мс; // Заполняем структуру класса окна мс.сЬЗые мс.8Ту1е мс. 1рГп1л1пс1Ргос мс.сЬС18Ехбга мс.сЫл1пс1ЕхТга мс.бТпзТапсе мс.И1соп мс.ПСигзог = 81 хеоШс); = С8-НЕЕ0Ш | С8_Ш0Ш; = 1л1пс1Ргос; = 0; = 0; = МпзТапсе; = Еоас11соп(ШЕЕ, Ю1_АРРЕ1САТЮЮ; = ЕоабСигеогСМиЕЕ, 10С_АРР0Ю; мс.ПЬгВаскдгоипс1 = (НВВ08Ю6еШоск0Ь)есШН1ТЕ_ВВ08Н); мс. 1р8хМепиМате = ШЕЕ; мс.1р8хС1а88Мате = 8хС1а88Мате; мс.П1соп8т = Еоаб1соп(МиЕЕ, IВ1_АРРЕIСАТ10М); // Регистрируем класс окна 1Г (!Ред18ЪэгС1а88Ех(8мс)) { Ме88адеВох(М0ЕЕ, "СаппоТ гед18Тег с1а88", "Еггог", МВ_ОК); гебигп 0; // Создаем основное окно приложения ПМа1п1л1пс1 = СгеабеШ пскМ 8хС1а88Мате, "А Не11о1 АррИсаШп", 1л18_07ЕРЕАРРЕВШШ01л1, Ш-ОЗЕОЕЕАОЕТ, О, СМ_03Е0ЕЕА0ЕТ, О, (НШВЖЕЕ, (НМЕМШШЕЕ, (Н1М5ТАМСЕ)Н1п8-Еапсе, МОЕЕ 1Г (! НМап п1л/пс1) { МеззадеВохШОЕЕ, "СаппоТ сгеабе ша1п м1пс1ом", "Еггог", МВ_ОК); гебигп 0; // Показываем наше окно 8Но\л/1л/1 пс1о\л/(НМап п1л1пс1, пСтсЗИом); // 0рс1а1зе1л/1 пс1о\л/(НМапп1л/пс1); // См. примечание в разделе "Отображение окна..." // Выполняем цикл обработки сообщений до закрытия приложения \л/Гп 1 е (СММе88аде(&т8д, МОЕЕ, 0, 0)) { Тгап81абеМе88аде(&т8д); В18рабсбМе88аде(&т8д); гебигп тзд.мРагат; ЕЕЕЗОЕТ САЕЕВАСК МРгос(НШВ Шпс1, 0П\1Т иМзд, АРАРАМ мРагат, ЕРАЕАМ 1Рагат) { НОС ВВС; РА1МТ8ТР0СТ р8; ВЕСТ гесб; 8\л/1Тс1п (иМзд)
{ сазе Ш_РА1МТ: РОС = Вед1 пРа1 п1з(Шпс1, &рз); 6еЬС11егОесЬ(Шпс1, &гесЬ); ОгамТехШОС, "Не11о, 1л1ог1с1!", -1, &гесЬ, 0Т_51М(31_Е1_1МЕ | ОТ_СЕМТЕР | 0Т_7СЕ1\1ТЕВ ); Епс1Ра1 пШ1л1пс1, &рз); Ьгеак; сазе 1л/М_СЕ08Е: ОезЬгоуШ пс1о\л/( Шпс1); Ьгеак; сазе Ш_0Е8ТЕ0У: РозЬОьп ЬМеззаде(0); Ьгеак; ЬеТаиИ: геЬигп ОеТШпЬомРгос(Шпс1, иМзд, мРагат, 1Рагат); } геЬигп 0; } ////////////////////////////////////////////////////////////////////// Создайте проект с именем Н е11о 1, добавьте к нему указанный файл. После ком- пиляции и запуска на выполнение на экране должно появиться окно программы, показанное на рис. 1.4. Рис. 1.4. Окно программы Не11о1 А вот теперь придется запастись терпением. Пояснения к программе Н еНо 1 зай- мут примерно в восемь раз больше страниц, чем сам код программы. Файл исходного текста программы В файле Не11о1.срр расположены только две функции: МпМаш и \Л/пс1Ргос. МпМаш — это точка входа в программу. \Л/пс1 Ргос — это оконная процедура для главного окна
программы. Оконная процедура инкапсулирует код, отвечающий за ввод инфор- мации, обычно осуществляемый при помощи клавиатуры или мыши, а также за вывод информации на экран. В Не11о1.срр отсутствуют инструкции для непосредственного вызова \Л/пс1 Ргос — оконная процедура вызывается только из АУтскпуз. Однако в \Л/1пМа1П имеется ссылка на \Л/пс1 Ргос (адрес оконной процедуры присваивается полю ж. 1рГп\Л/пс1 Ргос), поэтому прототип функции \Л/пс1 Ргос объявлен в самом начале файла, еще до опре- деления функции \Л/1пМа1п. Обратите внимание на идентификаторы, написанные полностью прописны- ми буквами. Эти идентификаторы задаются в заголовочных файлах АУшбомгз. Некоторые из них содержат двух- или трехбуквенный префикс, завершаемый символом подчеркивания, например С5_НКЕОКАМ, Ю1_АРР1_1САТЮМ, ЮС_АКК0М, М5_0УЕК1_АРРЕ0М1М00М, СМ_11$ЕОЕЕА111_Т. Это просто числовые константы. Префикс обозначает основную категорию, к которой принадлежит константа. Некоторые из этих категорий приведены в табл. 1.3. Таблица 1.3. Префиксы для числовых констант Префикс Категория С8_ ОЛ/_ от_ ЮС- 101- \л/м_ \Л/5_ Опция стиля класса Опция создания окна Опция рисования текста Идентификатор предопределенного курсора Идентификатор предопределенной иконки (пиктограммы) Сообщение окна Стиль окна Также следует обратить внимание на новые типы данных, специфичные для системы Ш1пс1о\\-з. Некоторые их них были описаны в табл. 1.1. Регистрация класса окна Сразу после входа в функцию УУ1пМа1П создается и регистрируется класс главного окна приложения. Для этого необходимо заполнить структуру типа \Л/М0СЕА55ЕХ, а затем передать адрес этой структуры в виде аргумента функции Кед1’$1:егС1а$$Ех. Структура \Л/М 0СЕА55ЕХ имеет двенадцать полей: 1зурес1еТ збгысб ЦадШ0С1_А55ЕХ { ШИТ сЬЗые: // размер данной структуры в байтах ЩИТ 8бу1е; // стиль класса окна ШОРЕОС 1 рГп1л1пс1Ргос; // указатель на функцию окна (оконную процедуру) Ш сЬСЯзЕхбга; // число дополнительных байтов, которые должны //быть распределены в конце структуры класса Ш сЫл1пс1Ехбга; // число дополнительных байтов, которые должны //быть распределены вслед за экземпляром окна Н1М5ТАМСЕ Щпзбапсе; // дескриптор экземпляра приложения, в котором //находится оконная процедура для этого класса Н1СОИ Мсоп; // дескриптор пиктограммы НСЫЕЗОР ИСыгзог; // дескриптор курсора НВЕЫЗН ИЬгВаскдгоипс1: // дескриптор кисти, используемой для закраски //фона окна
1РСТ5ТК 1р82МепиМате; // указатель на строку, содержащую имя меню, //применяемого по умолчанию для этого класса 1РСТЗТР 1р82С1аз8Мате; // указатель на строку, содержащую имя класса окна РИСОМ И1соп8т; // дескриптор малой пиктограммы } Ш0С1А55ЕХ; ПРИМЕЧАНИЕ----------------------------------------------------------------------------------- Следует упомянуть о некоторых аспектах использованных в этом определении типов данных, а также о венгерской нотации имен полей. Префиксы 1_Р и 1р означают «длинный указатель» (1опд ро1п1ег), являющийся пережитком 16-разряд- ной \Л/1пс1о\л/5, в которой различались короткие 16-разрядные указатели и длинные 32-разрядные указатели. В \Л/1п 32 АР1 все указатели имеют длину 32 разряда. Префикс 1рГп означает «длинный указатель на функцию» (1опд ро1п1ег 1о а Гипсйоп). Префикс сЬ означает «счетчик байтов» (соип1ег оГ Ьу1ез). А префикс НЬг — это «дескриптор кисти» (Напс11е 1о а ЬгизН). Рассмотрим назначение полей структуры \Л/М 0С1А55ЕХ. Значение первого поля, сЬ5ые, должно быть равно длине структуры. Второе поле, 1е, может содержать в своем значении один или несколько сти- лей, перечисленных в табл. 1.41. Таблица 1.4. Стили класса окна Стиль Описание С8_С1_ОВА1_С1_А88 Создать класс, доступный всем приложениям. Обычно этот стиль при- меняется для создания определяемых пользователем элементов управления в ОН С8_НВЕСНАМ Перерисовывать все окно, если изменен размер по горизонтали С5_1\ЮС1_О5Е Запретить команду С1озе в системном меню С8.ОМЫОС Выделить уникальный контекст устройства для каждого окна, созданного при помощи этого класса С8_УКЕОКАМ Перерисовывать все окно, если изменен размер по вертикали В заголовочных файлах \Упк1о\\ъ идентификаторы, начинающиеся с префик- са С5_, задаются в виде 32-разрядной константы, в которой только один разряд установлен в единичное значение. Например, константа С5_\/КЕ0КАМ задана как 0x0001, а С5_НКЕ0КАМ — как 0x0002. Подобные константы иногда называют пораз- рядными флагами (Ьй Яа§з). Они могут объединяться с помощью операции ИЛИ языка С++. В рассматриваемой программе используется комбинация стилей С5_НКЕОКАМ | С5_\/КЕ0КАМ. Это означает, что все окна этого класса должны це- ликом перерисовываться при изменении как горизонтального, так и вертикаль- ного размеров окна. Если попробовать изменить размеры окна А Не11о1 АррПсаВ’оп, то можно увидеть, что строка текста переместится в новый центр окна. Механизм уведомления оконной процедуры об изменении размеров окна будет рассмотрен позже. Третье поле, 1рГп\Л/пс1Ргос, содержит адрес оконной процедуры (в нашей про- грамме это — \Л/пс1Ргос). 1В таблице указаны наиболее употребительные стили. Полный список стилей см. в М3 ОКЕ
Назначение полей 4—6 представляется очевидным1. Седьмое поле, Н1соп, содержит дескриптор пиктограммы, которая предназначе- на для этого класса окна. Пиктограмма (синонимы: иконка, значок) — это малень- кая битовая картинка, которая появляется на панели задач АУтскпуз и в левой ча- сти заголовка окна. Значение Ысоп обычно получают вызовом функции 1_оас11соп, которая имеет следующий прототип: Н1СОИ 1_оас11соп(Н1М5ТАМСЕ Шнапсе, 1РСТ8ТК 1р1сопМате); Эта функция загружает ресурс пиктограммы, заданный параметром 1р1сопМате, из экземпляра приложения, указанного параметром Н1п$1:апсе2. Функцию можно использовать также для загрузки одной из системных (предопределенных) пик- тограмм, если передать первому аргументу значение МОП. В этом случае второй аргумент должен содержать константу, идентификатор которой начинается с пре- фикса Ю1_ («идентификатор значка» — Ю Гог хсоп). Возможные значения второ- го аргумента для предопределенных пиктограмм приведены в табл. 1.5. Таблица 1.5. Предопределенные идентификаторы пиктограмм Значение Описание Ю1_АРР1_1САТЮМ Пиктограмма приложения по умолчанию Ю1.А8ТЕИ18К То же, что и ЮЦМЕОИМАТГОЫ Ю1_ЕИИОИ Пиктограмма в виде белого креста на фоне красного круга. Она исполь- зуется в серьезных предупреждающих сообщениях Ю1_ЕХС1_АМАТЮЫ То же, что и ЮЦЛ/АИМИМС ЮЦНАЫО То же, что и Ю1_ЕИИОИ Ю1_1ЫЕОИМАТЮЫ Пиктограмма «1», которая используется в информационных сообщениях ЮЦ211Е8Т1ОМ Пиктограмма «?» ЮЦЛ/АИМИМС Пиктограмма «!», которая используется в предупреждающих сообщениях ЮЦЛ/1М1-ОСО ЛОГОТИП \Л/1ПС1О\Л/5 Поле НСигзог содержит дескриптор курсора мыши, используемого приложени- ем в клиентской области окна. Значение НСигзог обычно получают с помощью вы- зова функции 1_оас1Сиг8ОГ, имеющей следующий прототип: НСШ80К 1оас1Сиг8ОГ(НIМ8ТАМСЕ МпзШсе, ЕРСТЗТЕ 1рСигзогМате); Эта функция загружает ресурс курсора, заданный вторым параметром (фСигзог- Мате), из экземпляра приложения, заданного первым параметром (Н1п$1:апсе)3. Функцию можно также использовать для загрузки одного из системных (пре- допределенных) курсоров, если передать первому аргументу значение МОП. Зна- чение второго аргумента при этом выбирается из табл. 1.6. Таблица 1.6. Предопределенные идентификаторы курсора Значение Описание ЮС_АРР5ТАКТ1МС Стандартная стрелка и малые песочные часы ЮС.АИИСМ/ Стандартная стрелка 1 Поля сЬСЬЕхНа и сЫУпйЕхГга используются крайне редко. 2 Вопросы создания и использования пользовательских пиктограмм рассматриваются в главе 5. 3 Вопросы создания и использования пользовательских курсоров рассматриваются в главе 5.
Значение Описание ЮС_СНО88 ЮС_НЕ1_Р ЮС_1ВЕАМ ЮС_1\Ю ЮС_512ЕА1_1_ ЮС_512ЕЫЕ5\Л/ ЮС_512ЕЫ5 ЮС_512Е1\1\Л/5Е ЮС_512Е\Л/Е ЮС_11РАккСМ/ ЮСДОАГГ Перекрестье Стрелка и вопросительный знак Текстовый двутавр Перечеркнутый кружок Четырехконечная стрелка Двухконечная стрелка, указывающая на северо-восток и юго-запад Двухконечная стрелка, указывающая на север и юг Двухконечная стрелка, указывающая на северо-запад и юго-восток Двухконечная стрелка, указывающая на запад и восток Вертикальная стрелка Песочные часы Девятое поле, НЬгВаскдгоипф содержит дескриптор кисти, используемой при- ложением для закраски фона в клиентской области окна. Кисть (ЬгизИ) — это гра- фический объект, который представляет собой шаблон пикселов различных цве- тов, используемый для закрашивания области. В АУтскпуз имеется несколько стандартных или предопределенных кистей. Вызов функции 6е151:оскОЬ]’ес1: с ар- гументом МН1ТЕ_ВВ11$Н возвращает дескриптор белой кисти. Так как возвращае- мое значение имеет тип Н С 010 ВО, то необходимо его преобразование к типу НВВ115Н. Десятое поле, фзгМепиМате, указывает на строку, содержащую имя меню, при- меняемого по умолчанию для данного класса. Рассматриваемая программа не имеет меню, поэтому поле установлено в МОП. Поле 1р$2С1а$$Мате указывает на строку, содержащую имя класса. Это имя долж- но использоваться в параметре 1рС1а$$Мате функции Сгеа1е\Л/1’пс1о\л/. Двенадцатое поле, Н1соп5т, содержит дескриптор малой пиктограммы, кото- рая предназначена для использования в строке заголовка окон, созданных при помощи этого класса. Размеры пиктограммы должны быть 16x16 пикселов. Если поле равно МОП, то система ищет ресурс пиктограммы, указанный полем Н1соп, чтобы сформировать малую пиктограмму из него. Итак, с заполнением структуры класса окна мы разобрались. Осталось заре- гистрировать подготовленный класс, вызвав функцию КедпзЕегСЕаззЕх. Это расши- ренная версия1 функции Кед1’з1:егС1а58 из предыдущих версий АУтскпуз. В то же время можно пользоваться и функцией Кедп’зЕегСДазз, передавая ей адрес структу- ры \Л/М ИС1А55 (а не \Л/М ИС1А55ЕХ). Создание окна Если регистрация класса окна прошла успешно, то следующий этап — создание окна. Для этого вызывается функция Сгеа1е\Л/1’пс1о№, прототип которой приведен ниже: НЮ Сгеа^еШ пс1о\л/( 1РСТ5ТК 1рС1а88Мате, // имя зарегистрированного класса 1РСТЗТР 1 рШ псЫМате, // имя окна ОШКО с1\л/ЗЦу1е, // стиль окна Ш х, // горизонтальная позиция 1 На это указывает окончание имени Ех, то есть еМеп(1е(1 — расширенный.
Ш у, 1пб п1л/1сИзГ|, 1пб пНе1дГ|1з, НШО ШпбРагеггЬ, НМЕШ ИМепи, // вертикальная позиция // ширина окна // высота окна // дескриптор родительского окна // дескриптор меню окна или идентификатор элемента //управления Н1М8ТАМСЕ Щпзбапсе, 1_РУО1О 1Рагат // дескриптор экземпляра приложения // указатель на данные, передаваемые в сообщении //Ш СРЕАТЕ ); Рассматривая назначение параметров, не будем забывать, что функция Сгеа1е \Л/1пс1о\л/ позволяет создавать не только основное окно приложения, но и любые другие окна, включая окна предопределенных классов, с помощью которых реа- лизуются стандартные элементы управления АУтйомга. Первый параметр, 1рС1а$$Мате, — это указатель на строку, содержащую допус- тимое имя класса окна. Таким именем может быть либо имя класса, зарегистриро- ванного ранее при помощи функции Кед1’$1:егС1а$$Ех или Кед1’$1:егС1а$$, либо имя од- ного из предопределенных классов, перечисленных в табл. 1.7. Таблица 1.7. Предопределенные классы окон Класс Описание Префикс в обозначении стилей всггтоы Прямоугольное окно кнопки, группы, флажка, переклю- чателя или пиктограммы В5_ сомвовох Элемент управления, объединяющий элементы Ы8ТВОХ и ЕЭ1Т. В поле редактирования отображается выбранная строка из Ы8ТВОХ СВ8_ ЕЭ1Т Прямоугольное окно (поле редактирования), предназна- ченное для ввода текста с клавиатуры Е8_ Ы8ТВОХ Прямоугольное окно со списком строк, из которого поль- зователь может выбрать любую строку 1_В8_ МО1СЫЕМТ Клиентское окно многодокументного интерфейса. Это окно получает сообщения, которые управляют дочерни- ми окнами в МЭТ-приложении Н1СНЕО1Т Элемент управления кюН Ес№ версии 1.0. В дополнение к возможностям элемента ЕЭ1Т позволяет редактировать текст с разными шрифтами и стилями Е8_ к1СНЕО1Т_С1_А88 Элемент управления кюН Ес№ версии 2.0 или 3.01. Усовершенствованная версия элемента кТСНЕЭГГ с до- полнительными возможностями Е5_ 8СкО1_1_ВАк Элемент управления линейкой прокрутки 8В8_ 8ТАТ1С Элемент управления статическим текстом. Применяется для размещения в окне текста или рамок Второй параметр, 1р\Л/1’пс1о\л/Мате, — это указатель на строку, содержащую имя окна. Место отображения имени зависит от вида окна. Например, для главного окна приложения оно выводится как заголовок окна (см. рис. 1.3), а для окна пре- допределенного класса В11ТТОМ размещается по центру кнопки. 1В зависимости от версии АУюсЬлуз, установленной на компьютере.
Третий параметр, д\л/5Ту1е, позволяет указывать стиль окна, состоящий из зна- чений, указанных в табл. 1.8. Эти константы могут объединяться в значении пара- метра с помощью побитовой операции ИЛИ. Для окон предопределенных классов дополнительно могут быть добавлены стили, характерные для данного элемента управления. Идентификаторы таких стилей начинаются с префикса, указанного в табл. 1.71. Таблица 1.8. Стили окна Обозначение стиля Описание \Л/8_ВОНОЕк \Л/5_САРТЮЫ Создать окно с рамкой в виде тонкой линии Создать окно, которое имеет область заголовка (включает стиль \Л/8_ВОНОЕк) \Л/5_СН1Ю Создать дочернее окно. Окно этого стиля не может иметь полосу меню и не может иметь стиль \Л/8_РОР11Р \Л/8_С1_1РСН11_ОкЕМ Исключить перерисовку дочерних окон, принадлежащих данному родительскому окну \Л/5_С1_1Р51В1_11\1С5 Исключить перерисовку соседних дочерних окон при перерисовке данного дочернего окна \Л/8_О1_СРРАМЕ Создать окно, имеющее рамку со стилем, типичным для диалого- вых окон. Окно этого стиля не может иметь область заголовка \Л/8_СРО11Р Считать данное окно первым в группе элементов управления (обычно группируются переключатели) \Л/8_Н8СкО1_1_ \Л/8_МАХ1М12Е \Л/8_МАХ1М12ЕВОХ \Л/5_М11\ПМ12Е \Л/5_М11\11М12ЕВОХ \Л/8_СЛ/Ек1_АРРЕО Создать окно с горизонтальной линейкой прокрутки Создать окно, которое первоначально является развернутым Создать окно с кнопкой развертывания Создать окно, которое первоначально является свернутым Создать окно с кнопкой свертывания Создать перекрывающееся окно. Окно этого стиля имеет область заголовка и рамку №8_ОУЕк1_АРРЕО\Л™ ОСМ/ Сочетание стилей \Л/8_ОУЕк1_АРРЕО, \Л/5_САРТЮ1\1, \Л/5_5У5МЕ1\111, \Л/8_ТН1СКЕКАМЕ, \Л/5_М11\11М12ЕВОХ и \Л/8_МАХ1М12ЕВОХ \Л/8_РОР11Р Создать всплывающее окно. Этот стиль не может использоваться совместно со стилем \Л/8_СН1Ю \Л/8_РОР11Р\Л/1ЫОСМ/ Сочетание стилей \Л/5_ВОРОЕР, \Л/8_РОР11Р и \Л/5_5У5МЕ1\111. Чтобы сделать системное меню видимым, необходимо добавить стиль \Л/5_САРТЮЫ \Л/5_5У5МЕ1\111 \Л/8_ТАВ8ТОР Создать окно с системным меню в области заголовка Стиль указывает, что на окне может остановиться фокус ввода, перемещаемый при помощи клавиши ТАВ \Л/8_ТН1СКЕКАМЕ \Л/8_У181В1_Е \Л/5_У5СкОИ Создать окно с рамкой, которая позволяет изменять его размеры Создать окно, которое сразу же является видимым Создать окно с вертикальной линейкой прокрутки В рассматриваемой программе используется стандартный стиль для главного окна приложения — М5_0\/ЕК1_АРРЕ0М1М00М. Это обычное перекрывающееся окно с заголовком, системным меню, расположенным в левой части строки заголовка, 1 Стили для элементов управления здесь не приводятся. При необходимости эту информацию можно найти в М8БК
кнопками для сворачивания, разворачивания и закрытия окна справа на строке заголовка и «толстой» рамкой, позволяющей изменять размеры окна. Четвертый параметр, х, задает горизонтальную позицию левого верхнего угла окна. Для главного окна позиции х и у определяются в экранных координатах, а для дочерних окон и элементов управления координаты отсчитываются относи- тельно левого верхнего угла родительского окна. Если позиция х не важна, то можно установить значение СМ_11$ЕОЕЕА111_Т. В этом случае АУтйомга использует значения х и у по умолчанию. Это означает, что сис- тема располагает следующие друг за другом перекрывающиеся окна, равномерно отступая по горизонтали и вертикали от левого верхнего угла экрана. Пятый параметр, у, задает вертикальную позицию левого верхнего угла окна. Если параметр х имеет значение СМ_11$ЕОЕЕА1Л_Т, то значение параметра у игнорируется. Шестой параметр, пУ\йскН, задает ширину окна в пикселах. Если ему присвоено значение СМ_11$ЕОЕЕА1Л-Т, то система будет использовать значения пМ’сИН и пН1дН1: по умолчанию. Седьмой параметр, пН1дНЕ задает высоту окна в пикселах. Если параметр пМ’сЙН установлен в СМ_11$ЕОЕЕА1Л_Т, то значение пН1дН± игнорируется. Восьмой параметр, Н\А/пс1Рагеп1:, в рассматриваемой программе имеет значение МОП, поскольку у главного окна программы отсутствует родительское окно. За- метим, что, если между двумя окнами существует связь типа родительское—до- чернее, дочернее окно всегда появляется только на поверхности родительского. Девятый параметр, НМепи, содержит дескриптор меню окна или идентификатор элемента управления. Интерпретация значения параметра зависит от вида окна. Если приложение использует меню, определенное в классе окна (поле фзгМеп 11- Мате в структуре ММ0С1А55ЕХ), то параметру НМепи необходимо передать значение МОП. Если создаваемое окно относится к элементу управления, то параметру НМепи передается целочисленное значение, которое далее используется как идентифи- катор созданного элемента. Этот идентификатор, например, будет содержаться в сообщениях ММ_СОММАМО, поступающих от элемента управления. Десятому параметру, НТпзЕапсе, должен быть присвоен дескриптор экземпляра приложения, переданный программе в качестве аргумента функции УУ1’пМа1п. Последний параметр, 1Рагат, в рассматриваемом примере тоже имеет значение М1ШЕ. При необходимости он может быть использован в качестве указателя на не- кие дополнительные данные, передаваемые окну в момент его создания с помо- щью сообщения ММ_СКЕАТЕ. Итак, функция Сгеа1:е\Л/1пс1о\л/ вызвана. Отработав, она возвращает дескриптор созданного окна. Если по какой-то причине создать окно не удалось, то функция возвращает значение МОП. Поэтому рекомендуется проверять возвращаемое функ- цией значение и в случае неудачи выдавать соответствующее сообщение, исполь- зуя уже знакомую функцию МеззадеВох. Использование функции Сгеа1еУУ!пс1ошЕх Для создания окна вместо функции Сгеа1:е\Л/1’пс1о\л/ может быть вызвана функция Сгеа1:е\Л/1’пс1о\л/Ех, прототип которой приведен ниже: НЮ Сгеа^еШпс1омЕх(ОШРО с1\л/Ех5Цу! е, ЕРСТ8ТР 1 рС1 аззИате, ЕРСТ8ТР 1 р!л/1 псЫМате, ОШРО с1\л/ЗЦу!е, Ш х, 1 ггЬ у, Ш п!л/1 сЦзН,
Ш пНе1дН1з, НШО ШпбРагегтЬ, НМЕМЫ ИМепи, Н1М5ТАМСЕ Шнапсе, ЬР70Ю 1Рагат); Все параметры этой функции, кроме первого, имеют тот же смысл, что и у функ- ции Сгеа1е\Л/1пдо\л/. Первый параметр, с1\л/Ех$1у1е, задает расширенный стиль окна, применяемый совместно со стилем, определенным в параметре с1\л/51:у1е. Напри- мер, в качестве расширенного стиля можно задать один или несколько флагов, приведенных в табл. 1.9. Полный список расширенных стилей можно найти в М8ВК Таблица 1.9. Расширенные стили окна Стиль Описание У\/5_ЕХ_АССЕРТР11_Е5 Создать окно, которое принимает перетаскиваемые файлы \Л/8_ЕХ_С1_1Е1\1ТЕОСЕ Рамка окна имеет утопленный край №8_ЕХ_СО1\1ТкО1_РАкЕ1\1Т Разрешить пользователю перемещаться по дочерним окнам с помощью клавиши ТаЬ \Л/5_ЕХ_МО1СН1Ю Создать дочернее окно многодокументного интерфейса \Л/8_ЕХ_8ТАТ1СЕОСЕ Создать окно с трехмерной рамкой. Этот стиль предназначен для элементов, которые не принимают ввод от пользователя \Л/8_ЕХ_ТООЬ\Л/1ЫОО\Л/ Создать окно с инструментами, предназначенное для реа- лизации плавающих панелей инструментов \Л/8_ЕХ_ТВАМ8РАкЕ1\1Т Создать прозрачное окно. Любые окна того же уровня, накрываемые этим окном, получат сообщение \Л/М_РА11\1Т в первую очередь, тем самым создавая эффект прозрач- ности \Л/5_ЕХ_\Л/1Ы 00УЛ/Е08Е Создать окно, имеющее рамку с активизированным краем Функция Стеа1:е\Л/1’пс1о\л/Ех так же, как и функция Сгеа1е\Л/1’пс1о\л/, может быть при- менена для создания любых окон, в том числе и окон элементов управления \У1пс1()\\ъ. Некоторые из расширенных стилей окна предназначены как раз для дочерних окон элементов управления1. Отображение окна на экране Для отображения на экране созданного окна вызывается функция 8Но\л/\ЛНпс1о\л/, имеющая следующий прототип: ВООЕ 8Но\лМ1 пс1ом(Н1л1М0 Шпс1, Ш пСшОсм); Первым параметром функции является дескриптор окна, а второй параметр определяет, в каком виде будет показано окно. При начальном отображении главного окна рекомендуется присваивать вто- рому параметру то значение, которое передается приложению через параметр пСтс1$Нодо функции УУ1пМа1п. При последующих отображениях можно использовать любое из значений, при- веденных в табл. 1.102. 1 Базовые элементы управления рассматриваются в главе 7, элементы управления общего пользова- ния — в главе 8. 2 В таблице приведены наиболее употребляемые значения. Полный список значений см. в М8Б1У.
Таблица 1.10. Значения параметра пСтс15Иом функции 5Гю\л/\Л/1Пс1о\л/ Значение Описание 5\Л/_НЮЕ Скрыть окно 8\Л/_МАХ1М12Е Развернуть окно 5\Л/_М11\ПМ12Е Свернуть окно 8\Л/_8НСМ/ Активизировать окно и показать в его текущих размерах и позиции 5УУ_5НО\Л/М1Ы1\ЮАСПУЕ Окно не выводится, а на панели задач появляются его имя и пиктограмма ПРИМЕЧАНИЕ----------------------------------------------------------------------------------------- Во всех книгах, посвященных программированию с \Л/1п32 АР1, а также в справочных материалах М8ОЫ рекомендуется после вызова функции ЗГю\лЛЛ/1пс1о\л/ вызвать функцию С1рс1а1:е\Л/1пс1о\л/, ко- торая посылает оконной процедуре сообщение \Л/М_РА11\1Т, заставляющее окно перерисовать свою клиентскую область. Однако эксперименты показали, что в этом нет никакой необходимо- сти. Отладочная трассировка сообщений выявила, что результатом работы функции ЗГю\лЛЛ/1пс1о\л/ является генерация сообщений \Л/М_812Е и \Л/М_МО\/Е, а обрабатывая сообщение \Л/М_812Е, си- стема всегда генерирует сообщение \Л/М_РА11\1Т. Поэтому вызов функции С1рс1а1:е\Л/1пс1о\л/ в приме- ре закомментирован. Обработка сообщений После вывода окна на экран программа должна подготовить себя для получения информации от пользователя. Эта информация обычно вводится с помощью кла- виатуры или мыши. \Упк1о\\ъ поддерживает «очередь сообщений» (тезза^е диеие) для каждой программы, работающей в системе. Любое действие пользователя система АУтйомга преобразует в «сообщение», которое помещается в указанную очередь. Программа извлекает сообщения из очереди, выполняя блок команд, извест- ный как «цикл обработки сообщений» (тезза^е 1оорУ. \л/М1е (бебМеззадеС&тзд, М1Л_1_, 0,0)) { ТгапзШеМеззадеС&тзд); 018рабсбМе88аде(&т8д); } гебигп тзд.мРагат; В этом фрагменте кода переменная т$д — это структура типа М56, которая оп- ределена в заголовочных файлах АУтйомга следующим образом: буресМ збгисб бадМЗб { НШО Ыпс1; // дескриптор окна, которому адресовано сообщение ЩИТ теззаде; // номер (идентификатор) сообщения АРАРАМ мРагат; // параметр сообщения мРагат ЬРАРАМ 1Рагат; // параметр сообщения 1Рагат ОШРО б1те; // время отправки сообщения Р01ИТ рб; // позиция курсора (в экранных координатах) в момент //отправки сообщения } М86; Интерпретация параметров \л/Рагат и 1Рагат зависит от номера сообщения. Тип данных Р01МТ используется для представления точки парой ее координат:
Турес1еТ кадРснгК { ЬСЖ х; ЬСЖ у; } РО1ИТ; Извлечение очередного сообщения осуществляется с помощью функции БеВЧеззаде, имеющей прототип: В001 бе^МеззадеСЬРМЗб 1рМзд, НШО Шпс1, 1ЛМТ мМздП ИегМ1п, 1ЛИТ мМздПИегМах) □ Параметр 1рМзд задает адрес структуры типа М5Б, в которую помещается выб- ранное сообщение. □ Параметр Н\Л/пс1 содержит дескриптор окна, принимающего сообщение. Обыч- но значение этого параметра равно МОП, что позволяет выбрать сообщения для любого окна приложения. □ Параметр хл/Мзд Н1±егМ1 п указывает минимальный номер принимаемого сообще- ния. Обычно он имеет нулевое значение. □ Параметр мМздЕИТегМах задает максимальный номер принимаемого сообщения и тоже обычно имеет нулевое значение. Если оба последних параметра равны нулю, то функция выбирает из очереди любое очередное сообщение. Функция БеВЧеззаде возвращает значение ТК11Е при извлечении любого сооб- щения, кроме одного — ММ_СЦИТ. Получив сообщение ММ_СЦПТ, функция возвра- щает значение ЕА1.5Е. В результате этого происходит немедленный выход из цик- ла, и приложение завершает работу, возвращая операционной системе код возврата тзд.хл/Рагат. Но какова дальнейшая судьба принятого сообщения? В теле цикла обработки сообщений можно увидеть вызов двух функций: Тгап$1а1:еМе$$аде и О1’$ра1:сНМе$$аде. Строго говоря, вызов Тгап$1а1:еМе$$аде нужен только в тех приложениях, ко- торые должны обрабатывать ввод данных с клавиатуры. Дело в том, что для обес- печения независимости от аппаратных платформ и различных национальных раскладок клавиатуры в АУшсЕпуз реализована двухуровневая схема обработки сообщений от символьных клавиш. Сначала система генерирует сообщения о так называемых виртуальных клавишах, например: сообщение ММ_КЕУООММ — когда клавиша нажимается, и сообщение ММ_КЕУ11Р — когда клавиша отпускает- ся. В сообщении ММ_КЕУООММ содержится также информация о так называемом скан-кодех нажатой клавиши. Функция Тгап$1а1:еМе$$аде преобразует пару сообщений, ММ_КЕУООММ и ММ_КЕУ11 Р, в сообщение ММ_СНАК, которое содержит код символа (доРагат) в виде значения типа ТСНАК1 2. Сообщение ММ_СНАК помещается в очередь, а на следующей итера- ции цикла функция БеВЧеззаде извлекает его для последующей обработки. Наконец, функция О1’$ра1:сНМе$$аде передает структуру тзд обратно в АУшсЕпуз. Очередной ход — за АУтсЕпуз. АУтсЬм^з отправляет сообщение для его обработ- ки соответствующей оконной процедуре, вызывая ее как функцию обратного вызова. 1 Код, идентифицирующий нажатую клавишу и зависящий от аппаратной платформы. 2 Тип ТСНАК интерпретируется как скат, если макрос 13МСОВЕ не определен. В ином случае он ин- терпретируется как \л/сНаг_1! (Птсойе-символ).
Следует обратить внимание на то, что сообщение передается оконной проце- дуре именно того окна, которому оно было адресовано. Это может быть или \Л/пд Ргос (если сообщение адресовано главному окну), или оконная процедура некоторого диалогового окна, или оконная процедура одного из стандартных элементов уп- равления, спрятанная в недрах АУтскпуз (то есть в ВЬЕ). После возврата из оконной процедуры АУшсЬм^ передает управление опера- тору, указанному за вызовом О1’$ра1:сНМе$$аде, то есть на начало цикла доНйе, и ра- бота цикла продолжается. Как уже говорилось ранее, цикл обработки сообщений функционирует до тех пор, пока из очереди не поступит сообщение ММ_СЦ11Т. Появление этого сообще- ния вызывает немедленный выход из цикла и прекращение работы программы. Оконная процедура Реальная работа приложения осуществляется в оконной процедуре (\\1пс1о\\ ргосебпге). Оконная процедура определяет то, что выводится в клиентскую об- ласть окна, и то, как окну реагировать на пользовательский ввод. Заголовок оконной функции всегда имеет следующий вид: 1_КЕ81Л_Т САИВАСК МРгос(НШО Шпс1, 1Л1Х1Т теззаде, ПРАВАМ мРагат, ЬРАВАМ 1Рагат) Четыре параметра оконной процедуры идентичны первым четырем полям структуры М56. Первый параметр функции содержит дескриптор окна, получаю- щего сообщение. Во втором параметре указывается идентификатор сообщения. Для системных сообщений в \Уш32 зарезервированы номера от 0 до 1024. Третий и четвертый параметры содержат дополнительную информацию, которая распоз- нается системой в зависимости от типа полученного сообщения. Вместо имени \Л/пс1 Ргос можно использовать любое другое имя, но оно должно совпадать со значением поля 1рГп\Л/пс1Ргос структуры \л/с. В рассматриваемой программе имеется только одно окно класса МуС1а$$. Деск- риптор окна Н Ма1п\Л/пс1 получен вызовом функции Стеа1:е\Л/1’пс1о\л/. Поэтому при вы- зове оконной процедуры ее параметру Н\Л/пс1 всегда передается значение Н Ма1 пУУпд. Если же в программе создается несколько окон на основе одного и того же класса окна (и, следовательно, имеющих одну и ту же оконную процедуру), тогда пара- метру НУУпс1 будет передаваться дескриптор конкретного окна, получающего сооб- щение. Обычно программисты используют оператор змйсН для определения того, ка- кое сообщение получено и как его обрабатывать. Если сообщение обрабатыва- ется, то оконная процедура обязана вернуть нулевое значение. Все сообщения, не обрабатываемые оконной процедурой, должны передаваться системной функции ОеГУУп пс1о\л/Ргос. В этом случае оконная процедура должна вернуть то значение, которое возвращает ОеГУУ1’пс1о\л/Ргос. Таким образом, программист пишет код только для тех сообщений, которые нуждаются в нестандартной обработке. В программе Не11о1 таких сообщений все- го три: ММ_РА1МТ, УУМ_С1_05Е и \Л/М_ОЕ$ТКОУ. Обработка сообщения УУМ_РА11ЧТ Обработка сообщения ММ_РА1МТ крайне важна для программирования под \Уш- Это сообщение уведомляет программу, что часть или вся клиентская область
окна недействительна (тса1й!) и ее следует перерисовать. Во второй главе тема обработки сообщения ММ_РА1МТ будет рассмотрена более подробно, поэтому здесь излагается тот минимум, который необходим для понимания работы программы Не11о1. В каких ситуациях клиентская область окна становится недействительной? Очевидно, что при создании окна недействительна вся его клиентская область, поскольку в ней еще ничего не нарисовано. Это раз. Если вы меняете размеры окна, клиентская область также объявляется сис- темой недействительной, что обусловлено комбинацией стилей С5_НКЕПКАМ | С5_УКЕ0КАМ в поле зЕуЕе структуры класса окна. Это два. Когда вы минимизируете окно программы Не11о1, сворачивая его на панель за- дач, а затем снова разворачиваете до начального размера, то АУтйомга объявляет клиентскую область окна недействительной. Это три. Если вы перемещаете окна так, что они перекрываются, а затем закрытая часть окна вновь открывается, то АУтйомга помечает требующую восстановления кли- ентскую область как недействительную. Это четыре. Можно было бы продолжить, но оставим подробности для второй главы. Во всех перечисленных случаях, кроме первого, операционная система авто- матически помечает клиентскую область окна как недействительную, что влечет за собой посылку сообщения ММ_РА1МТ. В первом случае, когда окно только что было создано, аналогичный эффект достигается вызовом функции 5Но\л/\Л/1пс1о\л/. В процессе ее выполнения генерируются сообщения ММ_$12Е и ММ_М0\/Е, а обра- батывая ММ_$12Е, система автоматически генерирует сообщение ММ_РА1МТ. Завершив этот маленький теоретический экскурс, вернемся к анализу нашего кода. Обработку сообщения ММ_РА1МТ рекомендуется всегда начинать с вызова функ- ции Вед1пРа1пЕ ПОС = Вед1 пРа1 ггЬ(Шпс1, &рз); Первый параметр функции содержит дескриптор окна, полученный через ар- гумент оконной процедуры, а второй — адрес структуры р$ типа РА1МТ5ТК11 СТ. Поля этой структуры, заполняемые в результате выполнения функции Вед1пРа1пЕ в даль- нейшем используются операционной системой1. Для нас сейчас более важно воз- вращаемое функцией значение — это дескриптор так называемого контекста уст- ройства. Контекст устройства (р1ес1се сопСехС) описывает физическое устройство выво- да информации, например дисплей или принтер. Этот важнейший объект графи- ческой подсистемы АУтйомга рассматривается подробно во второй главе. Сейчас достаточно понимания того, что контекст устройства — это некоторая внутренняя структура данных, сохраняющая часто используемые графические атрибуты, такие как цвет фона, перо, кисть, шрифт и им подобные параметры. Эти атрибуты ис- пользуются при вызове всех рисующих функций, получающих дескриптор НОС в качестве параметра. Прежде чем перейти к следующей строке программного текста, отметим два побочных эффекта выполнения функции Ведт’п Ра1пЕ 1 Более подробно структура РА1МТ5ТКОСТ рассматривается в главе 2.
Во-первых, обрабатывая вызов Вед1пРа1гй, система АУшбомгз обновляет фон клиентской области, если обновляемый регион помечен для стирания1. По умол- чанию для этого используется кисть, заданная в поле НЬгВаскдгоипс! структуры \Л/М0С1_А88ЕХ. Во-вторых, вызов функции В едт п Рат поделает всю клиентскую область действи- тельной (саИ(1\ то есть не требующей перерисовки. Это предотвращает повтор- ную генерацию системой сообщения ММ_РА1МТ до тех пор, пока вновь не произой- дет одно из событий, требующих перерисовки окна. После вызова Вед1пРа1гй в рассматриваемой программе следует вызов функ- ции бе^СИегйКесЕ предназначенной для получения размеров клиентской области окна: 6еЮ1 еггЬКесЬ (Г|1л/пс1, &гесЕ); Результат работы функции помещается в переменную гее! типа ВЕСТ. Структу- ра ВЕСТ, описывающая прямоугольник (гес1ап&1е), определена в заголовочных фай- лах \У1пс1о\\ъ следующим образом: Сурес1еТ зШсД ЦадКЕСТ { ЬСЖ 1еИ; 1_СЖ 'Ёор; 1_(Ж г1дЩ; 1_(Ж ЬоНот; } ПЕСТ; Поля этой структуры задают координаты левого верхнего угла (1еТ±, 1ор) и пра- вого нижнего угла (пдЫ:, ЬоНот) прямоугольника. После выполнения функции 6е1:С11’еп1:Вес1: поля 1еТ± и Тюр всегда получают нуле- вое значение, а поля пдЫ: и ЬоНот содержат ширину и высоту клиентской области в пикселах. Информация о размерах клиентской области, сохраненная в перемен- ной гее!, используется далее в «рисующей» функции ОгамТехЕ В нашей программе все «рисование» сводится к выводу текста «Не11о, АУог1с1!» при помощи функции ОгамТех!:, имеющей следующий прототип: ВООЕ ОгамТехН НОС Нс1с, // дескриптор контекста устройства ЕРСТЗТВ 1р8Тг1пд, // указатель на символьную строку Ш пСоыгД, // длина текста ЕРРЕСТ 1рРесЕ, // указатель на ограничивающий прямоугольник Ы1МТ ыЕогттМ // флаги форматирования текста Функция2 выводит текст из строки 1р51:лпд в прямоугольную область, заданную структурой типа КЕСТ, используя метод форматирования, заданный параметром иЕогтаЕ Количество символов в выводимой строке задается параметром пСоипЕ Если установить значение п Со и Ей в -1, то система сама определит длину строки 1р81:ппд по завершающему нулевому символу. Но в этом случае программист должен поза- ботиться о том, чтобы строка действительно завершалась нулевым байтом. На месте последнего параметра функции задан набор флагов 0Т_$1М61_Е1_1МЕ | ОТ.СЕМТЕК | ОЦ/СЕМТЕК, значения которых определяются в заголовочных файлах 1 Информация об этом факте хранится в одном из полей структуры р$. 2 Здесь приводятся минимальные сведения о работе функции ОгамТех!:. Подробно эта функция разби- рается в главе 2.
\У1пс1о\\ъ. Флаги показывают, что текст будет выводиться в одну строку, по цент- ру относительно горизонтали и вертикали внутри прямоугольной области, задан- ной четвертым параметром. Благодаря этому текст «Не11о, АУогШ!» выводится в центре клиентской области. Когда клиентская область становится недействительной (например, при из- менении размеров окна), \Л/пс1Ргос получает новое сообщение ММ_РА1МТ. Обраба- тывая его, программа вновь вызывает функцию 6е1:С11 егйКес!: и поэтому рисует текст опять в центре окна. После окончания работы с графическими функциями в блоке обработки сооб- щения ММ_РА1МТ необходимо освободить полученный контекст устройства с по- мощью функции ЕпдРашЕ Следует отметить, что при выводе текста используются текущие установки для шрифта, цвета фона и цвета текста. О том, как управлять этими атрибутами, будет рассказано во второй главе. Для тех, кто хочет экспериментировать с цветами тек- ста и фона немедленно, ниже приводится краткая справка. Атрибуты цвета и фона для выводимого текста В приложении может использоваться функция 5еНех^Со1ог для установки цвета текста (1ех1 со1ог) в клиентской области окна. Функция 5е1ВкСо1ог используется для установки цвета фона графического элемента {Ъаск^гоипс! со1ог), то есть цве- та, который отображается позади каждого символа. Еще можно регулировать ре- жим смешивания фона {Ъаск^гоипФтгхтоФе}, вызывая функцию ЗеЕВкМоде. Ее вто- рой параметр может принимать значение ОРАСЦ1Е1, если цвет фона графического элемента выводится поверх существующего фона окна, прежде чем будет выве- ден символ. Также можно использовать значение ТКАМ5РАКЕМТ2, при котором цвет фона графического элемента игнорируется, а символ выводится на существую- щем фоне. Если эти функции не вызывались, то в контексте устройства будут использо- ваны значения по умолчанию, приведенные в табл. 1.11. Таблица 1.11. Атрибуты цвета и фона текста по умолчанию Атрибут Значение по умолчанию Цвет текста Черный Цвет фона графического элемента Белый Режим смешивания фона ОРАС^Е Именно эти параметры и использовались при работе программы Не11о1. Обработка сообщений УУМ_СЬО5Е и УУМ_ОЕ5ТНОУ Сообщение ММ_С1_О5Е появляется, когда пользователь щелкает мышью на кнопке закрытия окна или нажимает комбинацию клавиш А11+Е4. Вообще, обработка это- го сообщения не обязательна: если она отсутствует, то функция ОеЙЛ/1 пс1о\л/Ргос вы- зовет по умолчанию функцию 0е$1:гоуМ1’пс1о\л/. Но если вы хотите предусмотреть вывод каких-либо предупреждающих сообщений типа «А вы точно уверены, что 1 Непрозрачный режим смешивания фона. 2 Прозрачный режим смешивания фона.
хотите это сделать?», то данное место — самое подходящее. Ибо окно еще не раз- рушено, а вот после сообщения ММ_ОЕ$ТКОУ окна на экране уже нет. Функция ОезТнгоуУУп’пдохл/, оправдывая свое имя, разрушает указанное в ее пара- метре окно. Для этого она посылает окну сообщение ММ_ОЕ$ТКОУ. Если у данного окна есть дочерние окна, то функция посылает ММ_ОЕ$ТКОУ сначала ему, а потом уже дочерним окнам. Функция завершает свою работу только после уничтоже- ния всех дочерних окон. Когда главное окно приложения получает сообщение ММ_ОЕ$ТКОУ, то оконная процедура должна позаботиться о том, чтобы приложение корректно «покинуло сцену». Для этого вызывается функция РозШийМеззаде, посылающая сообщение ММ_СЦ11Т. Ну а что происходит в цикле обработки сообщений при появлении ДОМДЦДТ, вы уже знаете. Разрешите вас поздравить: мы завершили анализ текста программы Не11о1 — первого варианта приложения «Не11о, АУог1с1!». Как, будет еще и второй вариант?.. — Не волнуйтесь, пояснения к нему будут гораздо короче. Программа «Не11о, ЛЛЛог1с1!» — второй вариант Программу «Не11о, АУог1с1!» часто используют как каркас нового АУхпбом^з-прило- жения. Иными словами, начало работы над новой программой обычно происхо- дит по такому сценарию: вы создаете новый проект, открываете новый пустой файл для функции п Ма1 п, копируете в него содержимое файла Не11о1.срр1, а потом вно- сите необходимые изменения и дополнения. Но такой способ работы, мне кажется, должен оскорблять чувства программи- ста, пишущего на С++. Почему бы не воспользоваться известными средствами языка, позволяющими реализовать повторное использование кода гораздо более изящным способом? Вы уже догадались, что речь идет о программировании с клас- сами? — Да, конечно. Здесь предлагается одна из возможных реализаций этой идеи. Класс К\Л/пс1, пред- ставленный ниже, инкапсулирует код, отвечающий за регистрацию оконного класса, создание окна и показ его на экране. Соблюдая традицию [9], будем размещать код для каждого нового класса в двух файлах: интерфейс — в файле с расширением .Н, а реализацию — в файле с расширением .срр. Таким образом, программа реализуется как многофайловый проект, приведенный в листинге 1.22. Листинг 1.2. Проект Не11о2 ////////////////////////////////////////////////////////////////////// // К1л1пс1. И #1пс1ис1е <\л/1 пс1о\л/8. И> С1й88 К1л!пс1 { 1 Не писать же все заново! 2 Если у вас есть вопросы по созданию многофайловых проектов, то обратитесь к приложению 1.
риЫ 1с: К1л/пс1 (1РСТ5ТК \л/1 псЫМате, Н1М5ТАМСЕ И1п8Р, 1 пр стОЗМом, ЕЕЕЗСЕТ (ШИАР1 *рОпОРпос) (Н1л1МВ,01МТ,ОРАРАМ, ЕРАРАМ), ЕРСТЗТР тепиМате = МОЕЕ, 1пР х = С1л/_иЗЕ0ЕЕАиЕТ, 1 пр у = о, 1 пр \л/1 сНаН = Си/_ЫЗЕОЕЕАЫЕТ, 1 пр Ие1дИР = 0, 01 ИТ с! аззЗ-ЕуЧе = С8_НРЕ0РА1л1 | СЗ_7ЕЕ0РА1л1, 0ШР0 м1 псЫЗРу! е = 03_07ЕРЕАРРЕШIМВОО, НШО МРапепР = МОЕЕ); НШО СеРШпсК) { пеРипп ШпО: } рпоРесРеО: ШШ Шп0; ШОСЕАЗЗЕХ мс: }; ////////////////////////////////////////////////////////////////////// // КШО.срр #1пс1ис1е "КОпОТ" К1л/пс1::К1л/пс1(ЕРСТЗТР \л/1 псЫМате, Н1МЗТАМСЕ М1п8Р, 1 пр стОЗИом, ЕРЕЗОЕТ (ШМАР1 *рШс1Рпос) (ШШ,01ИТ,АРАРАМ, ЕРАРАМ), ЕРСТЗТР тепиМате, 1 пр х, 1 пр у, 1 пр м1 с1РИ, 1 пр Ие1дИР, 01 МТ с1а883Ру1е, ВООВВ \л/1 пОомЗРу 1 е, НШВ МРапепР) { сМап 8хС1аззМатеЕ] = "К0пс1С1а88": мс.сЬ31хе мс.зРу1е мс.1рРпОпОРпос мс.сЬСНЕхРпа мс.сЬОпОЕхРпа мс.МпзРапсе мс.Мсоп мс.МСипзоп = з1 хеоРЫс): = с1а883Ру1е; = рОпОРпос: = 0: = 0: = М1п8Р: = Еоас11соп(М0ЕЕ, 101_АРРЕIСАТ1 ОМ); = ЕоаОСипзоп(МОЕЕ, ЮС_АРР0Ю ; мс.МЬпВаскдпоипО = (НВРОЗН)ЗеРЗРоскОЬдесР(Ш1ТЕ_ВР03Н); \л/с. 1 рзгМепиМате = тепиМате: мс.1р8хС1а88Мате = 8хС1а88Мате: мс.М1сопЗт = Еоаб1соп(МОЕЕ, IВ1_АРРЕIСАТ1ОМ); // Регистрируем класс окна 1Р (!Ред18РегС1а88Ех(&\л/с)) { сМап тсдЦОО] = "СаппоР гед18Рег с1а88: ": 8РпсаР(т8д, 8хС1а88Мате): Ме88адеВох(М0ЕЕ, тсд, "Еппоп", МВ_0К): пеРипп: } // Создаем окно Шпс1 = СпеаРеШ п0ом(8хС1 аззМате, м1п0омМате, \л/1 пОомЗРу! е, х, у, мЮРМ, Ие1дИР, МРапепР, (НМЕМО)МОЕЕ, М1п8Р, МОЕЕ): 1Р (!И1л1пс1) { сМап РехР[100] = "СаппоР спеаРе м1п0ом: ": 8РпсаР(РехР, \л/1пс1о\л/Мате): Ме88адеВох(М0ЕЕ, РехР, "Еппоп", МВ_ОК): пеРипп: продолжение
Листинг 1.2 {продолжение} } // Показываем окно 8Гю\л/1л/1 пс1о\л/(Г|1л/пс1, спкОом); } ////////////////////////////////////////////////////////////////////// // Не11о2.срр #1пс1ыс1е <\л/1 пс1о\л/8. П> #1пс1ыс1е "Шпс1.Г|" 2КЕ81ЛТ САЕЕВАСК ШЬРгос(НШО, П1МТ, АРАРАМ, 1РАКАМ); //============================ 1пТ М1МАР1 ШпМаап(Н1МЗТАМСЕ ЫпзТапсе, Н1М8ТАМСЕ ПРгеу1пзТапсе, ЕРЗТЕ 1рСтсИ1пе, 1пТ пСтбЗГюУл/) { МЗЗ тзд; К1л/пс1 та1п1л1псК"А Не11о2 аррИсаЫоп", ЫпзТапсе, пСтсЕПом, 1л1пс1Ргос); \л/М1е (СеТМеззадеС&тзд, М1Л_1_, 0, 0)) { ТгапзШеМеззадеС&тзд); ОтзраТсПМеззадеС&тзд); } геТыгп тзд.мРагат; } //=================================================================== ЕЕЕЗПЕТ САЕЕВАСК Шс1Ргос(ШИВ Шпс1, ПIИТ ыМзд, ПРАВАМ мРагат, ЕРАВАМ 1Рагат) { НОС ВВС; РА1МТЗТВПСТ рз; ВЕСТ гесТ; Ш изегВер!у; 8\л/1ТсП (иМзд) { сазе Ш_РА1МТ: ПОС = Вед1пРа1пТ(Шпс1, &рз); СеТСП 1 епТВесТ(Шпс1, йгесТ); ОгамТехШОС, "Не11о, 1л1ог1с1!", -1, йгесТ, 0Т_311\13ЕЕЕ1МЕ | ОТ_СЕМТЕВ | 0Т_7СЕМТЕВ ); Епс1Ра1пШ1л1пс1, &рз); Ьгеак; сазе Ш_СЬ08Е: изегВер!у = МеззадеВох(Шпс1, "А вы уверены в своем желании закрыть приложение?", МВ-УЕЗМО | МВ_1С0Ма0ЕЗТ10Ю; 1Т (ЮУЕ8 == ызегВер1у) ОезТгоуШ пс1о\л/(Шпс1); Ьгеак; сазе Ш_0Е8ТВ0У: РозТОьп ТМеззаде(О); Ьгеак; с1еГаы1 Т:
Функции поддержки окон 49 ге^игп ОеТШпсЫРгос(Шпс1, иМзд, мРагат, 1Рагат); } ге^игп 0; } ////////////////////////////////////////////////////////////////////// Обратите внимание на следующие моменты предложенного решения: □ Заголовок конструктора класса К\Л/пс1 содержит 12 параметров. Первые четы- ре параметра являются обязательными, а остальные имеют значения по умол- чанию. □ Четвертый параметр является указателем на функцию, принимающую четыре аргумента и возвращающую значение типа 1_КЕ$1Л_Т. Здесь обязателен специ- фикатор УУ1ИАР1 или эквивалентный ему спецификатор___________$1с1са11. □ Код, отвечающий за подготовку и создание окна, размещен в теле конструкто- ра класса КУУпс1. □ В теле функции УУтпМатп определяется объект таппУУпс! класса КУУпс!, после чего остается только записать цикл обработки сообщений. □ В оконной процедуре усложнена обработка сообщения ММ_С1_О5Е по сравне- нию с первой версией программы. При помощи функции Ме$$адеВох пользова- телю предлагается подтвердить его желание закрыть приложение. Благодаря флагу МВ_УЕ$МО диалоговое окно будет содержать две кнопки: УЕ5 и МО. При щелчке мышью на кнопке УЕ5 функция возвратит значение ЮУЕ5, и только в этом случае будет вызвана функция Ое$1гоу\Л/1’пс1о№. Откомпилируйте эту программу (проект Не11о2), чтобы убедиться в ее рабо- тоспособности. Поэкспериментируйте с передачей других значений аргументов конструктору объекта татпУУпд. Например, замените строку К1л1пс1 ша1 п1л1пс1 ("Не11 о аррПсаШп", ИТпзРапсе, пСгткОом, МпбРгос); на строку К1л1пс1 ша1 п1л1пс1 ("Не11 о арр!1саР1оп", ЩпзРапсе, пСгткОом, МпбРгос, М1)1_1_, 50, 100, 200, 150); и посмотрите на поведение измененной программы. Разработка второго варианта программы «Не11о, АУог1с1!» завершена. Реализо- ванный в ней класс КУУпс! можно использовать для сокращения кода новых прило- жений. ВНИМАНИЕ--------------------------------------------------------------------------- Если вы будете использовать класс ЮЛ/пф то подключите к вашему проекту файлы К\Л/псЫэ и К\Л/пс1.срр, а также добавьте директиву #1пс1ис1е "К\Л/пс1.1а" в том файле, где он используется. Функции поддержки окон АУш32 АР1 предоставляет широкий набор функций, которые позволяют менять размеры, расположение и характеристики отображения окна. В табл. 1.12 приве- дены наиболее употребительные функции.
Таблица 1.12. Функции поддержки окон Функция Назначение Вппд\Л/1Пс1о\л/ТоТор Активизирует окно и переносит его в верхнее положение, если оно находится позади других окон С1озе\Л/1пс1о\л/ ЕпаЫе\Л/1пс1о\л/ Сворачивает окно Разрешает или запрещает окну принимать ввод данных от мыши или клавиатуры Нпс1\Л/1пс1о\л/ Находит окно верхнего уровня по заданным имени класса и имени окна Се1Ас11Уе\Л/1Пс1о\л/ ОеЮаззТпГо ОеЮаззкопд Выбирает дескриптор активного окна Извлекает информацию о классе окна в структуру типа \ЛЛ\ЮС1_А88 Выбирает специфицированное по индексу значение из структуры типа \Л/Ы0С1_А88ЕХ Се1СНепЩес1 Се1Росиз Се1Рагеп1 6ейЛ/1пс1см/ 6ейЛ/1пс1см/1пГо 6ейЛ/1пс1см/1_опд 6ейЛ/1пс1см/кес± 15СГн1с1 Получает координаты клиентской области окна Выбирает дескриптор окна, имеющего фокус ввода Выбирает дескриптор родительского окна Выбирает дескриптор окна Извлекает информацию об окне в структуру типа \Л/1Ы00\Л/1ЫР0 Выбирает указанное по индексу значение из данных окна Получает координаты левого верхнего и правого нижнего углов окна Определяет, является ли окно дочерним по отношению к данному родительскому окну 151С0П1С 1з2оотес1 15\Мпс1см/ЕпаЫес1 Моуе\Л/1Пс1о\л/ 8е1Асйуе\Л/1пс1см/ 8еЮа551_опд Проверяет, свернуто ли окно Проверяет, развернуто ли окно Проверяет, разрешено или запрещено окно Перемещает и изменяет размеры окна Делает окно активным Замещает указанным 32-битным значением прежнее значение ука- занного поля в структуре \Л/Ы0С1_А88ЕХ для класса, к которому отно- сится указанное окно 8е1Росиз 8ейМпс1см/1_опд Придает окну фокус ввода Изменяет атрибуты указанного окна (первоначально заданные функцией Сгеа1е\Мпс1о\л/) 8ейМпс1см/Ро5 Изменяет одновременно размеры, позицию и расположение окна относительно других окон 5еил/1пс1оууТех1: ЗНо\лЛЛ/1пс1о\л/ С1рс1а1:е\Л/1пс1о\л/ Изменяет текст заголовка окна Отображает, скрывает или изменяет состояние показа окна Обновляет клиентскую область окна при помощи посылки сообщения \Л/М_РА1Г\1Т непосредственно оконной процедуре, минуя очередь сооб- щений приложения Часто используемые сообщения В табл. 1.13 приведены сообщения, обработка которых в оконной процедуре встре- чается наиболее часто.
Таблица 1.13. Часто используемые сообщения Сообщение Назначение \Л/М_С1_О5Е Уведомляет окно о том, что оно должно быть закрыто. Сообщение может быть использовано для вывода запроса пользователю с предложением подтвердить завершение работы, прежде чем будет вызвана функция Оез1:гоуУ\/1пс1о\л/. По умолчанию (если обработка \Л/М_С1_О8Е не предусмо- трена) ОейЛ/1пс1о\л/Ргос вызывает Оез1:гоу\Л/1пс1о\л/ \Л/М_СОММАШ Сообщение посылается, когда пользователь выбирает команду меню или посылает команду из элемента управления \Л/М_СНЕАТЕ Посылается, когда приложение создает окно вызовом функции Сгеа1е\Л/1пс1о\л/ или Сгеа1е\Л/1пс1о\л/Ех. Оконная процедура получает это сообщение, когда окно уже создано, но еще не показано на экране. Поэтому, обрабатывая это сообщение, можно изменить характеристики окна либо выполнить некото- рые инициализирующие действия, например открыть необходимые файлы. После обработки сообщения приложение должно вернуть нулевое значение для продолжения процесса создания окна. Если приложение вернет зна- чение -1, то окно не будет создано, а функция Сгеа1е\Л/1пс1о\л/ вернет значение 1\1111_1_ \Л/М_ОЕ8ТНОУ Посылается оконной процедуре уничтожаемого окна после того, как окно удалено с экрана \Л/М_11\11ТО1А1_ОС Сообщение посылается оконной процедуре диалогового окна1 непосредствен- но перед тем, как оно будет отображено на экране. Обработка этого сообще- ния позволяет произвести инициализацию тех объектов, которые связаны с диалоговым окном \Л/М_МОУЕ \Л/М_РА11\1Т \Л/М_812Е \Л/М_Т1МЕР Посылается окну, которое переместилось на экране Посылается окну, содержимое которого требует перерисовки Посылается окну, размеры которого изменились Уведомляет окно о том, что некоторый системный таймер, установленный функцией ЗеСПтег, отсчитал заданный ему интервал Модификация характеристик окна Если окно уже создано, например, в виде объекта класса К\Л/пс1, то его характерис- тики могут быть изменены с помощью одной из двух функций: 5е1:С1а$$1_опд или 5е1:\Л/1’пс1о\л/1опд. Рассмотрим применение функции 5е1:С1а$$1_опд, предназначенной для измене- ния одного из полей структуры ММ 0СЕА55ЕХ. Напомним, что указатель на эту струк- туру с начальными значениями ее полей передавался функции Кед1’$1:егС1а$$Ех для регистрации класса окна. Функция ЗеЕСЕаззЕопд имеет следующий прототип: ОШКО ЗеЦС1 аззЬопд( НШО Шпс1, Ш пМех, // дескриптор окна // индекс значения, которое нужно изменить 1_(Ж с1\л/Ме\л/1_опд // новое значение ); Диалоговые окна рассматриваются в главе 7.
Здесь п1пс1ех — смещение (с отсчетом от нуля) дополнительных данных1 класса, которые должны быть изменены, или одно из значений, приведенных в табл. 1.14. Таблица 1.14. Значения параметра п!пс1ех функции 5е1С1а881_опд Значение Описание 6СЫ-1ВкВАСК6кО11Ш 6С1__НС1Ж8Ок осы-псоы 6С1__Н1СОЫ5М 6С1__МЕ1\111ЫАМЕ 6С1__8ТУ1_Е ОСЫЛ/ЫОРНОС Изменить дескриптор кисти цвета фона Изменить дескриптор курсора Изменить дескриптор пиктограммы Изменить дескриптор малой пиктограммы Изменить адрес имени ресурса меню Изменить стиль класса окна Изменить адрес оконной процедуры, связанной с классом Покажем на примере проекта Не11о2, как можно модифицировать характерис- тики главного окна приложения. Добавьте в текст функции \Л/пс1Ргос, а именно в блок оператора змйсН, следую- щие строки: сазе 1л/М_СКЕАТЕ: Зе-ЕС 1 аззЬопд(Шпс1, ССЕ_НВЕВАСКСЕОСИ0, (ЬСЖ) СгеаЕеЗо! 1 с1Вги8Н(ВОВ(200,160,255))); Ьгеак; Посмотрите, как изменится вид окна приложения после сделанной модифи- кации2. А теперь добавьте после строки ПОС = Вед1 пРа1 пЕ(Шпс1, &рз); следующую строку: Зе-ЕВкМобеСГЮС, ТВАИЗРАВЕМТ); и посмотрите на полученный результат. Особенности программирования для УУтс1о№ В разделе «Базовые концепции» уже говорилось о том, что в основе взаимодей- ствия приложения и операционной системы лежит механизм сообщений. Для каж- дого приложения Ш1пс1о\\-з создает очередь сообщений приложения и направляет в эту очередь все сообщения, адресованные окнам приложения. В то же время в описании функции 11рс1аЕе\Л/1’пс1о\л/ (см. табл. 1.12) отмечается, что в процессе ее выполнения \У1пс1о\\ъ посылает сообщение ММ_РА1МТ непосредственно оконной процедуре, минуя очередь сообщений приложения. Внимательный читатель на- верняка заметил это противоречие. На самом деле противоречия нет, а есть два типа сообщений, с которыми работает АУтскпуз. 1 Дополнительные данные — это те байты, количество которых задано в поле сЬС1$ЕхНа. 2 Вызов функции СгеаЕеЗоНдВгизН (КСВ (200, 160, 255)) создает сплошную кисть, цвет которой обра- зован тремя цветовыми составляющими заданной интенсивности: красной (200), зеленой (160) и синей (255). В результате должен получиться сиреневый цвет. Более подробно создание кисти с использованием функции СгеаТеЗоНс!ВгизН рассматривается в главе 2.
Синхронные и асинхронные сообщения Асинхронными сообщениями называются сообщения, которые помещает в очередь сообщений приложения. Такие сообщения извлекаются и диспетчери- зуются в цикле обработки сообщений. Синхронные сообщения передаются непосредственно окну, когда АУтйомга вы- зывает оконную процедуру. Приведем примеры асинхронных сообщений. Прежде всего, к ним относятся сообщения о событиях пользовательского ввода, таких как нажатие клавиш (ММ_КЕУООММ и ММ_КЕУ11Р), перемещение мыши (ММ_МО115ЕМО\/Е) или щелчок левой кнопкой мыши (ММ_ЕВ11ТТОМООМ1\1). Кроме этого, асинхронными являют- ся сообщения от таймера (ММ_Т1МЕК), сообщение о необходимости перерисовки клиентской области (ИМ_РА1МТ) и сообщение о выходе из программы (ММ_СЦ11Т). Приложение может само направить в очередь асинхронное сообщение, вызвав функцию Ро$1:Ме$$аде. Остальные сообщения, как правило, являются синхронными. Во многих слу- чаях синхронные сообщения являются результатом обработки асинхронных со- общений. Вообще, когда синхронное сообщение обрабатывается функцией ОеЛЛ/1пдомРгос, АУтйомга часто генерирует другие сообщения, направляемые окон- ной процедуре. Приложение также может послать синхронное сообщение, выз- вав функцию 5епс1Ме$$аде. Таким образом, оконная процедура должна быть повторно входимой (гееп1гап1 ргоррат). Это означает, что АУтйомга часто вызывает функцию \Л/пс1 Ргос с новым сообщением, появившимся в результате вызова ОеЙЛ/1пс!о\л/Ргос из \Л/пс1 Ргос при об- работке предыдущего сообщения. В большинстве случаев повторная входимость оконной процедуры не создает каких-то особых проблем, но знать об этом по- лезно. Рассмотрим, например, какие события произойдут после щелчка кнопкой мыши на кнопке закрытия окна приложения Не11о1. Все начнется с того, что \У1пс1о\\ъ отправит синхронное сообщение ММ_$У$С0ММАМ0 оконной процедуре \Л/пс1Ргос. Оконная процедура передаст это сообщение на обработку функции ОеГ\ЛНпс1о\л/Ргос. Функция ОеГ\ЛЛпс1о\л/Ргос реагирует на него, отправляя сообщение УУМ_С1_О5Е оконной процедуре. В рассматриваемом примере предусмотрена об- работка этого сообщения — вызывается функция 0е$1:гоуМ1’пс1о№. Однако если не предусмотреть эту обработку, то функция ОеГ\ЛйпдомРгос сделала бы то же самое, то есть вызвала бы функцию 0е$1:гоуМ1 пс!о\л/. Функция 0ез1:гоу\Л/1’пс1о\л/ заставляет Ш1пс1о\\^ отправить оконной процедуре сообщение ММ_ОЕ$ТКОУ. И наконец, \Л/пс1Ргос, обрабатывая это сообщение, вызывает функцию РозШийМеззаде, кото- рая посылает асинхронное сообщение ММ_СЦ11Т в очередь сообщений приложе- ния. Сообщение ММ_СЦЛТ прерывает цикл обработки сообщений в М1пМа1п, и приложение завершает свою работу. Сообщения не похожи на аппаратные прерывания. Пока оконная процедура обрабатывает одно сообщение, программа не может быть прервана другим сооб- щением. Только в том случае, когда функция, выполняемая в теле оконной проце- дуры, генерирует новое синхронное сообщение, оно вызывает повторно оконную процедуру, и только после его обработки выполнение прерванной функции про- должается.
Посылка сообщений из приложения Основным средством программного взаимодействия между разными окнами при- ложения и даже между разными приложениями является посылка сообщений. Приведем краткие сведения об основных функциях, используемых для отправки сообщений. Функция 5епс1Ме$$аде Эта функция посылает синхронное сообщение указанному окну или нескольким окнам. Она имеет следующий прототип: 1КЕЗЫ1Т ЗепбМеззадеС НШО Шпс1, // дескриптор окна-получателя ЩИТ Мод, // код сообщения ФРАКАМ мРагат, // первый параметр сообщения ЬРАРАМ 1Рагат // второй параметр сообщения ); Параметры функции те же, что и параметры, передаваемые в оконную процедуру. Когда приложение вызывает 5епс1Ме$$аде, АУшскл^з, в свою очередь, вызывает оконную процедуру с дескриптором окна Н\Л/пс1, передавая ей эти четыре параметра. После того как оконная процедура заканчивает обработку сообщения, система АУшсклуз передает управление инструкции, следующей за вызовом 5епс1Ме$$аде. Оконная процедура, которой отправляется сообщение, может быть той же самой оконной процедурой, другой оконной процедурой той же программы или даже оконной процедурой другого приложения. Если первый параметр функции имеет значение НММ0_ВК0А0СА5Т, то сообще- ние посылается всем окнам верхнего уровня, существующим в настоящий момент в системе. Параметры доРагат и 1Рагат содержат дополнительную информацию, интерпре- тация которой зависит от кода сообщения. Чтобы получить справочную инфор- мацию в М8^^ об интерпретации этих параметров, применяйте поиск по коду сообщения, то есть по идентификаторам ММ_РА1МТ, ММ_Т1МЕК, ММ_$ЕТЕОМТ и им по- добным. Функция 5епс11Чо1|ГуМе$$аде Функция 5епс1Мо1:йуМе$$аде имеет те же параметры, что и функция 5епс1Ме$$аде. Если окно, которому адресовано сообщение, создано вызывающим потоком, то поведение функции 5епс1Мо1:1’ГуМе$$аде не отличается от поведения функции 5епс1Ме$$аде. Она отправляет сообщение оконной процедуре и не возвращает уп- равление, пока оконная процедура не обработает это сообщение. Если же окно, которому адресовано сообщение, создано другим потоком, то функция 5епс1Мо1:йуМе$$аде передает сообщение указанной оконной процедуре и немедленно возвращает управление, не дожидаясь окончания обработки сообще- ния оконной процедурой. Функция Ро$1Ме$$аде Функция Ро$1:Ме$$аде посылает асинхронное сообщение указанному окну. Она име- ет те же параметры, что и функция 5епс1Ме$$аде. В отличие от 5епс1Ме$$аде, функция РозВЧеззаде не вызывает оконную проце- дуру, а отправляет сообщение в очередь сообщений потока, создавшего окно Н\Л/пс1.
После этого функция возвращает управление, не дожидаясь, пока поток обрабо- тает отправленное сообщение. Использование глобальных или статических переменных Иногда бывает нужно, чтобы информация, полученная в процессе обработки ка- кого-то сообщения, была сохранена и использована позже при обработке другого сообщения. Рассмотрим следующий пример. В программе Не11о1 размеры клиентской об- ласти окна были получены при помощи функции беКПегйКес!:. Но существует и другой способ получить эту информацию — обрабатывая сообщение ММ_$12Е. Параметр 1Рагат этого сообщения содержит в своем младшем слове значение ши- рины, а в старшем слове — значение высоты клиентской области окна. Допустим, что мы определили две локальные переменные в теле функции \Л/пс1Ргос: Ш и1(Ж Ие1д^Д; и добавили обработку сообщения ММ_$12Е, обеспечивающую сохранение соответ- ствующих значений: сазе Ш_812Е: = ЮШК0(1 Рагат); Не1 дН1а = Н1ШЕ0(1 Рагат); ге^игп 0; Здесь ЮУУОКО и НI\Л/О К 0 макросы, определенные в заголовочных файлах Ш1пс1о\\ъ; первый из них извлекает младшее слово, а второй — старшее слово из 32-разрядного аргумента. Закомментируем вызов функции беШтегйКес!: в блоке обработки сообщения ММ_РА1МТ и вместо него вставим следующий код: гесЫеГЕ = 0; гесДЛор = 0; гесЕ. г1д1тЕ = \л/1 сИаН: гесЕ. ЬоШт = Ие1д^Д; После компиляции и запуска программы будет показано пустое окно, не со- держащее никакого текста. Неудача объясняется тем, что при повторном вызове \Л/пс1Ргос значения ее ло- кальных переменных от предыдущего вызова не сохраняются. Но эту ошибку легко исправить. Надо лишь объявить переменные и Не1дН1: либо как статические (з-ЬаНс), либо как глобальные. ВНИМАНИЕ------------------------------------------------------------------- Если в оконной процедуре присваивание значения некоторой локальной переменной и дальней- шее использование этого значения разделены по разным ветвям оператора з^л/ИсН, то не забудь- те объявить эту переменную с классом памяти зЬайс! Другое возможное решение — использование глобальной переменной. Получение дескриптора экземпляра приложения Для многих функций АУш32 АР1 необходимо передавать дескриптор экземпляра приложения в качестве одного из параметров. Напомним, что значение этого
дескриптора Н1п$1:апсе функция иМа1 и получает от операционной системы через свой первый параметр. Если значение дескриптора ЫпзЕапсе используется в теле функции М1пМа1п, проблем никаких нет, если в теле другой функции — возникает вопрос, как полу- чить значение ЫпзЕапсе? Есть три способа решения проблемы. Первый способ (наихудший, с точки зрения стиля программирования на С++) — объявить глобальную переменную Н1М8ТА1Х1СЕ И1П8Ц; и в теле функции УУ1 и Ма1 и запомнить значение дескриптора НТпзЕаи се. Естественно, значение глобальной переменной будет доступно во всех других функциях. Во втором и третьем способах переменная НТизЕ объявляется как локальная. Во втором способе ее значение определяют при помощи функции СеЕСПаззЕопд: Шзб = (Н1М5ТАМСЕ)СеЦС1а881опд(Шпс1, 6С1_НМ001ЕЕ); В третьем способе для этого используется функция СеЕМоди1еНапсНе: Н1П8Ц = 6еЦМос1и’1 еНапсП е(ШЕЕ); Предотвращение зависания приложения в случае медленной обработки отдельных событий Предположим, что в вашей программе обработка какого-то события является очень ресурсоемкой, занимая процессор в течение нескольких минут. Несмотря на то что \У1пс1о\\ъ является многозадачной операционной системой, выделяющей при- нудительно кванты времени для выполнения других приложений, пользователь будет не в состоянии что-нибудь сделать с этой злополучной программой, пока не завершится указанная обработка. Он не сможет даже перетащить окно в дру- гое место экрана или закрыть приложение. Внешне это выглядит как зависание приложения. Вряд ли пользователю понравится такое поведение программы. Рассмотрим эту проблему на конкретном примере. Предположим, в приложе- нии имеется обработка некоторой команды меню (например, Р1ау) с помощью сле- дующей функции: уо1с1 0пР1ау() { \л/ГИ1е (! ТКеасЮаТза. еоТ()) { ГЕеасЮаба.геасКЬыГ, 512); ОоЗотеШпдО; // какая-нибудь обработка } } Здесь ГКеасЮа1:а — это объект класса йзВ'еат. В цикле доНйе осуществляются чтение файла ГКеасЮа1:а блоками по 512 байт и последующая обработка каждого блока с помощью функции ОоЗотеПйпд. Допустим, что размер файла достигает 5 Мбайт, а время выполнения функции ОоЗотеВйпд составляет 10 мс. Тогда тело цикла будет выполняться 5335040 /512 = 10420 раз, и возврат из функции ОпР1ау произойдет примерно через 105 с. В течение этого промежутка времени окно программы будет безвольно висеть на экране. Для решения проблемы нам нужен способ узнать, имеется ли какое-нибудь сообщение в очереди сообщений приложения или же она пуста. Если сообще- ние есть, нужно дать возможность АУшсклуз обработать его, если нет — можно продолжить выполнение цикла. АУш32 АР1 предлагает использовать для этого
функцию РеекМеззаде. Ее прототип практически идентичен прототипу функции БебМеззаде: В001 РеекМеззаде( 1РМ30 1рМзд, НШО Шпс1, ШИТ мМздГ11бегМ1п, ШИТ \л/МздП1бегМах, ШИТ мРетоуеМзд // адрес структуры с сообщением // дескриптор окна // первое сообщение // последнее сообщение // флаг удаления сообщения Данная функция возвращает ненулевое значение, если в очереди имеется со- общение. Различие прототипов функций заключается в последнем параметре, который определяет, как именно должно быть выбрано сообщение из очереди. Возможными значениями параметра мКетоуеМзд являются: □ РМ_МОКЕМО\/Е — сообщение остается в очереди; □ РМ_КЕМО\/Е — сообщение удаляется из очереди. Покажем, как нужно переделать функцию ОпР1ау, используя вызов РеекМеззаде, чтобы приложение не зависало: 7016 ОпР1ау(НШО И1л1пс1) { М86 теззаде; \л/М1е (! ТРеабОаба. еоТ()) { бРеасЮаба.геасКЬиТ, 512); // Предотвращение зависания приложения 16 (РеекМеззадеС&теззаде, Шпс1, О, О, РМ_РЕМО\/Е)) { ТгапзШеМеззадеС&теззаде); ОтзрабсИМеззадеС&теззаде); } ОоЗотеШпдО; // какая-нибудь обработка } } Если функция РеекМеззаде обнаруживает сообщение в очереди, то оно обраба- тывается так же, как и в основном цикле обработки сообщений. Данный инструментальный прием будет использован в приложении РгодгеззВаг, рассматриваемом в главе 8. Использование утилиты 5ру++ В процессе отладки приложений иногда бывает полезным увидеть, какие сообще- ния вырабатывает АУшсклуз в ответ на те или иные действия пользователя. В со- ставе интегрированной среды Ухзпа! Збпсйо 6 есть удобное инструментальное сред- ство для решения данной проблемы — утилита 8ру++. Утилиту можно вызвать при помощи команды меню интегрированной среды: ТооЕ ► 5ру++. Работа с утилитой 8ру++ описана в приложении 3.
601 — графический интерфейс устройства. Рисование линий, фигур, текста Графический интерфейс устройства (^гарИгсз (1ес1се шСет/асе (СВ1)) — это состав- ная часть АУш32 АР1, обеспечивающая графический вывод на устройства отобра- жения информации, такие как дисплей или принтер. АУшбом^-приложение не имеет непосредственного доступа к аппаратным устройствам. Вместо этого оно обращается к функциям СВ1, а СВ1 транслирует эти обращения к программным драйверам физических устройств, обеспечивая аппаратную независимость при- ложений (рис. 2.1). Код библиотеки СВ1 находится в файле дсй32.с111, то есть биб- лиотека является динамически загружаемой. Драйверы стандартных устройств поставляются как часть АУтскпуз, а драйверы специализированных устройств со- здаются производителями оборудованиями. Дисплей Принтер Принтер 2 Рис. 2.1. Соотношения между приложениями, С01 и драйверами устройств
Контекст устройства Взаимодействие приложения с СВ1 осуществляется при непременном участии еще одного посредника — так называемого контекста устройства. Контекст устройства (йетсе сопкехй) — это внутренняя структура данных, ко- торая определяет набор графических объектов и ассоциированных с ними атри- бутов, а также графических режимов, влияющих на вывод. В следующем списке приведены основные графические объекты: □ Перо (реп) для рисования линий. □ Кисть (Ъгизк) для заполнения фона или заливки фигур. □ Растровое изображение (Ыстар) для отображения в указанной области окна. □ Палитра (ра1еРе) для определения набора доступных цветов. □ Шрифт (/опй) для вывода текста. □ Регион (ге§юп) для отсечения области вывода. Если необходимо рисовать на устройстве графического вывода (экране дис- плея или принтере), то сначала нужно получить дескриптор контекста устрой- ства. Возвращая этот дескриптор после вызова соответствующих функций, пс1о\\5 тем самым предоставляет разработчику право на использование дан- ного устройства. После этого дескриптор контекста устройства передается как параметр в функции СВ1, чтобы идентифицировать устройство, на котором долж- но выполняться рисование. Контекст устройства содержит много атрибутов, определяющих поведение функций СВ1. Благодаря этому списки параметров функций СВ1 содержат толь- ко самую необходимую информацию, например начальные координаты или раз- меры графического объекта. Все остальное система извлекает из контекста уст- ройства. Когда в приложении Не11о1 вызывалась функция ОгамТех!:, то в ее параметрах нужно было указать только дескриптор контекста устройства, адрес строки с вы- водимым текстом, его длину, положение и размеры прямоугольной области для размещения текста, а также способ позиционирования текста внутри этой облас- ти. Однако при этом не указывались шрифт, цвет текста, цвет фона, режим сме- шивания фона, режим рисования и другие атрибуты, потому что все они являют- ся частью контекста устройства и имеют значения по умолчанию. И только если значения по умолчанию не устраивают разработчика, необходимо вызывать фун- кции СВ1, изменяющие значения соответствующих атрибутов. Типы контекстов устройства АУш32 АР1 поддерживает следующие типы контекстов устройства: □ контекст дисплея; □ контекст принтера; □ контекст в памяти (совместимый контекст); □ метафайловый контекст; □ информационный контекст.
Конечно, чаще всего используется контекст дисплея. Первое знакомство с ним состоялось, когда рассматривалась обработка сообщения ММ_РА1МТ в про- грамме Не11о1 (глава 1). Сообщение ММ_РА1МТ уведомляет программу, что часть или вся клиентская область окна недействительна и ее следует перерисовать. Понятие недействительного, или обновляемого, региона очень важно для взаи- модействия \У1пс1о\\ъ и приложения, поэтому, прежде чем продолжить более подробное рассмотрение контекста дисплея, мы остановимся кратко на следую- щей теме. Регионы №тс1ош5. Отсечение Для повышения эффективности работы АУтскпуз оперирует с несколькими типа- ми регионов. Идея заключается в том, чтобы рисовать именно в той части окна, которая требует обновления, а не перерисовывать все окно. Также регионы позво- ляют отсекать вывод той части графической информации, которая не может быть отображена в данный момент. Вообще полное изучение всей иерархии регионов и их взаимодействия является непростой задачей, требующей пространного из- ложения1. В то же время приведенное ниже упрощенное описание достаточно для понимания работы большинства функций \Уш32 СВ1. Обновляемый регион (ирйаЬе ге&оп), или, как его тоже иногда называют, недей- ствительный регион (теаИй ге^гоп) — это часть окна, которая требует обновления после возникновения тех или иных событий. Видимый регион (с1$1Ые ге&оп) — та часть окна, которую в данный момент ви- дит пользователь. Система изменяет видимый регион окна и в том случае, когда окно изменяет размеры, и в том случае, когда перемещение другого окна либо закрывает часть данного окна, либо открывает закрытую прежде часть. Регион отсечения {сИррт^ ге^гоп) ограничивает область, внутри которой сис- тема разрешает отображение графической информации. Когда приложение полу- чает контекст устройства при помощи функции Вед1пРа1пЕ система устанавлива- ет регион отсечения путем пересечения видимого региона и обновляемого региона. Приложение может ужесточить регион отсечения и ввести дополнительные огра- ничения при помощи вызова функции 5е±\Л/1 п до\л/ Кд п, 5е1есШ1рРа^Н или 5е 1ес±С11 р Кдп. Если при создании окна функцией Сгеа1:еМ1’пдо\л/ был использован стиль УУ5_С1_1РСН11_0КЕМ или \Л/5_СИР51ВИМ65, то это вносит дополнительные правила в определение видимого региона, исключая из него любое дочернее или любые «сестринские» окна. Благодаря этому рисование не затрагивает отображаемые об- ласти таких окон. Контекст дисплея Контекст дисплея создавать не нужно — об этом уже позаботилась операционная система. Нужно лишь получить его дескриптор. АУтйомга предоставляет для это- го два метода, применение которых обусловлено (извините за каламбур) программ- ным контекстом. 1 Интересующихся мы можем отослать к известной книге Фень Юаня «Программирование графики для АУшскиуз» [6].
Первый метод получения дескриптора контекста устройства Этот метод используется при обработке сообщения ММ_РА1МТ. Контекст устрой- ства получают вызовом функции Ведт’п Ра1гй, имеющей следующий прототип: НОС Вед1пРа1гтЬ( НШО Ыпс1, // дескриптор окна ОРРА1ИТЗТРНСТ 1Ра1п1с // указатель на структуру типа РА1МТЗТРНСТ ); В случае успешного завершения функция возвращает дескриптор контекста дисплея для клиентской области окна. Кроме этого функция заполняет поля структуры РА1МТ5ТК11 СТ, имеющей следу- ющее определение: Уурес1еТ з^гыс^ ЦадРА1 МТЗТРНСТ { НОС Нбс; // контекст устройства В000 ТЕгазе; // признак стирания фона клиентской области ВЕСТ гсРааггЬ: // границы недействительного прямоугольника ВОСЕ СВезбоге; ВОСЕ НпсОрсШе; ВУТЕ гдЬВезегуес1Е32]; } РА1МТЗТРНСТ: Последние три поля используются операционной системой. Поле гсРа1гй типа КЕСТ содержит координаты обновляемого прямоугольника в пикселах относительно левого верхнего угла клиентской области окна. Эти координаты либо определяются системой, либо задаются при вызове функции 1пуа11’с1а1:еКес1:. Регион отсечения в этом случае определяется посредством пере- сечения видимого региона и обновляемого прямоугольника. Поле ГЕгазе определяет, будет ли АУтскпуз обновлять фон недействительного ре- гиона. Чаще всего ГЕгазе имеет значение ТРОЕ, что означает стирание (обновление) фона. Когда обновляемый регион формируется вызовом функции IпуаПс1а1:е Кес! или 1пуаПс1а1:еКдп, один из параметров этих функций разрешает или подавляет стирание фона. Если задано стирание фона, то функция Вед1пРа1гй посылает оконной процедуре сообщение ММ_ЕКА$ЕВК6М0. Приложение может обработать это сообщение, чтобы отобразить однородный или растровый фон. Однако обычно оно обрабатывается по умолчанию функцией ОеЙЛ/1пс1о\л/Ргос, которая обновляет фон с использованием кисти, определенной в поле НЬгВаскдгоипс! класса окна. Следует отметить, что функция Вед1 п Рат гй сбрасывает обновляемый регион в МII 1_1_, или, другими словами, превращает недействительный регион в действительный. Это предотвращает повторную генерацию системой сообщения ММ_РА1МТ до тех пор, пока обновляемый регион вновь не изменится. До своего завершения функция Вед1пРа1гй посылает оконной процедуре еще одно сообщение — ММ_МСРА1МТ. Оно заставляет приложение перерисовать так на- зываемую неклиентскую область, которая представляет собой остальную часть окна — кроме клиентской области. Как правило, это сообщение обрабатывается также функцией ОеГ\Л/1пс1о\л/Ргос. Типовой процесс обработки сообщения ММ_РА1МТ выглядит следующим образом: сазе Ш_РА1МТ: РОС = ВедапРап ггЬ(Шпс!, &рз); [ использование функций 601 ] Епс1Ра1 гй (Шпс!, &рз); геРыгп 0;
После завершения операций рисования приложение должно вызвать функцию ЕпдРа1пЕ чтобы освободить контекст устройства. Примеры использования контекста дисплея в блоке обработки сообщения ММ_РА1МТ были приведены в листингах из первой главы. Второй метод получения дескриптора контекста устройства Иногда рисование должно происходить не в блоке обработки сообщения ММ_ РА1МТ, а при обработке других сообщений. Или, быть может, дескриптор кон- текста устройства нужен не для рисующих, а для иных функций (например, в качестве аргумента функции беНех^МеЫсз), вызываемых вне блока обработки сообщения ММ_РА1МТ. В таких ситуациях контекст устройства получают либо вы- зовом функции беЮС: Мс = 6еШ(Шпс1); либо вызовом функции Се1:\ЛЛ пс1о\л/ОС: Мс = 6еШпбомОС(Шпс1); Первая функция возвращает дескриптор контекста дисплея для клиентской области окна Н\Л/пс1, а вторая — для всего окна. Если в качестве аргумента Н\Л/пс1 передать значение МОП, то обе функции вернут дескриптор устройства для все- го экрана. По окончании работы с функциями СВ1 необходимо освободить контекст ус- тройства с помощью функции Ке1еа$еОС, например: ПОС = 6еШ(Шпс1); [ использование функций 601 ] 0е1еа8е0С(Шпс1, НОС); Заметим, что, в отличие от контекста устройства, полученного при помощи вызова Вед1пРа1пЕ контекст устройства, возвращаемый функцией беЮС, работа- ет с регионом отсечения, который равен всей клиентской области. Это значит, что рисование можно производить в любом месте клиентской области, а не только в недействительном прямоугольнике, если он вообще определен. В отличие от функции Вед1пРа1П±, функция беЮС не делает действительными какие-либо недействительные зоны. Обычно вторым методом получения дескриптора контекста дисплея пользу- ются программы текстовых редакторов, обрабатывая сообщения от клавиату- ры, а также программы рисования, обрабатывая сообщения от мыши. Это позво- ляет обновлять клиентскую область непосредственно в ответ на ввод информации с клавиатуры или от мыши и при этом не объявлять часть окна недействитель- ной для генерации сообщения ММ_РА1МТ. Тем не менее, подобные программы долж- ны уметь обновлять окно в любой момент, когда они получают сообщение ММ_РА1МТ. Но на этом наше знакомство с контекстом дисплея еще не закончилось. АУш32 АР1 предлагает к использованию два подтипа контекста дисплея, различающие- ся механизмами выделения и освобождения памяти1. 1 На самом деле есть еще и третий подтип — классовый контекст устройства, но он реализован лишь для совместимости с 16-разрядными версиями АУтсЬлуз и не рекомендован к примене- нию в 32-разрядных приложениях.
Подтипы контекста дисплея Любая из функций, Вед1пРа1п1:, БеЮС, 6е1:\Л/1’пс1одоОС, возвращает либо общий (соттоп), либо частный (^эпсаке) контекст устройства в зависимости от стиля, указанного в классе данного окна (поле 81у1е в структуре типа \Л/М0С1А55). Общий контекст дисплея Система предоставляет общий контекст дисплея по умолчанию, если в классе окна не указан явно стиль контекста устройства. Обычно общий контекст использу- ется, если рисование в приложении не требует интенсивных изменений атрибу- тов контекста, которые могли бы занимать много процессорного времени. Система АУтскпуз получает все общие контексты устройств из кэша экранных контекстов. Поэтому приложение должно освобождать общий контекст устрой- ства сразу же после окончания рисования. При освобождении контекста теряют- ся все сделанные изменения атрибутов. При повторном вызове общего контекста дисплея все значения атрибутов, от- личающиеся от значений по умолчанию, должны быть установлены заново. Частный контекст дисплея Частный контекст задается посредством указания флага С5_0ММ0С для поля 81у1е в структуре \Л/М 0С1А55 перед регистрацией класса окна. В противоположность об- щему контексту дисплея в частном контексте все изменения атрибутов сохраня- ются даже после освобождения контекста. Частный контекст устройства не является частью системного кэша, и память для него выделяется системой специально для данного приложения. Система ав- томатически освобождает частный контекст устройства после того, как уничто- жается последнее окно данного класса. Частный контекст используется в приложениях с высокой интенсивностью применения операций рисования, например в программах компьютерного проек- тирования (САВ), издательских системах, графических редакторах и им подоб- ных приложениях. Использование сообщения УУМ_РА11ЧТ Обычно приложение рисует что-либо в окне, реагируя на сообщение ММ_РА1МТ. Система посылает это сообщение окну во всех случаях, требующих перерисовки клиентской области окна. Например, типичными причинами генерации этого со- общения могут быть следующие события: □ изменились размеры или местоположение окна; □ клиентская область была частично или полностью закрыта другим окном или выпадающим меню, а теперь закрывающий объект исчез; □ приложение вызвало одну из функций работы с полосами прокрутки. Кроме того, приложение может само инициировать посылку сообщения ММ_РА1МТ посредством вызова одной из функций, 1пуаПс1а1:еКес1,1пуа 11 с1а1:еКдп или Врс1а1:е\Л/1’пс1оуу. Функция IIрда1:е\/У1 пдоуу посылает сообщение ММ_РА1МТ непосредственно в окон- ную процедуру, минуя очередь приложения. Работа с сообщением ММ_РА1МТ требует от разработчика понимания общей па- радигмы отображения графики на экране, которая принята в системах семейства
\У1пс1о\\ъ. Желательно структурировать программу таким образом, чтобы инфор- мация, необходимая для рисования в клиентской области, готовилась там, где это удобно с точки зрения реализуемого алгоритма. Но само рисование должно вы- полняться только тогда, когда появляется сообщение ММ_РА1МТ. Впрочем, иногда решаемая задача диктует и другие подходы к рисованию: например, оно может выполняться при обработке сообщений от мыши или от таймера. Использование функций 1пуа1!с1а1еЯес1 и 1пуа1!с1а1еЯдп Если в каком-то месте алгоритма необходимо сгенерировать сообщение ММ_РА1МТ для перерисовки некоторой части окна, то приложение может воспользоваться функциями 1пуа11’с1а1:еКес1: и ТпуаПдаЕеКдп. Функция 1пуа11да±еКес± имеет следующий прототип: В001_ 1уа11(ШеКесД(ШМ0 Шпс1, С0И8Т КЕСТ* 1 рКесТ, ВООЕ ЬЕгазе); Параметры этой функции перечислены в следующем списке: □ Н\Л/пс1 — дескриптор окна, у которого изменился обновляемый регион. Если этот параметр равен МОП, то система обновляет и перерисовывает все окна прило- жения, а также посылает сообщения ММ_ЕКА$ЕВК6М0 и ММ_МСРА1МТ оконной про- цедуре до возврата из функции. □ 1рКес! — указатель на структуру типа КЕСТ, содержащую клиентские координа- ты прямоугольника, который добавляется к обновляемому региону. Если этот параметр имеет значение МОП, то к обновляемому региону добавляется вся клиентская область. □ ЬЕгазе — флаг, определяющий, будет ли стираться фон обновляемого региона. Если этот параметр равен ТРОЕ, то фон стирается, когда вызывается функция Ведт’п Ра1пЕ Если указано значение ЕАЕ5Е, то фон остается без изменения. Последний параметр очень важен. Нужно быть очень внимательным, опреде- ляя его значение. Ни в коем случае нельзя передавать значение ТВОЕ, если можно обойтись значением ЕАЕ5Е, так как это может оказаться причиной очень неприятного мерцания фона окна. Если же стирание фона все-таки необходимо, следует поста- раться минимизировать обновляемый регион. Иными словами, если можно ограни- чить стираемую область некоторым прямоугольником гс11рс1а1:е, то в качестве второ- го параметра следует передавать ссылку на этот прямоугольник, а не значение МОП. Функция ТпуаПдаЕеКдп имеет следующий прототип: ВООЕ 1пуа11 (ШеЕдп(НШО Шпс1, НО НЕдп, ВООЕ ЬЕгазе); В этой функции все параметры, кроме второго, имеют тот же смысл, что и в пре- дыдущей функции. Параметр НКдп содержит дескриптор региона, добавляемого к обновляемому региону. Этот дескриптор можно получить при помощи функций типа СгеаЕеКесЕКдп, Сгеа1:еРо1удопКдп и других функций, рассматриваемых в разделе «Регионы». Таким образом, и функция 1пуа11’с1а1:еКесЕ и функция 1пуа11’с1а1:еКдп изменяют об- новляемый регион так, что в любом случае он уже не является пустым. Если же обновляемый регион не пуст, то система отправляет окну Н\Л/пс1 сообщение ММ_РА1МТ. Типичная ошибка Если не нужно что-либо выводить в клиентскую область окна, то можно доверить обработку сообщения ММ_РА1МТ функции ОеЙЛ/1 пс1о\л/Ргос. Но не следует писать та- кой код:
сазе Ш_РА1МТ: геШп 0; // ОШИБКА !!! Дело в том, что, как только появится непустой обновляемый регион, АУтйомга поставит сообщение ММ_РА1МТ в очередь. А оконная процедура в приведенном при- мере не вызывает функцию Вед1пРа1пЕ которая делает обновляемую область дей- ствительной. Поэтому \У1пс1о\\ъ снова отправит сообщение ММ_РА1МТ. И система будет отсылать это сообщение постоянно, создавая бесконечный цикл. Контекст принтера При необходимости вывода информации на принтер приложение должно создать контекст устройства, вызвав функцию Сгеа1:еОС. В качестве аргументов функции передаются имя драйвера устройства, имя устройства и, при необходимости, дан- ные для инициализации устройства. Используя полученный дескриптор контек- ста, можно подготовить устройство для печати и вывести необходимую информа- цию. Завершив работу с принтером, приложение должно удалить контекст принтера с помощью функции Ое1е1:еОС. Контекст в памяти (совместимый контекст) Рассмотренные выше типы контекстов устройства позволяют выводить графику только на физические устройства, такие как видеоадаптеры или принтеры. Рабо- та этих устройств обеспечивается драйверами, взаимодействующими с ними на низком уровне. Но в некоторых ситуациях бывает удобно осуществлять вывод на виртуальное графическое устройство, имитируемое в памяти в виде растра. Кон- текст устройства в памяти (или совместимый контекст} позволяет системе ра- ботать с указанным виртуальным устройством. Такой контекст создается функцией, имеющей следующий прототип: НОС СгеаШотраШ1еБС (НОС Нбс); Параметр Нек позволяет указывать дескриптор существующего контекста уст- ройства, с которым должен быть совместим создаваемый контекст. Если этот па- раметр равен ИII 1_1_, то функция создает контекст в памяти, совместимый с теку- щим экраном приложения. Если функция завершается успешно, то она возвращает дескриптор совмести- мого контекста устройства. Совместимый контекст устройства можно создавать только для тех устройств, которые поддерживают растровые операции. Когда совместимый контекст устройства создан, с ним по умолчанию связа- но растровое изображение из одного пиксела. Поэтому прежде чем использо- вать этот контекст, необходимо связать с ним растр (ЪИтар} с необходимыми шириной и высотой. Это осуществляется при помощи функции 5е1ес1:ОЬ]’ес1: или Сгеа^еСотраНЫеВйтар. После создания совместимого контекста устройства он используется как обыч- ный контекст: вы можете устанавливать его атрибуты, выбирать перья, кисти и иные необходимые параметры. Когда совместимый контекст устройства перестанет быть нужным, необходи- мо его удалить, вызвав функцию Ое1е1:еОС. Пример использования совместимого контекста приведен в главе 3.
Метафайловый контекст Другой разновидностью контекста, не соответствующего реальному физическо- му устройству, является метафайловый контекст устройства. Совместимый контекст позволяет сформировать растр с использованием гра- фических команд СВ1. В противоположность этому метафайловый контекст ус- тройства позволяет сохранить команды СВ1 в виде списка графических команд. Этот список обычно сохраняется в файле на диске, который затем может быть прочитан и воспроизведен для восстановления сформированного изображения. Существенное различие между этими типами контекстов состоит в том, что совместимый контекст работает с растром, имеющим фиксированные размеры и разрешение, в то время как метафайловый контекст сохраняет векторные и рас- тровые команды, которые при последующем использовании точно масштабиру- ются по заданным размерам. Вопросы использования метафайлового контекста будут рассматриваться в главе 3. Информационный контекст Иногда потребности приложения ограничиваются простым получением атрибу- тов графического устройства. В таких ситуациях можно использовать усеченный контекст устройства, называемый информационным контекстом, причем под гра- фическим устройством здесь понимается не только экран, но и любое устройство, поддерживаемое СВ1. Информационный контекст создается функцией Сгеа1е1С, прототип которой приведен ниже: НОС Сгеа-Ье1С( 0РСТ8ТК ТрзгОгчУег, 0РСТ8ТК 1р820еу1се, 0РСТ8ТК 1р82СШрцД, СОИЗТ ОЕУМООЕ* 1рОут ); // имя драйвера // имя устройства // имя порта или файла // указатель на структуру ОЕ\/МООЕ В АУ ш32-приложениях третий параметр всегда игнорируется и при вызове функ- ции должен быть равным МОП. Четвертый параметр задает адрес структуры типа ОЕУМООЕ, в которую записы- вается извлекаемая информация. Когда информационный контекст устройства перестанет быть нужным, не за- будьте его удалить, вызвав функцию 0е1е1:е ОС. Системы координат и преобразования Экранные, оконные и клиентские координаты Система координат для окна базируется на координатной системе дисплея. Основ- ной единицей измерения служит пиксел. Точки на экране задаются парой коорди- нат (х, у). При этом х-координаты возрастают слева направо, а //-координаты — сверху вниз. Расположение начала координат зависит от режима отображения.
Позиция окна на экране задается в приложениях в так называемых экранных координатах (зсгееп соогйта^ез), для которых началом координат является левый верхний угол экрана. Часто эта позиция описывается структурой типа КЕСТ, содер- жащей экранные координаты левого верхнего угла и правого нижнего угла окна. Позиция точки в окне задается либо в оконных координатах (тпскм) соогНтаЬез), либо в клиентских координатах (сПепё соогйта^ез) (см. рис. 1.3). Выбор системы координат зависит от используемых функций АУш32 СН1, поэтому следует обра- щать внимание на спецификацию системы координат, читая описание функции в М8НК Оконные и клиентские координаты гарантируют надлежащее позицио- нирование точки в окне независимо от положения окна на экране. Типы координатных систем Приложения используют координатные системы и геометрические преобразова- ния для масштабирования, вращения, перемещения, сдвига и зеркального отраже- ния графических объектов. Каждая из используемых систем является некоторой разновидностью декартовой прямоугольной системы координат. По практическим соображениям в АУш32 СН1 реализована поддержка четырех координатных сис- тем, функционирующих подобно некоторому конвейеру, по которому движется гео- метрическая модель, описывающая размеры и местонахождение объектов. □ Мировая система координат (рюМ зрасе) может использоваться как началь- ная система координат, обеспечивающая любое преобразование. □ Страничная система координат (раре зрасе) используется или как следующая система координат после мировой системы координат, или как стартовая сис- тема, которая поддерживает ограниченные преобразования. В этой системе могут устанавливаться режимы отображения. □ Система координат устройства (йетсе зрасе) используется после страничной системы. В ней осуществляется только перемещение начала координат, чтобы обеспечить надлежащее положение изображения в физической системе коор- динат. □ Физическая система координат (ркузгса! йесгсе зрасе} — последняя система в конвейере геометрических преобразований. Она используется драйвером гра- фического устройства. Начальное размещение изображения на уровне приложения происходит в ми- ровой системе координат, если приложение вызвало функцию ЗеВ/УогШТгапзГогт. В ином случае начальное размещение изображения производится в страничной системе координат. На рис. 2.2 показаны типичные преобразования систем коор- динат, формируемые системой после вызова функции ЗеЖогШТгапзГогт. Ут1п Ут1п -1 утах \*/ог1с1 красе Раде красе 0е*1се красе 0е*1се Рис. 2.2. Типичные преобразования систем координат для вывода изображения
Физическая система координат Физическая система координат представляет собой матрицу пикселов фиксиро- ванной ширины и высоты. Начало координат находится в левом верхнем углу. Ось X направлена слева направо, а ось У — сверху вниз. Максимальный размер физического устройства равен 227 х 227 пикселов. Для дисплея физические координаты называют также экранными координа- тами{. Экранные координаты обычно используются в операциях управления окнами. Например, функция 6е!:М1’пс1о№Кес1: возвращает ограничивающий пря- моугольник окна в экранных координатах. Также экранные координаты содер- жатся в параметрах таких сообщений, как ММ_1\1СМО115ЕМО\/Е. Это сообщение ге- нерируется при перемещении курсора мыши в пределах неклиентской части окна. Следует учитывать, что бывают случаи, когда не все пикселы физической по- верхности могут отображаться устройством. Например, для принтеров существу- ют механические ограничения, не позволяющие печатать у краев страниц. В та- ких ситуациях приложение должно получить информацию об отображаемой части поверхности при помощи функции БеЮеукеСар. Система координат устройства Координаты устройства (йетсе соогНтаЪез) используются при работе с контек- стами устройств. В общем случае система координат устройства является под- множеством соответствующей физической системы координат. Для контекстов устройств, созданных функциями Сгеа1:еОС, Сгеа1:е1С и Сгеа1:е СотраВЫеОС, система координат устройства идентична физической системе ко- ординат. Для контекстов устройств, связанных с конкретными окнами (то есть возвра- щаемых функциями СеЮС, Се!:\Л/1’пс1о\л/ОС и Ведт’пРаш!:), система координат устрой- ства определяется прямоугольником окна или его клиентской области. Как и в физической системе координат, левый верхний угол системы коорди- нат устройства имеет координаты (0, 0), ось Xнаправлена слева направо, а ось У — сверху вниз. Зная дескриптор контекста устройства, можно узнать относитель- ную позицию системы координат устройства в его физических координатах при помощи функции СеЮСОгдЕх. В интерактивных графических приложениях, использующих сообщения мыши для работы с графическими объектами, возникает необходимость преобразова- ния между физическими экранными координатами и координатами устройства (клиентскими координатами). АУш32 АР1 предлагает соответствующие функции для решения этой задачи: В001 С11егДТо8сгееп(НШ0 Шпс1, 1Р01МТ 1рРо1ггЬ); В001 ЗсгеепТоС! 1 егтЬ(ШИВ Шпс1, 1Р01ИТ 1рРо1гтЬ); В области управления окнами координаты устройства чаще всего интерпрети- руются как клиентские координаты. Клиентские координаты содержатся также в сообщениях, генерируемых мышью, таких как ММ_МО115ЕМО\/Е и ММ_ 1_ВОТТОМ ОО\Л/М. 1 См. выше раздел «Экранные, оконные и клиентские координаты».
Страничная система координат и режимы отображения Обе рассмотренные системы координат ограничиваются представлением в виде аппаратно-зависимого массива пикселов. Размер окна на экране с высоким разре- шением отличается от размера окна при низком разрешении, а толщина напеча- танной линии из трех пикселов будет зависеть от разрешения принтера. Чтобы графическое программирование в меньшей степени зависело от устройства, \Уш32 СВ1 позволяет приложениям создавать собственные логические системы коорди- нат, приближенные к их геометрическим моделям. В таких системах координат удобнее работать, и они гораздо меньше зависят от оборудования. Одной из двух логических систем координат в \Уш32 АР1 является странич- ная система координат. Это единственная логическая система координат, кото- рая поддерживается 16-разрядными ОС семейства АУтйомга и даже реализация- ми АУт32 в \У1пс1о\\ъ 95/98. Мировые координаты, которые являются второй логической системой координат, поддерживаются только в АУтскпуз ?<Т/2000. По историческим причинам в АУтсЬм^з под логической системой координат обыч- но понимается именно страничная система. При отображении страничных координат в координаты устройства использу- ются два базовых понятия компьютерной графики: □ окно (ртпбоюу, □ область просмотра (рпеюрогф. Иногда можно встретить другие термины для обозначения этих же понятий, когда окно называют логической областью вывода, а область просмотра — физи- ческой областью вывода. Окном называется любая прямоугольная область в страничной системе коор- динат. Областью просмотра называется прямоугольная область в системе координат устройства. Таким образом, окно определяет отображаемую часть геометричес- кой модели, а область просмотра — ее расположение на поверхности устройства. Соотношение между размерами определяет масштаб вывода. Для формализации рассматриваемых преобразований следует ввести несколь- ко понятий. Окно (рюгпбою} определяется четырьмя переменными в страничных коорди- натах: □ х\Л/О — абсцисса начала координат (х-оп§ш). □ у\Л/О — ордината начала координат (г/-оп§ш). □ х\Л/Е — горизонтальные габариты окна (х-ех!еп1). □ у\Л/Е — вертикальные габариты окна (г/-ех!еп1). Область просмотра (рпеюрогй) определяется четырьмя переменными в коорди- натах устройства: □ хУО — абсцисса начала координат (х-оп§ш). □ уУО — ордината начала координат (г/-оп§ш). □ XVЕ — горизонтальные габариты области просмотра (х-ех!еп1). □ уУЕ — вертикальные габариты области просмотра (г/-ех!еп1).
Точка (х, у) в страничной системе координат отображается на точку (х', у') в координатах устройства по следующим формулам: х' = (х - хАУО) • хУЕ / хАУЕ + хУО; у’=(у- уАУО) • уУЕ /уАУЕ + уУО. Существует несколько вариантов отображений логической области вывода в физическую область вывода. □ Тождественное отображение. Окно и область просмотра задаются квартета- ми (0, 0, 1, 1). В этом случае х’ = х, у ’ = у, а страничные координаты идентичны координатам устройства. □ Смещение. Окно определяется квартетом (0,0,1,1), а область просмотра — квар- тетом (т/х, бу, 1,1). В этом случае х' = х + бх,у’ = у + бу. Каждая точка странич- ного пространства при отображении в систему координат устройства смеща- ется на величину (бх, бу). □ Масштабирование. Окно определяется квартетом (0, 0, 1, 1), а область про- смотра — (0, 0, тх, ту). В этом случае х' = х • тх, у1 = у • ту. Каждая точка страничного пространства при отображении в систему координат устройства масштабируется с коэффициентами (тх, ту). □ Отражение. Окно определяется квартетом (0, 0, ю, к), а область просмотра — квартетом (то, к, -то, -к). В этом случае хг=то -х, лу’ = к - у. При отображении из страничной системы координат в координаты устройства рисунок может подвергаться зеркальному отражению относительно горизонтальной и верти- кальной осей. Отражение позволяет использовать в страничной системе коор- динат направления осей, которые отличаются от фиксированных направлений системы координат устройства. Эти варианты отображений можно комбинировать в необходимой последова- тельности. В \У1п32 АР1 имеются соответствующие функции для настройки страничной системы координат при помощи задания параметров окна и области просмотра: В001 8еХ1л/1 пс!о\л/ОгдЕх ( НОС Нбс, // дескриптор контекста устройства Ш X, // новая х-координата начала координат окна Ш V, // новая у-координата начала координат окна 1РР01МТ 1рРо1п1с // указатель на структуру типа Р01ИТ для // сохранения прежнего начала координат окна ); ВООЕ 8еОШ псЫЕхОЕх ( НОС Нс1с, // дескриптор контекста устройства 1гтЕ пХЕхОепО, // новый размер окна по оси X 1гтЕ пХЕхОепО, // новый размер окна по оси V ЕР812Е 1р81хе // указатель на структуру типа 812Е для // сохранения прежних размеров окна В приведенных функциях, настраивающих логическую область вывода, все ко- ординаты задаются в логических единицах. Но есть также функции, настраиваю- щие физическую область вывода. В их вызовах все координаты задаются в физи- ческих единицах: ВООЕ ЗеШемрогООгдЕх ( НОС Мс, // дескриптор контекста устройства
1пб X, // новая Х-координата начала координат области просмотра 1пб V, // новая Х-координата начала координат области просмотра 1РР01МТ 1рРо1пб // прежнее начало координат области просмотра ); В001 8еб\/1емрогбЕхбЕх ( НОС Нбс, // дескриптор контекста устройства 1пб пХЕхбепб, // новый размер области просмотра по оси X 1пб пХЕхбепб, // новый размер области просмотра по оси V ЕР317Е 1р81хе // прежние размеры области просмотра Для всех четырех функций в качестве последнего параметра можно передать значение МОП, если сам параметр не используется. Использование функций 5е1\Л/1 пдо\л/Огд Ех и 5е1:\/1 е\л/рог±Огд Ех требует дополнитель- ных пояснений. Первая функция устанавливает, какая именно точка логического пространства отображается в точку (0,0) физического пространства1. Соответствен- но, вторая функция устанавливает, какая точка физического пространства отобра- жается в точку (0, 0) логического пространства. Но учтите, что одновременное ис- пользование обеих функций запрещено! Остановите свой выбор на одной из них, так как обе функции позволяют добиться одного и того же результата. Пример использования приведенных функций для настройки страничной сис- темы координат можно найти в листинге 2.3. Для упрощения работы программиста АУш32 АР1 содержит несколько заготовок страничных систем координат, называемыхрежгшогш отображения (таррт^тобез). В большинстве режимов отображения устанавливаются заранее выбранные га- бариты окна и области просмотра. Эти габариты определяют размер единицы из- мерения в страничной системе координат и коэффициент масштабирования при переходе к системе координат устройства. Впрочем, приложение может изменить положение начала координат окна и области просмотра, что позволяет выводить различные фрагменты геометрической модели в разных частях экрана. Режим отображения контекста устройства выбирается следующей функцией: Ш 8ебМарМобе (НОС НОС, 1пб 1МарМобе); В этой функции параметр 1МарМос1е задает один из восьми идентификаторов режимов отображения, приведенных в табл. 2.1. Таблица 2.1. Режимы отображения Режим отображения Направление по оси X Направление по оси у Логические единицы ММ_ТЕХТ Вправо Вниз Пиксел ММ_1_ОМЕТР1С Вправо Вверх 0,1 мм ММ_Н1МЕТР1С Вправо Вверх 0,01 мм ММ_1_ОЕ1\161_15Н Вправо Вверх 0,01 дюйма ММ_Н1ЕМ61_15Н Вправо Вверх 0,001 дюйма ММ_та/1Р8 Вправо Вверх 1 / 1440 дюйма ММ_18ОТРОР1С Любое Любое Произвольные единицы (х == у) ММ_А1\118ОТРОР1С Любое Любое Произвольные единицы (х != у) 1 Это эквивалентно такому сдвигу осей, в результате которого точка (0, 0) логического пространства уже не относится к левому верхнему углу окна.
Если функция 5е1:МарМос1е не вызывалась, то по умолчанию используется ре- жим отображения ММ_ТЕХТ. Таким образом, страничная система координат позволяет приложению стро- ить свою геометрическую модель в произвольных координатах с произвольно выб- ранными направлениями осей и физическим масштабом. Следует отметить, что реальность, однако, не столь радужна, как может пока- заться из последнего утверждения. Все получается великолепно до тех пор, пока вы отображаете отдельные точки при помощи функции 5е1:Р1хе1 либо используете дру- гие графические функции с пером толщиной 1 пиксел. Для этого при вызове функ- ции СгеаЕеРеп аргумент пМ1’с11:Н должен иметь нулевое значение. Но при попытке ри- совать линии разной толщины вы столкнетесь с неприятными проблемами. Эти проблемы и пути их решения рассматриваются ниже в разделе «Вывод временной диаграммы напряжения переменного электрического тока» (листинг 2.3). Мировая система координат Страничная система координат имеет несколько недостатков, причиной которых являются следующие особенности архитектуры АУш16 СВ1 АР1: □ Дробные коэффициенты при отображении окна на область просмотра, что мо- жет приводить к потере точности. □ Неполная реализация АР1. Так, вывод текста не соответствует семантике от- ражения относительно оси У. Другими словами, если приложение направляет ось X справа налево, то текстовые строки все равно выводятся слева направо. □ Ограниченный набор преобразований. Отображение окна на область просмотра позволяет выполнить масштабирование, перемещение и зеркальное отраже- ние. Вращение и сдвиг не поддерживаются, а без прямой поддержки со сторо- ны СВ1 реализовать их очень трудно. В \Упн1о\\ъ ?<Т/2000 для решения этих проблем была создана новая логичес- кая система координат — мировые координаты. В этой системе координаты зада- ются по-прежнему в виде 32-разрядных целых чисел, однако появляется возмож- ность использования более общих преобразований. Аффинные преобразования Преобразования, поддерживаемые в АУхпйоауз ?<Т/2000, относятся к классу дву- мерных аффинных преобразований. Аффинное преобразование отображает па- раллельные линии в параллельные линии, а конечные точки — в конечные точки. Двумерное аффинное преобразование определяется шестью числами, обра- зующими матрицу 2 х 3. В \Уш32 АР1 такие матрицы определяются структурой ХЕОКМ: Цурес1еГ _ХЕОКМ { ЕЮАТ еМИ; ЕЮАТ еМ12; ЕЮАТ еМ21; ЕЮАТ еМ22; ЕЮАТ еОх; ЕЮАТ еОу; } ХЕОКМ;
Аффинное преобразование, определяемое этими числами, преобразует точку (х, у) в точку (х', у ) следующим образом: х = еМ11 • х + сЛ/21 • у + сЛх; у1 = еМ12 • х + еМ22 • у + еТ)у. Аффинные преобразования позволяют выполнить следующие операции: □ Тождественное отображение. Оно определяется матрицей (1, 0, 0, 1,0, 0). При этом х' = х, а у' = у. □ Смещение. Это преобразование определяется матрицей (1, 0, 0, 1, б/х, бу}. При этом х1 = х + бх,ъу’ = у + бу. □ Масштабирование. Задается матрицей (тх, 0, 0, ту, 0, 0). Выполняются соот- ношения х' = тх • х,у' = ту • у. □ Зеркальное отражение. Преобразование задается матрицей (-1, 0, 0, -1, 0, 0). При этомх'= -х, аг/'= - у. □ Поворот. Определяется матрицей (соз(9), зш(0), -ып(0), соз(0), 0, 0). При этом существуют соотношения х' = соз(0) • х - ып(0) • у и у1 = ып(0) • х + соз(0) • у. □ Сдвиг. Определяется матрицей (1, 5, 0, 1, 0, О)1. При этом выполняется соотно- шения х' = х + з - у жу' = у. □ Комбинированные операции. Матрицы нескольких аффинных преобразований объединяются операцией матричного умножения и образуют новое аффинное преобразование. Функции мировых преобразований в У\Лп32 АР1 (УУ|пс1оа/у5 ИТ/2000) По умолчанию контекст устройства работает в так называемом совместимом режиме. При этом имеется в виду совместимость с 16-разрядной семантикой СВ1. В совместимом режиме мировые координаты не поддерживаются, а един- ственной логической системой координат является страничная система. Для использования мировой системы координат приложение должно переключить контекст устройства в графический режим вызовом функции 5е16гарЫс$Мос1е (НОС, 6М_А0УАМСЕ0). В результате вызова будет обеспечена поддержка двух уровней логического координатного пространства — мировых и страничных координат, а также матрицы преобразования. Чтобы вернуться к совместимо- му режиму работы, следует заполнить матрицу данными тождественного пре- образования и вызвать функцию 5е16гарН1’с$Мос1е(Н0С, 6М_С0МРАТ1В1_Е). Кроме того, можно воспользоваться функциями 5ауеОС и Ве$1:огеОС. В контексте устройства преобразование из мировых координат в страничные по умолчанию инициализируется тождественной матрицей. Для изменения теку- щего преобразования используются следующие функции: В001 ЗеШгШгапзТоптООС НОС, С0И8Т ХНЖ* 1рХТогт); В001_ МоШуШгШгапзТоптООС НОС, С0И8Т ХНЖ* 1рХТогт, ОШКО 1Мос1е); Функция ЗеЖогШТгапзГогт просто заменяет атрибут преобразования в контек- сте устройства новым преобразованием, заданным структурой ХЕОКМ. 1 Координаты х смещаются на величину, пропорциональную у.
При вызове функции М осПТу\Л/ог1с1Тга пзГогт параметр 1М оде может принимать одно из трех предопределенных значений: □ МТО.ЮЕМПЕУ — преобразование приводится к тождественной форме; □ МТМ_1_ЕЕТМ111_Т1Р1У — текущее преобразование умножается на матрицу параметра фХГогт слева; □ МТМ_К16НТМ111_Т1Р1_У — текущее преобразование умножается на матрицу пара- метра фХГогт справа. Для получения информации о текущем преобразовании используется функ- ция СеШог1с1Тгап5Еогт. Пример использования мировой системы координат приведен в главе 12. Получение информации о возможностях устройства Иногда бывает необходимо уточнить характеристики конкретного графического устройства. Для этого можно воспользоваться функцией беЮеукеСарз, которая воз- вращает информацию об атрибутах или возможностях графического устройства на основе целочисленного индекса, который передается в качестве параметра: Ш 6еШу1сеСарз(Н0С Нбс, Ш п1пс1ех); Некоторые возможные значения индекса п!пс1ех приведены в табл. 2.21. Таблица 2.2. Значения параметра п!пс1ех Индекс Значение ОкТУЕНУЕкЗЮЫ ТЕСН1\Ю1_ОСУ Версия драйвера устройства Технология устройства (ОТ_Р1_ОТТЕк, ОТ_РА5О15Р1АУ, ОТ_КА8РкИ\1ТЕк и другие значения) НОН2812Е УЕНТ812Е НОН2кЕ8 УЕНТНЕЗ ЮСР1ХЕ18Х 1_О6Р1ХЕ1_8У В1Т8Р1ХЕ1 Р1.АЫЕ8 1\111МВк118НЕ8 ЫС1МРЕЫЗ 1М11МРОМТ8 1\111МСО1_Ок8 СИРСАР8 812ЕРА1ЕТТЕ Ширина физического экрана в миллиметрах Высота физического экрана в миллиметрах Ширина экрана в пикселах Высота экрана в пикселах Число пикселов на 1 логический дюйм по горизонтали Число пикселов на 1 логический дюйм по вертикали Число смежных битов цвета для каждого пиксела Число цветовых плоскостей Число кистей, зависящих от устройства Число перьев, зависящих от устройства Число шрифтов, зависящих от устройства Число входов в таблице цветов устройства Флажок, который указывает возможности отсечения устройства Число входов в системной палитре 1 Полный список возможных значений индекса см. в МЗВМ
Индекс Значение СО1_ОККЕ8 Фактическая разрешающая способность устройства по цвету в битах на пиксел РНУ51СА1_\Л/ЮТН РНУ81СА1НЕ1СНТ РНУ81САЮРР8ЕТХ Ширина физической страницы (для принтеров) в пикселах Высота физической страницы (для принтеров) в пикселах Смещение в пикселах от левой границы страницы, где фактически начинается печать РНУ81САЮЕЕ8ЕТУ Смещение в пикселах от верхней границы страницы, где фактически начинается печать КА8ТЕИСАР8 Растровые возможности устройства. Возвращаемое значение может представлять собой комбинацию следующих флагов: ИС_В1ТВ1_Т — способно передавать растровые изображения; ИС_В1ТМАР64 — способно поддерживать растровые изображения, превышающие по размерам 64 Кбайт; ИС_ОЕ\/В1Т8 — способно поддерживать аппаратно- зависимые изображения; ИС_1\ЮЫЕ — устройство не имеет растровых возможностей; ИС_РА1_ЕТТЕ — указывает на устройство, действующее на основе палитры; ИС_8СА1_1М6 — способно к масштабированию; ИС_8ТИЕТСНВ1_Т — способно выполнять функцию З^ге^сНВИ; ИС_8ТИЕТСНО1В — способно выполнять функцию 81ге1с11016115 С1ЖУЕСАР8 Возможности вывода кривых. Возвращаемое значение может представлять собой комбинацию следующих флагов: СС_СНОНО — устройство может выводить дуги хорды; СС_С1ИС1_Е8 — может выводить окружности; СС_Е1_1_1Р8\Л/8 — может выводить эллипсы; СС_11\1ТЕИЮИ8 — может закрашивать внутренние области; СС_1\ЮЫЕ — устройство не поддерживает вывод кривых ЫЫЕСАР5 Возможности вывода линий. Возвращаемое значение может представлять собой комбинацию следующих флагов: 1_С_8ТУ1_Е0 — устройство может выводить стилизованные линии; 1_С_\Л/ЮЕ — может выводить широкие линии; 1_С_Р01_У1_1ЫЕ — может выводить ломаную линию; 1_С_11\1ТЕИ10И8 — может закрашивать внутренние области; 1_С_1\ЮЫЕ — устройство не поддерживает линии РО1УСОЫА1САР8 Возможности вывода многоугольников. Возвращаемое значение может представлять собой комбинацию следующих флагов: РС_8ТУ1_Е0 — устройство может выводить стилизованные рамки; РС_\Л/ЮЕ — может выводить широкие рамки; РС_Р01_УС0Ы — может выводить многоугольники; РС_11\1ТЕИЮИ8 — может закрашивать внутренние области; РС.1М0ЫЕ — устройство не поддерживает многоугольники ТЕХТСАР8 Текстовые возможности устройства. Возвращаемое значение может представлять собой комбинацию следующих флагов: ТС_СИ_90 — устройство способно поворачивать символы на 90°; ТС_СИ_А1\1У — способно поворачивать символы на любой угол; ТС_ЕА_0011В1_Е — может выводить символы с двойным весом; ТС_1А_АВ1_Е — может выводить символы курсивом; ТС_КА_АВ1_Е — может выводить растровые шрифты; ТС_УА_АВ1_Е — может выводить векторные шрифты Например, если необходимо определить количество пикселов на 1 логический дюйм экрана по вертикали, чтобы рассчитать необходимые размеры создаваемо- го шрифта, то это можно сделать в блоке обработки сообщения ММ_СКЕАТЕ: НОС Мс = 6еШ(Ь/пс1); Ш 1одР1хеП = СМ0еу1 сеСарз (Нбс, ЮЗР1ХЕ1У): // используем значение 1одР1хе1У ... Ке1еа8еОС(Ыпс1, Ыс);
Управление цветом. Вывод пиксела При внимательном рассмотрении можно увидеть что экран дисплея составлен из тонких горизонтальных и вертикальных линий. На пересечении каждой го- ризонтальной и вертикальной линии находится точка, называемая Пикселом. Пиксел — это минимальный по размерам изобразительный элемент, которым может управлять приложение. В свою очередь, пиксел образован тремя микроточками, не различимыми не- вооруженным глазом, которые имеют, соответственно, красный, зеленый и синий цвета. Микроточки могут светиться с интенсивностью от 0 (отсутствие излуче- ния) до 255 (максимальная яркость). Совокупность их свечения образует теку- щий цвет пиксела. Таким образом, цвет пиксела можно рассматривать как некоторую точку в трех- мерном КСВ-пространстве, образованном тремя цветовыми осями: Вес! (красная цветовая составляющая), Сгееп (зеленая составляющая) и В1пе (синяя составля- ющая). В компьютерной графике используются и другие цветовые пространства. На- пример, для цветных принтеров чаще используется цветовое пространство СМУК, в котором каждый цвет является комбинацией голубой, малиновой, желтой и чер- ной составляющих. Для человеческого глаза более привычно различать цвета по их оттенку, ярко- сти и насыщенности. Поэтому в графических пакетах более распространенным является использование пространства НЕ8. Цветовое пространство Н1_5 В пространстве НЕ8 цвет описывается через значения оттенка (Нг/е), яркости (ЬщЫпезз) и насыщенности (^а1ига1юп). Оттенок измеряется в угловых величинах от 0 до 360°. Нулевое значение со- ответствует синему цвету, 60 — малиновому, 120 — красному, 180 — желтому, 240 — зеленому, 300 — голубому. Яркость описывает интенсивность чистого цвета. Значение яркости находит- ся в промежутке от нуля до единицы. Условно говоря, увеличение яркости добав- ляет белый цвет к чистому цвету, а уменьшение яркости добавляет черный цвет. Насыщенность, также изменяемая от нуля до единицы, является мерой «чис- тоты» цвета. Если насыщенность уменьшается, то к чистому цвету добавляется больше серого цвета. Нулевое значение насыщенности приводит к шкале серого цвета. Цветовое пространство КСВ Система \Упк1о\\ъ поддерживает цветовое пространство КСВ и пространство ин- дексов палитры, соответствующее базовым возможностям видеоадаптера. Цвет кодируется в любом из этих пространств при помощи типа данных СОЮККЕЕ, представляющего четырехбайтное значение. Старший байт задает один из трех возможных форматов: 0 — КСВ, 1 — РА1_ЕТТЕ1 И0ЕХ, 2 — РА1_ЕТТЕКСВ. Интерпретация формата КСВ показана на рис. 2.3.
77 Управление цветом. Вывод пиксела 31 | 30 | 29 | 23 | 27 | 26 | 25 | 24 23 | 22 | 21 | 20 | 19 | 13 | 17 | 16 15 1 14 1 13 1 12 1 11 1 10 1 9 3 7 6 5 4 3 2 1 0 □ В1ие бгееп Кес! Рис. 2.3. Тип данных СОЮккЕЕ, формат кСВ Минимальное значение (0) всех трех составляющих цвета задает черный цвет. Максимальное значение (255) всех трех составляющих цвета задает белый цвет. Комбинируя все возможные значения КСВ-составляющих, можно получить 224 комбинаций, или примерно 16,7 миллиона возможных цветов. АУш32 АР1 содержит несколько макросов, позволяющих объединять три со- ставляющие КСВ в одно 32-разрядное значение типа СОЮККЕЕ либо, наоборот, разделять данные СОЮККЕЕ на составляющие КСВ-модели. Эти макросы, определенные в файле \ллпдсН.Н, можно представить в виде сле- дующих подставляемых (гпИпе) функций: СОЕСШЕЕ О(ВУТЕ ЬуО, ВУТЕ Ьубгееп, ВУТЕ ЬуВ1ие); ВУТЕ 6еОТа1ие(СОЕООЕ гдЬ); ВУТЕ 6еОа1ие(СОЕООЕ гдЬ); ВУТЕ 6еШа1ие(СОЕООЕ гдЬ); Следующий фрагмент кода демонстрирует использование макроса КСВ для оп- ределения констант, представляющих наиболее часто применяемые «чистые» цвета: сопзк СОЕСШЕЕ Ыаск = 0(0, 0, 0); // 0(0x0, 0x0, 0x0); сопзк СОЕОЕЕЕЕ гее! = 0(255, 0, 0); // О(0хЕЕ, 0x0, 0x0); сопзк СОЕОЕЕЕЕ дгееп = 0(0, 255, 0); // 0(0x0, ОхЕЕ, 0x0); сопзк СОЕОЕЕЕЕ Ыие = 0(0, 0, 255); // 0(0x0, 0x0, ОхЕЕ); сопзк СОЕСШЕЕ уе! 1 о\л/ = 0(255, 255, 0); // О(0хЕЕ, ОхЕЕ, 0x0); сопзк СОЕСШЕЕ тадепка = 0(255, 0, 255); // О(0хЕЕ, 0x0, ОхЕЕ); сопзк СОЕОЕЕЕЕ суап = 0(0, 255, 255); // 0(0x0, ОхЕЕ, ОхЕЕ); сопзк СОЕСШЕЕ шМЧе = 0(255, 255, 255); // О(0хЕЕ, ОхЕЕ, ОхЕЕ) Определение цвета при работе с палитрой К сожалению, не все устройства способны воспроизводить 16 777 216 цветов из КСВ-пространства типа СОЮККЕЕ. Например, все еще встречаются дисплеи ста- рых типов, которые могут поддерживать только 256 цветов. В таких случаях для приемлемого качества рисования нужно работать с цветовой палитрой. Вопросы работы с палитрой рассматриваются в главе 3. Здесь же приводятся только фор- маты данных для типа СО ЮКК ЕЕ, которые позволяют работать с палитрой. АУш32 АР1 содержит макросы РА1_ЕТТЕ1МОЕХ и РАЕЕТТЕКСВ, формирующие зна- чения одноименных форматов для типа данных СОЮККЕЕ. Интерпретация этих фор- матов показана на рис. 2.4. РА1_ЕТТЕ1ЬЮЕХ О пс!ех) 31 |30 |29 |28 |27|26 | 25 | 24 23 | 22 21 20 | 19 18 | 17 16 15 | 14 | 13 | 12 | 11 | 10 | 9| в| 7 б| 5| 4| 3 з| 1| 0 1 0 1П(5ех РАЕЕТТЕЕСВ (Рес1, Сгееп, В1ие) 31 30 |29 28 27 | 26 25 24 23 | 22 | 21 | 20 | 19 18 | 17 16 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 7 | 6 | 5 | 4 | 3 2 | 1 | 0 2 В1ие Згееп Вес! Рис. 2.4. Тип данных СОЮккЕР, форматы РА1ЕТТЕ1ЫОЕХ и РАкЕТТЕкСВ
Вывод пикселов Для работы с пикселами предусмотрены следующие функции: СОЕСШЕЕ беШхеПНОС Мс, 1гД X, 1гД V); В001_ Зе-ЬРпхе!У(НОС Мс, Ш X, Ш V, СОЕСШЕЕ со!ог); СОЕОЕЕЕЕ ЗеЦР1хе!(НОС Мс, 1гД X, 1гД V, СОЕОРРЕР со!ог); Параметры X и V определяют позицию пиксела в логических единицах. Пара- метр со 1ог, устанавливающий цвет пиксела, обычно задается при помощи макро- са КС В1. Функция 6е1Р1’хе1 возвращает цветовое значение пиксела с заданными коорди- натами. Функции 5е1Р1хе1А/ и 5е1Р1хе1 устанавливают заданное цветовое значение пик- села, различаясь возвращаемым значением. Первая функция возвращает логичес- кое значение, указывающее, успешно ли прошла операция. Вторая функция в слу- чае успешного выполнения возвращает старый цвет пиксела, а если произошла какая-то ошибка, то возвращается значение -1. Реализация несложных, на первый взгляд, функций 5е±Р1хе1\/ и 5е1Р1’хе1 на са- мом деле связана с существенными затратами процессорного времени. Обе функ- ции инициируют вызов системной функции М1:6с11’$е1:Р1’хе1, которая, в свою очередь, осуществляет довольно много действий: □ блокировку контекста устройства; □ отображение логических координат в физические; □ преобразование в случае необходимости значения типа СОЮККЕЕ в индекс; □ вызов функции драйвера Оп/ВйВИ; □ разблокировку контекста устройства. Хронометраж процесса выполнения этих функций [6] показал, что процесс вывода одного пиксела требует более 1000 тактов работы процессора. Как ни стран- но, функция 6е1Р1’хе1 работает еще медленнее и требует более 6000 тактов работы процессора. Поэтому, если рисование ведется посредством вывода отдельных пикселов, могут возникнуть проблемы с быстродействием программы. Решение таких про- блем рассматривается в главе 12. Атрибуты контекста устройства, влияющие на рисование В этом разделе будут рассматриваться атрибуты рисования, к которым в первую очередь относятся режим рисования, режим смешивания фона и цвет фона гра- фических элементов. 1 Если приложение работает с логической палитрой, то вместо макроса КС В следует использовать макрос РАЬЕТТЕКСВ.
Режим рисования. Бинарные растровые операции Любую операцию рисования можно разложить на элементарные акты рисования отдельных пикселов. В предыдущем разделе упоминалась функция 5е±Р1хе1(Н ОС, х, у, со 1ог), которая изменяет текущий цвет пиксела с координатами х и у на цвет со1ог. Но такое определение функции 5е1:Р1хе1 не является абсолютно корректным. Точ- нее говоря, оно корректно, если приложение не изменяет режим рисования, уста- новленный по умолчанию в контексте устройства. На самом деле в АУтйомга реализована более гибкая технология установки цвета пиксела при рисовании. Эту технологию можно представить как процесс переда- чи цвета из источника 5 (инструмента рисования) в приемник Б («холст» или по- верхность устройства). Независимо от разновидности инструмента рисования (перо, кисть или функция 5е1:Р1хе1) результат такой передачи определяется как функция Г( 0, 5), то есть представляет собой некоторую комбинацию цвета источ- ника и цвета приемника: Г>=/(Д5). Теоретически, число таких функций является бесконечным, но в СН1 реали- зованы только поразрядные логические операции. Слово «поразрядные» означа- ет, что к битам двух аргументов, находящимся в одинаковой позиции, применяет- ся одна и та же логическая операция. В АУтйомга поразрядная логическая операция, применяемая к пикселам, носит название растровой операции (га&ег орегайоп (ВОР)). А поскольку эта операция применяется к двум аргументам, то ее называ- ют бинарной растровой операцией {Ыпагу гаМег орегайоп (КОР2)). В М8^^ при описании бинарных растровых операций в качестве источника рассматривается только один инструмент рисования — перо Р, поэтому приведен- ная выше формула имеет следующий вид: г>=ЖР). Однако все бинарные растровые операции, приведенные в табл. 2.3, имеют ана- логичное действие и для альтернативных инструментов рисования, таких как кисть или функция 5е±Р1хе1. Таблица 2.3. Бинарные растровые операции Р: О: 1100 1010 Формула КОР2 Описание Р 0 0 0 0 0 = 0 Н2_В1_АСК Всегда 0 (черный цвет) е 0 0 0 1 0 = -(0 | Р) к2_1\ЮТМЕкОЕРЕЫ Инверсия Н2_МЕкСЕРЕЫ 3 У л 0 0 10 0 = 0 & ~Р К2_МА5К1\ЮТРЕЫ Конъюнкция приемника с инвертированным пером ь 0 0 11 0 = ~Р к2_1\ЮТСОРУРЕЫ Инверсия цвета пера т а 0 10 0 0 = Р&~0 К2_МА5КРЕЫ1\ЮТ Конъюнкция пера с инвертирован- ным приемником ы 0 10 1 0 = ~0 к2_1\ЮТ Инверсия приемника 0 110 0 = 0 74 Р Н2_Х0кРЕЫ Исключающее «ИЛИ» для приемника и пера 0 111 0 = ~(0 & Р) К2_1\ЮТМА5КРЕЫ Инверсия Н2.МА8КРЕЫ 10 0 0 0 = О&Р Н2.МА8КРЕЫ Конъюнкция приемника с пером продолжение &
Таблица 2.3 {продолжение} Р: О: 1100 1010 Формула КОР2 Описание Р 10 0 1 0 = ~ф 74 Р) К2_1\ЮТХОкРЕЫ Инверсия Н2_Х0НРЕЫ е 10 10 0 = 0 к2_1\ЮР Приемник не изменяется 3 У л 10 11 0 = 0 | ~Р к2_МЕкОЕ1\ЮТРЕЫ Дизъюнкция приемника с инвертированным пером ь 110 0 0 = Р Н2_С0РУРЕЫ Цвет пера (значение по умолчанию) т а т 110 1 0 = Р 1 ~0 к2_МЕкОЕРЕЫ1\ЮТ Дизъюнкция пера с инвертирован- ным приемником ы 1110 1111 0 = Р | 0 0 = 1 Н2_МЕкСЕРЕЫ Н2_\Л/Н1ТЕ Дизъюнкция пера с приемником Всегда 1 (белый цвет) Контекст устройства СВ1 содержит атрибут, называемый режимом рисования {(1гасс1пр то(1е). Он определяет текущую операцию К0Р2. Режим рисования можно установить при помощи функции 5е1К0Р2: Ш 5еЦК0Р2(НОС Нбс, Ш ТпОга\л/Мос1е); При вызове функции параметру ГпОгамМос1е передается одно из значений К0Р2, приведенных в табл. 2.3. Функция возвращает предыдущий режим рисования. Для получения текущего режима рисования можно воспользоваться функцией Се1К0 Р2. По умолчанию в контексте устройства установлен режим К2_С0РУРЕМ, при ко- тором пикселу приемника просто присваивается цвет пера. При использовании бинарных растровых операций следует помнить о том, что если в устройстве используется цветовое пространство КСВ, то операции приме- няются к каждой из трех составляющих ВОВ-модели. Поэтому результат всегда предсказуем, но не всегда может быть оправдан с точки зрения логики цветового восприятия. Например, если в контексте устройства выбрано перо красного цве- та (ВСВ(0хВВ, 0, 0)) и установлен режим рисования К2_Х0КРЕМ, то при рисова- нии на белом фоне (ВСВ(0хВВ, ОхРР, ОхРР)) результирующая линия будет иметь цвет морской волны(суап), поскольку операция «Исключающее ИЛИ» для при- емника и пера даст результат ВСВ(0, ОхРР, ОхРР). Интересно, что при повторном выводе линии на том же месте будет нарисована линия белого цвета, то есть вос- становится первоначальное состояние приемника. На этом полезном свойстве ре- жима рисования К2_Х0КРЕМ основан вывод «эластичных прямоугольников». Для устройств с палитрой растровые операции применяются к цветовым ин- дексам, поэтому результат зависит от упорядочения цветов в палитре. Бинарные растровые операции играют важную роль в компьютерной графике. Режим К2_В1_АСК используется для закраски пикселов черным цветом, а режим К2_МНГГЕ — белым цветом. Режим К2_М0ТС0Р7РЕМ меняет цвет пера на противопо- ложный. Режим К2_М0Р полностью подавляет вывод линий и кривых — это удобно, если вы не хотите обводить прямоугольник рамкой. Режимы К2_Х0КРЕМ и К2_М0ТХ0КРЕМ часто используются в интерактивной ком- пьютерной графике для вывода эластичных контуров или движущихся изобра- жений. Эластичные прямоугольники, например, используются для выделения с помощью мыши некоторых областей на изображении. В процессе перемещения мыши построение фигуры считается не законченным, поэтому приложение дол- жно быстро стереть прежнюю версию контура и нарисовать его в новой позиции. Бинарные операции К2_Х0КРЕМ и К2_М0ТХ0КРЕМ позволяют легко реализовать эту функциональность, поскольку при повторном выводе линии восстанавливается
исходное содержимое изображения. Пример вывода эластичных прямоугольни- ков приведен в четвертой главе. Режим смешивания фона и цвет фона графического элемента Некоторые графические примитивы СВ1 содержат пикселы двух видов: основ- ные (фогеруоипФ) и фоновые (фаскрроипФ). Например, при выводе текстового сим- вола пикселы, образующие глиф1 символа, считаются основными, а остальные пикселы из прямоугольной области вокруг символа2 считаются фоновыми. При выводе пунктирных линий пикселы отрезков считаются основными, а пикселы промежутков — фоновыми. Для шаблона штриховой кисти штриховые линии ри- суются основными пикселами, а промежутки между линиями — фоновыми пик- селами. Фоном графического элемента будем в дальнейшем считать совокупность его фоновых пикселов. Цвет фона графических элементов {ЪаскрроипФ со1ог) являет- ся атрибутом контекста устройства, который можно устанавливать с помощью функции 5е1ВкСо1ог. По умолчанию этот атрибут имеет значение белого цвета. Но не следует пу- тать понятие цвета фона графических элементов, который относится к контексту устройства, с понятием цвета фона окна, который является атрибутом класса окна. Чаще всего фон класса окна также имеет белый цвет. Именно поэтому при выво- де текста в программе Не11о1 нельзя было заметить цвет фона для букв текста. Он просто слился с фоном окна. Если основные пикселы графического элемента выводятся всегда, то вывод фоновых пикселов зависит от еще одного атрибута контекста устройства, кото- рый называется режимом смешивания фона {ЪаскрроипФ тгх тоФе)3. По умолчанию режим смешивания фона имеет значение О РАОIIЕ4. Этот режим указывает, что АУтйомга выводит фон графического элемента (цветом ЬаскргоипФ со1ог) поверх фона окна. Разработчик может изменить режим смешивания фона, запретив отображение фона графических элементов. Для этого нужно вызвать функ- цию ЗеЕВкМоде, передав ей в качестве второго параметра константу ТКАМ5РАКЕМТ5. Чтобы узнать, какой режим смешивания фона является текущим, следует выз- вать функцию СеЕВкМос1е. Общие операции с графическими объектами Как уже упоминалось ранее, СВ1 содержит набор графических объектов, обеспе- чивающих выполнение графических операций. К таким объектам относятся пе- 1 Глиф (&1урк) — графическая форма символа. 2 Эту область иногда называют ячейкой символа. 3 В литературе встречается другой перевод этого термина — режим заполнения фона. 4 Непрозрачный режим смешивания фона. 5 Прозрачный режим смешивания фона.
рья, кисти, растровые изображения, палитры, шрифты. Использование любого гра- фического объекта предполагает выполнение следующей последовательности опе- раций: 1. Создать графический объект. 2. Выбрать созданный объект в контекст устройства. 3. Вызвать графическую функцию, работающую с объектом. 4. Удалить объект из контекста устройства, вернув предшествующий объект. 5. Уничтожить объект. Для создания СВ1-объектов предназначены соответствующие функции Сгеа1е.. которые в случае успешного завершения возвращают дескриптор объекта. Выбор объекта в контекст устройства осуществляется при помощи функции 8е1ес1:ОЬ]'ес1: (палитры выбираются с помощью функции 5е1ес1:Ра1е1:1:е). Функция 5е1есЮЬ]ес1: имеет следующий прототип: НСО1ОВД 8е1есШэаесД( НОС Нс1с, // дескриптор контекста устройства Н60ЮВ0 НдсНоЬд // дескриптор 601-объекта ); В результате ее выполнения новый объект НдсйоЬ] заменяет предшествующий объект того же типа в контексте устройства Нс1с. Функция возвращает дескриптор предшествующего объекта. Для корректной работы приложение должно запомнить этот дескриптор и после окончания рисо- вания с новым объектом (шаг 3) вернуть в контекст устройства предшествующий объект (шаг 4). Для уничтожения объекта, ставшего ненужным, вызывается функция 0е1е1е ОЬ]ес1. Следует отметить, что не всегда для создания СВ 1-объекта нужно вызывать соответствующую функцию типа Сгеа1:е.... В системе имеется набор предопреде- ленных (стандартных) графических объектов, и если параметры предопределен- ного объекта (перо, кисть, шрифт и т. д.) подходят для решаемой задачи, то при- ложение может получить такой объект с помощью функции 6е±5±оскОЬдес±. После окончания работы с предопределенным объектом его не нужно удалять при по- мощи функции Ве1е1:еОЬ]'ес1:. Предопределенные объекты существуют в системе постоянно. Линии и кривые Теоретически, рисование любой линии можно было бы свести к многократному вызову функции 5е1:Р1хеI с соответствующим изменением координат х и у. Однако, во-первых, это не очень удобно для программиста, а во-вторых, подобный способ рисования был бы очень медленным. Значительно более эффективной в графических системах является реализация функций рисования отрезков и дру- гих сложных графических операций на уровне драйвера устройства, который со- держит код, оптимизированный для этих операций. Поэтому АУш32 СВ1 предос- тавляет соответствующий набор графических функций.
Рисование отрезков Любая линия рисуется в \У1пс1о\\ъ с использованием графического объекта, назы- ваемого пером. Контекст устройства содержит перо по умолчанию — сплошное перо черного цвета толщиной 1 пиксел. Многие графические функции начинают рисование с так называемой текущей позиции пера. Текущая позиция пера Текущая позиция пера является одним из атрибутов контекста устройства. Она определяется значением типа РО11МТ. По умолчанию текущая позиция пера уста- новлена в точку (0, 0). Если нужно изменить текущую позицию, вызывается функ- ция Мсл/еТоЕх: В001 МоуеТоЕх( НОС Мс, // дескриптор контекста устройства Ш X, // х-координата новой текущей позиции Ш V, // у-координата новой текущей позиции 1РР01МТ 1рРо1п1с // предыдущая позиция пера ); Если предыдущая позиция пера вас не интересует, передавайте последнему параметру значение МОП. В случае успешного завершения функция возвращает ненулевое значение. В любой момент можно получить значение текущей позиции пера, вызвав функцию 6е^СиггегДРо8П1опЕх(Н0С, &р^); Результат выполнения функции помещается в переменную рЕ типа Р01МТ. Рисование прямой линии Для создания прямой линии используется функция ИпеТо: ВООЕ Е1пеТо( НОС Нс1с, // дескриптор контекста устройства 1гтЕ хЕпс1, // х-координата конечной точки 1гтЕ уЕпб // у-координата конечной точки ); Эта функция рисует отрезок, начиная с точки, в которой находится текущая позиция пера, до точки (хЕпс1, уЕпс1), не включая последнюю точку в отрезок. Ко- ординаты конечной точки задаются в логических единицах. Если функция завер- шается успешно, то она возвращает ненулевое значение, а текущая позиция пера устанавливается в точку (хЕпс1, уЕпс1). Последнее свойство функции можно использовать, если требуется нарисовать ряд связанных отрезков. Например, можно определить массив из пяти точек, за- дающих контур прямоугольника, в котором последняя точка совпадает с первой, и нарисовать прямоугольник, как показано в следующем фрагменте кода: { Р01ИТ р-ЬЕВ: = {{100,100}, {200,100}, {200,200}, {100,200}, {100,100}}; МоуеТоЕх(НОС, рЦЕОО.х, рЦЕОЦ.у, ШЕЕ); Тог (1гтЕ 1 =0; 1 <5; ++1) Е1пеТо(И0С, рЕ[1].х, р№’].у); }
Вставьте этот фрагмент в код программы Не11о2 (глава 1) после вывода текста функцией Ога\л/Тех1д чтобы можно было быстро посмотреть, как он работает. На- ружные фигурные скобки в данном фрагменте делают возможным объявление локальных переменных внутри блока оператора змйсН. Рисование связанных отрезков (ломаной линии) Последовательность связанных отрезков гораздо проще нарисовать с помощью функции Ро1у11’пе: В001_ Ро1у11пе(Н0С Мс, СОИЗТ РО1ИТ* 1 рр^, 1гД сРоШз); Второй параметр здесь — это адрес массива точек, а третий — количество точек. Предыдущий пример рисования прямоугольника теперь можно переписать так: { РО1ИТ р-ЬР5: = {{100,100}, {200,100}, {200,200}, {100,200}, {100,100}}; Ро1у11пе(РОС, р^, 5); } Хотя результат выполнения этого фрагмента будет таким же, следует заметить, что функция Ро1у11’пе не использует текущую позицию пера и не изменяет ее. Функция Ро1у11 пеТо предназначена для рисования последовательности связан- ных отрезков: В001_ Ро1у11пеТо(Н0С Обе, СОИЗТ РО1ИТ* 1 рр^, ОШКО сРоШз); Она использует текущую позицию пера для начальной точки и после каждого своего выполнения устанавливает текущую позицию в конец нарисованного от- резка. Если вы захотите применить ее в рассмотренном выше примере, то вызов функции Ро1у11’пе надо заменить двумя инструкциями: МоуеТоЕхШОС, рШЪх, рШЪу, ШЮ; Ро1у11пеТо(РОС, рр + 1, 4); Функция Ро1у Ро1уГте позволяет нарисовать несколько ломаных линий за один вызов. Она имеет следующий прототип: В001 Ро1уРо1у11пе( НОС Ыс, // дескриптор контекста устройства СОИЗТ РО1ИТ* 1ррр, // массив точек для нескольких ломаных линий СОИЗТ 0Ш00* 1рРо1уРоШ8, // массив с числом точек в каждой ломаной 0Ш00 сСоьитЕ // количество ломаных линий ); Эта функция не использует и не изменяет текущей позиции пера. Дуги Дуги в АУ1пс1оау8 рисуются как часть эллипса. Размеры и расположение эллипса определяются ограничивающим прямоугольником. Ограничивающий прямоуголь- ник задается координатами левой верхней и правой нижней вершин. Если обозна- чить эти координаты как (х1_еГ1:, уТор) и (хВ1’дНЕ уВоНот), тогда центром эллипса бу- дет точка (хО, уО), где хО = хЬеП: + (хКтдЫ: - х1_еН:)/2, а уО = уТор + (уВоНот - уТор)/2. Для рисования дуг предназначены функции Аге, АгсТо и Апд1еАгс. Первые две функции имеют одинаковый набор параметров, поэтому рассмотрим прототип функции Аге:
В001 Агс(НОС Нс1с, 1гД хЕеСО, Ш уТор, 1пР хВ1 дИР, Ш уВо^от, Ш хЗОагО, Ш уЗОагО, Ш хЕпс1, Ш уЕпсО; Параметры со второго по пятый задают вершины ограничивающего прямоуголь- ника. Начало и конец дуги определяются начальным и конечным углами, которые задаются косвенно через две дополнительные точки с координатами (х5±аг(:, у51:аг(:) и(хЕпс1, уЕпс1). Начало дуги — это пересечение эллипса с лучом, который начинает- ся в центре эллипса и проходит через точку (х5±аг1:, у51:аг1:). Конец дуги — это пересе- чение эллипса с лучом, который начинается в центре эллипса и проходит через точ- ку (хЕпд, уЕпд). Все координаты задаются в логических единицах. В Ш1пс1о\\ъ 95/98 дуга рисуется против часовой стрелки. В Ш1пс1о\\ъ НТ/2000 направление дуги определяется соответствующим атрибутом в контексте устрой- ства, значение которого можно получить вызовом функции 6еЬ\гс01гесйоп или ус- тановить вызовом функции 5еМгс01гесНоп. Когда вы пользуетесь функцией 5е1Агс01гес1:1оп, в качестве второго параметра можно передать одно из значений: А0_С0иМТЕКС1_0СКМ$Е, устанавливающее режим рисования против часовой стрел- ки, либо АО_С1_ОСКМ$Е, устанавливающее режим рисования по часовой стрелке. По умолчанию в контексте устройства используется значение А0_ СОII1МТЕ КС1_ОСК\Л/18Е. На рис. 2.5 показана дуга, нарисованная при помощи вызова функции Агс(Н0С, 100, 100, 400, 300, 350, 50, 50, 300); Рис. 2.5. Дуга, нарисованная с использованием функции Аге (направление по умолчанию — против часовой стрелки) Если перед обращением к функции Аге вызвать функцию 8еОАгсО1 гесШоп(НОС, А0_СЕ0СКШ5Е); то функция Аге нарисует дугу, показанную на рис. 2.6. Функция Аге не использует текущую позицию пера и не обновляет ее. Функция АгсТо отличается от функции Аге тем, что она проводит линию из те- кущей позиции пера в заданную начальную точку дуги, и только после этого ри- сует дугу. После завершения рисования функция перемещает текущую позицию пера в конечную точку дуги. Функция Апд1еАгс, поддерживаемая только в \Упк1о\\ъ 18ГГ/2000, существенно отличается от двух предыдущих функций способом определения рисуемой дуги, что выражено и в ее прототипе: ВООЕ Апд1 еАгс (НОС Нбс, Ш X, Ш V, ОШВО гасНиз, ЕЕОАТ 80аг0Апд1е, ЕЕОАТ 8меерАпд1е);
Рис. 2.6. Дуга, нарисованная с использованием функции Агс (направление по часовой стрелке) Параметры X и V задают центр круга, а параметр гасйиз — его радиус. Дуга, рису- емая этой функцией, является частью круга. Чтобы нарисовать часть эллипса, при- ложение должно определить соответствующее преобразование или отображение. Параметр $1:аг1:Апд1е определяет начальный угол дуги в градусах, а параметр $меерАпд1е — длину дуги в градусах. Атрибут контекста устройства, отвечающий за направление рисования дуг, в этой функции не используется. При положитель- ном значении 5\л/еерАпд1е дуга рисуется против часовой стрелки, при отрицатель- ном значении — по часовой стрелке. Функция Апд1еАгс, так же как и АгсТо, проводит линию из текущей позиции пера в заданную начальную точку дуги, после чего рисует дугу и перемещает те- кущую позицию пера в конечную точку дуги. Кривые Безье В дополнение к рисованию кривых, являющихся частью эллипса, АУтйомга по- зволяет рисовать нерегулярные кривые, называемые кривыми Безье. Кривая Бе- зье (сплайн Безье) — это кубическая кривая, положение и кривизна которой за- даются четырьмя определяющими точками р1, р2, рЗ и р4. Точка р1 является стартовой точкой, точка р4 — конечной точкой. Точки р2, рЗ называются конт- рольными точками — именно они определяют форму кривой, играя роль «магни- тов», оттягивающих линию от прямой, соединяющей р1 и р4. Пример кривой Бе- зье показан на рис 2.7. + Р2 + РЗ Рис. 2.7. Кривая Безье
Математический аппарат для рисования таких кривых предложил П. Безье (Р.Веххег) в ходе разработки систем автоматизированного проектирования для компаний «Ситроен» и «Рено». Впоследствии оказалось, что кривые Безье — очень удобный инструмент для рисования кривых в компьютерной графике. АУш32 СВ1 содержит две функции, позволяющие рисовать набор связанных кривых Безье за один вызов: В001_ Ро1уВег1ег(Н0С Мс, С0И8Т РО1ИТ* 1 рр^, ОШВО сРоШз); В001_ Ро1уВег1егТо(Н0С Мс, С0И8Т РО1ИТ* 1 рр^, ОШРО сСоигД); Для рисования п кривых функция Ро1уВегтег получает адрес массива 1рр1:, содержащего Зп + 1 точек, при этом параметр сРо1п1з должен быть равен Зп + 1. Первые четыре точки, 1рр1:[О], 1рр1[1], 1рр1[2], 1рр1:[3], задают первую кри- вую. Точка 1рр1[3] вместе со следующими тремя точками определяет вторую кривую и т. д. Функция Ро1уВеДег не использует текущую позицию пера и не обновляет ее. В отличие от Ро1уВеДег, функция Ро1уВеДегТо получает Зп точек в массиве 1рр1:, при этом параметр сСоип!: должен быть равен Зп. Функция рисует первую кривую, начиная с текущей позиции пера, до позиции, заданной третьей точкой, исполь- зуя первые две точки в качестве контрольных. Каждая последующая кривая ри- суется так же, как и функцией Ро1уВеДег. По окончании рисования текущая пози- ция пера переводится в последнюю точку из массива 1ррЕ Более подробную информацию об использовании кривых Безье можно найти в издании [6]. Перья Любая функция рисования линий и кривых, а также контуров замкнутых фигур использует перо (реп), выбранное в контексте устройства в данный момент. Если вы не выбирали никакого пера, то используется перо по умолчанию В1_АСК_РЕМ. Оно рисует сплошные черные линии толщиной 1 пиксел независимо от режима отображения. Возможно, вы захотите большего разнообразия при рисовании ли- ний, например, захотите придать линиям разный цвет, разную толщину и даже разный стиль! Для удовлетворения таких запросов АУш32 СВ1 позволяет создавать объекты логических перьев. Логическое перо представляет собой описание требований к перу со стороны приложения. Эти требования могут в каких-то деталях и не соответствовать тому, как будут выводиться линии на поверхности физического устройства. Драйвер графического устройства может поддерживать собственные структуры данных, определяющие реализацию логического пера, — такие внут- ренние объекты называются физическими перьями. Структура данных логического пера находится под управлением СВ1, как и иные логические объекты. Дескриптор созданного пера возвращается приложе- нию и используется для ссылок на перо во время последующей работы. Дескрипторы объектов СВ1 описываются общим типом Н6010ВЗ; для дескрип- торов логических перьев зарезервирован тип Н РЕМ (капсЛе 1о а реп). Следует заме- тить, что тип Н 6 010 ВО определен в заголовочных файлах АУтйомга как указатель на тип УО1’с1, а Н РЕМ и другие специализированные типы указателей — как указатели
на совершенно разные структуры. Таким образом, типом НРЕМ можно заменить тип Н6010ВЗ, но попытка использования Н6010ВЗ вместо НРЕМ требует обязатель- ного преобразования типа. Итак, объект пера с дескриптором НРеп объявляется следующим образом: НРЕИ ИРеп; Значение дескриптора получают вызовом соответствующей функции. Вид вы- зываемой функции зависит от типа пера. Объект логического пера, как и другие объекты СВ1, поглощает ресурсы опера- ционной системы. Следовательно, когда необходимость в нем отпадает, рекоменду- ется исключить его из контекста устройства и удалить функцией Ое1е1:еОЬ]’ес1:1. Стандартные перья В АУш32 СВ1 определено четыре типа стандартных перьев, которые приведены в табл. 2.4. Таблица 2.4. Стандартные перья Индекс пера Описание Примечание В1_АСК_РЕЫ Сплошное черное перо толщиной 1 пиксел Установлено по умолчанию \Л/Н1ТЕ_РЕЫ Сплошное белое перо толщиной 1 пиксел М111_1__РЕМ Пустое перо (ничего не рисует) Может использоваться для вывода фигур без внешнего контура ОС.РЕЫ Перо ОС — сплошное перо толщи- ной 1 пиксел. По умолчанию оно имеет черный цвет. Этот цвет может быть изменен функцией 8еЮСРепСо1ог Реализовано только в \Л/1пс1о\л/5 2000 Для получения дескриптора стандартного объекта нужно вызвать функцию 6е1:51:оскОЬ]’ес1:, передав ей индекс стандартного объекта, как показано в следую- щей инструкции: НРеп = (НРЕЮ6еШоскОЬаесШН1ТЕ_РЕЮ; Функция 6е1:51:оскОЬ]’ес1: возвращает значение типа Н6010ВЗ, поэтому для кор- ректного присваивания выполняется преобразование типа. Получив значение дескриптора, нужно выбрать объект пера в контекст уст- ройства: 8е1есШэаесШ0С, ИРеп); После этого все функции, рисующие линии, будут использовать МН1ТЕ_РЕМ до тех пор, пока вы не выберете другое перо в контекст устройства или не освобо- дите контекст устройства. Вместо того чтобы определять переменную НРеп, можно совместить вызовы 6е151:оскОЬ]ес1: и 8е1ес1ОЬ]’ес1: в одной инструкции: 8е 1 есШаесД (РОС, беШоскОЬаесД (ШIТЕ_РЕИ)); 1 Стандартные перья удалять не нужно.
Хорошим стилем считается сохранение предшествующего дескриптора объекта и возврат его в контекст устройства после завершения операций рисования с но- вым объектом, например, как показано в следующем фрагменте кода: НРЕИ ИОПбРеп = (НРЕЮ8е1есШэаесШ0С, 6еШоскОЬ1есШН1ТЕ_РЕЮ); // ... рисуем с пером Ш1ТЕ_РЕМ // возврат в контекст устройства предыдущего пера 8е1есШдесШ0С, ИОПбРеп); Стандартное перо ОС, возвращаемое вызовом 6е1:51:оскОЬ]'ес1:(ОС_РЕ1\1), является представителем «нового поколения» объектов СВ1. Обычные объекты СВ1 «на- мертво» фиксируются при создании. Их можно использовать, можно удалять, но их нельзя изменять. Если вам понадобился слегка отличающийся объект СВ1, приходится создавать новый объект и удалять старый. Это может приводить к снижению быстродействия в определенных ситуациях. Концептуальная новизна пера ОС заключается в том, что после его выбора в контекст устройства вы можете изменять его цвет. Для этого предусмотрена функция ЗеЮСРепСсйог, имеющая следующий прототип: СОЕОЕРЕЕ ЗеУВСРепСо!ос(НОС Мс, СОЕОРРЕЕ сгСо!ог); Параметр стСо 1от задает новый цвет пера ОС. Функция возвращает предшеству- ющий цвет пера ОС. Следующий фрагмент кода показывает, как можно нарисовать градиентную заливку всего одним пером: { Н6В10В5 6016 = 5е1есЦ0ЬзесЦ(60С, 6еШоскОЬдесШС_РЕЮ); Тог (1 гтЬ 1 =0; 1 < 256; ++1) { 5еЦ0СРепСо1ог(60С, Р0В(255-п, 128, 1)); МоуеТоЕхШВС, 10, 1+10, ШЕЕ); Е1пеТо(6ВС, 266, 1+10); } 8е1есШэ0есШВС, 6016); } Вставьте этот фрагмент в код программы Не11о2 (глава 1) после вывода текста функцией ОгамТех!:, чтобы посмотреть на его работу1. Компиляция модифицированной программы, скорее всего2, будет неудачной. Компилятор сообщит, что идентификатор ОС_РЕМ ему неизвестен. Чтобы испра- вить эту ошибку, необходимо добавить в самом начале файла (еще до директивы #1 пс1ис1е <тпс1одо$.Н>) следующую строку: #6еГ1пе _ШМ32_ШМ1\1Т 0x500 После этого компиляция должна пройти успешно. Простые перья Все стандартные перья имеют сплошной цвет и единичную толщину. Чтобы ри- совать прерывистые или более толстые линии, нужно использовать нестандартные 1 Наружные фигурные скобки в данном фрагменте делают возможным объявление локальных пере- менных внутри блока оператора 5УУ1ЧсН. 2 При использовании компилятора УЕпа! С++ 6.0.
объекты логического пера. Простые перья создаются вызовом функции Сгеа1:еРеп или Сгеа1еРеп1псПгес1:. Функция Сгеа1:еРеп имеет следующий прототип: НРЕИ Сгеа^еРеп(1гД ГпРеп8Уу1е, Ш п1л/1сИзГ|, СОЮЕРЕЕ сгСо!ог); Первый параметр задает стиль пера, определяющий порядок следования пик- селов и расположение линии. Возможные значения этого параметра приведены в табл. 2.5. Таблица 2.5. Стили простых перьев Стиль Вид линии Выравнивание Ограничения на толщину Р8_8О1_Ю Сплошная По центру Нет Р8.0А8Н Пунктирная По центру п\Л/1с11:1э <= 1 Р8.00Т Точечная По центру п\Л/1с11:1э <= 1 Р8_ОА8НООТ Пунктирно-точечная По центру п\Л/1с11:1э <= 1 Р8_ОА8НООТООТ Отрезок и две точки По центру п\Л/1с11:1э <= 1 Р8_М111_1_ Не рисуется Р5-1Ы5ЮЕРКАМЕ Сплошная Внутри контура п\Л/1с11:1э > 1 На рис. 2.8 показано, как выглядят линии этих стилей. Все линии имеют тол- щину 1 пиксел. Р8_801_Ю ------------------- РЗОАЗН ------------------- РЗООТ ------------------- РЗОДЗНООТ ---------------------- РЗОДЗНООТООТ ------------------- рз_мои_ Р81М8ЮЕЕПАМЕ ------------------- Рис. 2.8. Стили простых перьев Второй параметр функции Сгеа1:еРеп задает толщину линии в логических еди- ницах. Напомним, что если в контексте устройства используется режим отобра- жения по умолчанию ММ_ТЕХТ, то логические единицы совпадают с физическими единицами и выражаются в пикселах. Если логические единицы не совпадают с физическими, то физическая тол- щина пера устанавливается в соответствии с масштабом, который определяется режимом отображения и мировыми преобразованиями, если они есть. При этом толщина пера будет всегда одинаковой только в том случае, когда масштабы по обеим осям одинаковы. Если же масштабы различаются, то вертикальные и гори- зонтальные линии будут иметь разную толщину. То же касается и наклонных ли- ний, причем их толщина будет зависеть от угла наклона. Параметру пМ’сЙН можно передать нулевое значение. В этом случае будет ис- пользоваться перо толщиной 1 пиксел независимо от режима отображения. Если задать точечный или пунктирный стиль (Р5_0А5Н, Р5_00Т, Р5_0А5Н00Т, Р5_0А5Н00Т00Т) с физической толщиной более единицы, то будет ис- пользовать перо со стилем Р$_$01_Ю. Таким образом, стилевые (прерывистые) ли- нии можно рисовать только с толщиной 1 пиксел. Обратите внимание на то, что точки в стилевых линиях изображаются тремя пикселами.
Третьему параметру функции Сгеа1:еРеп передается цвет пера в виде значения типа СОЮ КРЕЕ. Обычно это значение задается либо с помощью макроса Кб В, либо с помощью макроса РАЕЕТТЕ ВС В1. Первый вариант используется, если устройство вывода поддерживает полный диапазон цветов, определяемый 24-битным ВСВ- значением, и следовательно, приложению нет необходимости работать с палит- рой. Второй вариант (макрос РАЕЕТТЕКбВ) необходимо использовать, если прило- жение работает с логической палитрой, например, для моделей дисплеев, которые поддерживают только 256 цветов. В последнем случае система АУтйомга преобра- зует запрошенный ВСВ-цвет в наиболее подходящий индекс палитры. Характеристика пера, названная в таблице «выравниванием», проявляется наиболее существенно, когда линия используется для обводки замкнутых фи- гур или для рисования дуг, являющихся частью эллипса. Для всех стилей, кро- ме Р$_1М$ЮЕЕКАМЕ, линия центрируется таким образом, что часть линии оказы- вается за пределами ограничивающего контура. Для стиля Р5_1М5ЮЕЕКАМЕ вся линия рисуется внутри ограничивающего контура. Эту разницу можно заметить, только если параметр пМ’сЙН имеет значение большее, чем единица. Например, рисование дуг на рис. 2.5 и 2.6 выполнено пером со стилем Р5_1М5ЮЕЕКАМЕ. Если же параметр пМсЙН имеет единичное значение, то стиль Р5_1М5ЮЕЕКАМЕ совпа- дает со стилем Р5_501_Ю. Приведем пример использования функции СгеаЕеРеп. Следующий вызов со- здает простое сплошное (однородное) перо красного цвета толщиной 5 логичес- ких единиц: НРеп = СгеаСеРеп(Р8_80Е1В, 5, Р8В(255, 0, 0)); Следует отметить, что утолщенные линии рисуются всегда с закругленными окончаниями. Это можно заметить для линий, у которых параметр пМ’сЙН больше или равен трем единицам. Второй способ создания простых перьев связан с вызовом функции Сгеа1:еРеп1п- сйгесЕ НРЕИ Сгеа^еРеп1пс11 сесЬ(С0М8Т ЕОбРЕМ* 1р1дрп); Этой функции в качестве параметра передается адрес структуры типа Юб РЕМ: ЦуребеТ зШсД ЦадЕОСРЕИ { ЩИТ 8ру1е; // стиль пера РО1ИТ м'(Ж // толщина в логических единицах СОЕОРРЕЕ со1ог; // цвет } ЕСЮРЕМ; Член структуры мсИН имеет тип РО1МТ, но АУшсЬмгз использует только величи- ну м’сИН .х как толщину пера и игнорирует значение \ллс11:1п .у. Таким образом, сначала определяется переменная типа Юб РЕМ, например: ЕСЮРЕМ 1одреп; Затем полям этой переменной присваиваются нужные значения, например: 1одреп.8ру1е = Р8_80ЕЮ; 1 одреп. \л/1 с1РИ. х = 10; 1одреп.со1ог = Р8В(255, 0, 0); И только после этого вызывается функция СгеаЕеРепТпсНгес!:: НРеп = Сгеа^еРеп1пс11 гесШ!одреп); 1 См. раздел «Управление цветом. Вывод пиксела».
Простые перья, созданные с помощью функции Сгеа1е Ре п I п сП гее!, обладают точ- но такими же характеристиками, что и перья, созданные функцией Сгеа1:еРеп. Обратите внимание на то, что функции Сгеа1:еРеп и Сгеа1:еРеп1псНгес1: не требуют дескриптора контекста устройства. Они создают логические перья, которые никак не связаны с контекстом устройства, пока не будет вызвана функция 5е1есЮЬ]’ес1. Покажем возможный сценарий использования нескольких перьев в программе. Допустим, приложению требуются три нестандартных пера: красное толщиной 2, зеленое толщиной 5 и синее пунктирное. Сначала нужно определить переменные для хранения дескрипторов этих перьев: 5-Ьа-Ыс НРЕИ ПРеп1, 5Реп2, ИРепЗ; Сами перья могут быть созданы в процессе обработки сообщения ММ_СКЕАТЕ: ИРеп! = Сгеа^еРеп(Р8_80И0, 2, РСВ(255, 0, 0)); НРеп2 = Сгеа^еРеп(Р8_80И0, 5, Р8В(0, 255, 0)); НРепЗ = Сгеа^еРеп(Р8_ВА8Н, 1, РЗВ(0, 0, 255)); В процессе обработки сообщения ММ_РА1МТ можно выбрать одно из этих перь- ев в контекст устройства и рисовать с его помощью: 8е1есШэцесШВС, НРеп1); // ... функции рисования линий 8е1 есШцесД(НОС, НРеп2); // ... функции рисования линий В процессе обработки сообщения ММ_ОЕ$ТКОУ рекомендуется удалить эти перья: ВеШеОЬцесШРеп!); ВеШеОЬцесШРеп2); ВеШеОЬцесШРепЗ); Это наиболее общий подход. Но возможны и другие сценарии. Например, мож- но создать перо в блоке обработки сообщения ММ_РА1МТ и удалить его перед вызо- вом функции Епс1Ра1’п1: или даже после ее вызова. Кроме того, можно создавать перья «на лету», объединяя вызовы функций Сгеа1:еРеп и 8е1ес1ОЬ]’ес1: в одну инструкцию: 8е1есШэцесШВС, Сгеа^еРеп(Р8_В0Т, 0, РЗВ(255, 255, 0))); Теперь последующее рисование линий будет выполняться желтым пером с то- чечным стилем. Закончив рисовать, нужно удалить выбранное перо. Но как это сделать, если его дескриптор не сохранен? В этом случае на выручку приходит функция 5е1ес1:ОЬ]’ес1: с ее свойством возвращать дескриптор объекта, выбранного в контекст устройства ранее. Таким образом, можно удалить перо при помощи выбора стандартного пера В1_АСК_РЕМ в контекст устройства и удаления значения, возвращенного функцией 5е1есЮЬ]ес1:: 0е1 еЪэОЬцесД (8е1 есШцесД (НОС, ЗеШоскОЬцесД (В1_АСК_РЕМ))); Рассмотрим слегка модифицированную версию последнего примера. Дело в том, что можно сохранить дескриптор пера при первом вызове 5е1ес1:ОЬ]’ес1:: ИРеп = 8е1есШэцесШВС, Сгеа^еРеп(Р8_В0Т, 0, РЗВ(255, 255, 0))); Если это первый вызов 5е1ес1:ОЬ]’ес1: после получения дескриптора контекста устройства, то НРеп примет значение дескриптора стандартного пера В1_АСК_РЕМ. Тогда после окончания рисования выбранным пером его удаление можно совмес- тить с возвратом в контекст устройства стандартного пера: Ве1еШЬцесШе1есШэцесШВС, ВРеп));
Расширенные перья Простые перья, рассмотренные в предыдущем разделе, обладают ограниченными возможностями. Так, стилевые линии можно рисовать только толщиной 1 пик- сел, а сплошные линии рисуются любой толщиной, но всегда имеют закруглен- ные окончания. Для преодоления этих ограничений в АУш32 АР1 появилась новая функция Ех1Сгеа1:еРеп, создающая перья с расширенным набором атрибутов: НРЕИ ЕхбСгеабеРеп( ОШРО с!\л/Реп5Цу 1 е, ОШРО (УШ, С0И8Т Е06ВЕЮ* 1р1Ь, ОШРО сШЗЦу! еСоипС., С0И8Т ОШРО* 1р8бу1е // тип, стиль и атрибуты пера // толщина // атрибуты кисти // длина массива 1р8бу1е // массив, задающий правила чередования пикселов Параметр д\л/Реп51:у1е может принимать значения в виде комбинации флагов, определяющих тип пера, его стиль, тип завершения линий и тип соединения ли- ний. Возможные значения этих флагов приведены в табл. 2.6-2.9. Таблица 2.6. Типы расширенных перьев Флаг типа Описание Р5_СО5МЕТ1С Косметическое перо. Толщина равна 1 пиксел Р8_СЕОМЕТН1С Геометрическое перо. Толщина задается в логических единицах Из табл. 2.6 видно, что расширенные перья могут быть косметическими и гео- метрическими. Таблица 2.7. Стили расширенных перьев Флаг стиля Описание Ограничения Р8_А1_ТЕкЫАТЕ Р8_8О1_Ю Чередование «пиксел — промежуток» Непрерывная линия Поддерживается только в \Л/1пс1о\л/5 ЫТ/2000 и только для косметических перьев Р8.ОА8Н Пунктирная линия \Л/1пс1о\л/5 95: не поддерживается для геометрических перьев. \Л/1пс1о\л/5 98: не поддерживается Р8.ООТ Точечная линия \Л/1пс1о\л/5 95/98: не поддерживается для геометрических перьев Р8_ОА8НООТ Пунктирно-точечная линия \Л/1пс1о\л/5 95: не поддерживается для геометрических перьев. \Л/1пс1о\л/5 98: не поддерживается Р8_ОА8НООТООТ Р8_М111_1_ Отрезок и две точки Линия не рисуется \Л/1пс1о\л/5 95: не поддерживается для геометрических перьев. \Л/1пс1о\л/5 98: не поддерживается Р5_1Ы5ЮЕРКАМЕ Непрерывная линия. Выравнивание внутри контура Только для геометрических перьев Р8_118Ек8ТУ1_Е Чередование отрезков и про- межутков задается параме- трами сНл/81у1еСоип1: и 1р51у1е Поддерживается только в \Л/1пс1о\л/5 ЫТ/2000
Все стили, кроме Р5_1М5ЮЕЕКАМЕ, имеют выравнивание по центру. Интерпре- тация этого параметра была приведена при описании простых перьев. Таблица 2.8. Тип завершения, используемый только для геометрических перьев Флаг завершения Описание Р8_ЕШСАР_кОС1ЫО Закругленное завершение, когда к линии добавляется половина круга Р8_ЕШСАР_8(211АкЕ Квадратное завершение, когда к линии добавляется половина квадрата Р8_ЕШСАР_Е1_АТ Плоское завершение Таблица 2.9. Тип соединения, используемый только для геометрических перьев Флаг соединения Описание Р5_ДО11\1_ВЕУЕ1_ Усеченное соединение Р8_Ю11\1_М1ТЕк Заостренное соединение Р5_ДО11\1_КО11ЫО Закругленное соединение Косметические перья Косметические перья можно использовать только с толщиной 1 пиксел. При по- пытке задать большую толщину использует стиль Р$_$01_Ю. Для стилей Р5_0А5Н, Р5_00Т, Р5_0А5Н00Т и Р5_0А5Н00Т00Т косметические пе- рья рисуют точно такие же линии, как и простые перья. Однако, в отличие от про- стых перьев, они всегда рисуют в прозрачном режиме смешивания фона, даже если в контексте устройства установлен режим ОРАСЦ1Е. Для стиля Р$_А1_ТЕРИАТЕ косметическое перо рисует «настоящую» точечную линию, которую образуют точки размером 1 пиксел и промежутки между точка- ми также размером 1 пиксел. Для использования стиля Р5_115ЕК$ТУ1_Е необходимы два дополнительных па- раметра: с!\л/51у 1еСоип1: задает количество элементов в массиве с адресом 1р51:у1е. Пер- вый элемент массива содержит длину первого отрезка, второй — длину проме- жутка, третий — длину второго отрезка и т. д. При этом одна единица длины соответствует трем пикселам вместо одного. Геометрические перья Геометрические перья рисуют линии, которые могут различаться толщиной, сти- лем, заливкой, типом завершения и типом соединения. Толщина геометрического пера задается в логических координатах. Если в про- стых перьях единица длины, соответствующая изображению точки, равна 3 пик- села, то в геометрических перьях единица длины в режиме отображения ММ_ТЕХТ равна 1 пиксел. Но с увеличением толщины линии пропорционально увеличива- ется и длина точки, равно как и длина других отрезков, составляющих линию. Также эти размеры могут изменяться в соответствии с мировыми преобразовани- ями или режимом отображения. В отличие от простых перьев, утолщенные геометрические перья рисуют ли- нии в соответствии со своим стилем, как показано на рис. 2.9. Из рисунка также видно, что геометрические перья по умолчанию имеют завер- шение, определяемое стилем Р5_ЕМ0САР_К011М0. Но тип завершения можно изменить, если при вызове функции ЕхЕСгеаГе Ре п передать первый параметр с добавлением флага
из табл. 2.8. На рис. 2.10 показано, как выглядит буква X, состоящая из трех утолщенных линий с разными завершениями. Линии нарисованы при помощи фун- кции ИпеТо. Для большей четкости картины в местах стыковки вторая линия нарисо- вана более светлым пером, чем первая и третья линии. Тонкие белые линии, нанесен- ные поверх фигуры, обозначают положение базовых осей каждой линии. Сгез1еРеп Ех1Сгез1еРеп (Р56Е0МЕТН1С | ..., ...) Рис. 2.9. Простые и расширенные геометрические перья толщиной 10 пикселов Р5_ЕМОСАР_НОиЫО РЕ ЛЮСАР. ; 1_АТ Р5_ЕЫ0САР_5(}11АНЕ Рис. 2.10. Стыковка линий с разными завершениями Учтите, что если вы будете рисовать при помощи функции ИпеТо все три ли- нии пером одного и того же цвета в режиме рисования К2_Х0КРЕМ, то на стыках из- за повторной прорисовки образуются «провалы», как показано на рис. 2.11, слева. Та же Х-образная фигура, нарисованная функцией Ро1уПпе (на рис. 2.11 в центре и справа), таких провалов не имеет. При этом фигура справа демонстрирует воз- можность заливки линий с помощью кисти. В данном случае перед созданием гео- метрического пера поле 1Ь51у1е структуры Ю6ВК115Н было установлено в значение В5_НАТСНЕ0, а поле 1ЬНа±сЬ — в значение Н5.01А6СК0551. Нарисовано с помощью ЫпеТо Нарисовано с помощью Ро1уНпе Рис. 2.11. Рисование геометрическим пером в режиме рисования Н2_Х0НРЕЫ Функция Ро1у11пе относится к группе функций СВ1, позволяющих выполнить рисование нескольких линий и кривых за один вызов. Функции этой группы обла- дают тем замечательным свойством, что обеспечивают плавную стыковку линий. Именно для них имеет значение тип соединения геометрического пера, устанавли- ваемый флагом из табл. 2.9. По умолчанию в контексте устройства используется флаг Р5_ЗО1М_КО11МО. На рис. 2.12 показаны различные типы соединений в сочета- нии с разными типами завершения геометрического пера. Рисование производи- лось при помощи функции Ро1у11пе. 1 Работа с объектом логической кисти рассматривается далее в разделе «Кисти».
РЗ_ЕЫОСАР_РОиЫО Р5_Ю1М_РО11М0 Р5_ЕЫ0САР_РО11М0 Р5_Ю1М_ВЕУЕ1_ РЗ-ЕЫОСАР-РОиИО Р5_аО1И_М1ТЕР РЗ_ЕЫОСАР_ЗОиАРЕ Р5_Ю1М_РО11М0 Р5_ЕЫОСАР_5О0АРЕ Р5_Ю1М_ВЕУЕ1_ Р5_ЕЫ0САР_ЕЕАТ рз_ю1ы_роиыо Р5_ЕЫ0САР_ЕЕАТ Р5_Ю1Ы_ВЕ7Е1_ Рис. 2.12. Рисование с помощью Ро1у11пе для разных типов соединений и разных типов завершения геометрического пера Перейдем теперь к рассмотрению рисования замкнутых фигур. Любая замкнутая фигура, например прямоугольник или эллипс, рисуется в \У1пс1о\\ъ с использованием двух графических объектов, выбранных в контекст устройства. Первым объектом является текущее перо, которым обводится контур фигуры, а вторым объектом — текущая кисть, используемая для заливки (закра- шивания) внутренней области фигуры. Контекст устройства содержит кисть по умолчанию, которой является сплошная кисть белого цвета. Обычно фон окна тоже имеет белый цвет, поэтому, рисуя замкнутые фигуры с кистью по умолча- нию, нельзя увидеть эффект заливки. В связи с этим стоит сначала научиться соз- давать и использовать различные кисти, а уже потом знакомиться с функциями, рисующими замкнутые фигуры. Кисти Кисть — это растр размером 8x8 пикселов, который при закрашивании области дублируется в горизонтальном и вертикальном направлении. Когда АУтскпуз ис- пользует смешивание (сИЛепп§) для отображения большего числа цветов, чем до- ступно на дисплее, то на самом деле для этого используется кисть. На монохром- ном дисплее, например, смешивание черных и белых пикселов позволяет получить 64 разных оттенка серого цвета. На цветных видеосистемах полутона тоже реали- зуются при помощи подобных растровых образов, но с гораздо более широким набором доступных цветов. АУЙ132 СН1 предоставляет несколько функций для создания объектов логичес- ких кистей. Логическая кисть описывает требования, предъявляемые к заливке со стороны приложения. Эти требования не всегда совпадают с возможностями фи- зических устройств. Драйверы устройств поддерживают собственные структуры данных, определяющие реализацию логической кисти. Такие внутренние объекты называются физическими кистями.
Для дескрипторов логических кистей зарезервирован тип НВК115Н {капд,1е 1о а Ьгизк), поэтому новая кисть объявляется следующим образом: НВИСЗН ИВгизИ; Значение дескриптора получают вызовом соответствующей функции. Вид вы- зываемой функции зависит от типа кисти. Так же как и при работе с перьями, созданные кисти выбираются в контекст устройства с помощью функции 5е 1ес1:ОЬ]’ ес1, после чего заливка всех замкнутых фигур осуществляется выбранной кистью. Ког- да кисть перестает быть нужной, рекомендуется вернуть в контекст устройства прежнюю кисть, а ненужную кисть удалить при помощи функции Ое1е1еОЬ]’ес1:. В разделе «Простые перья» были приведены возможные сценарии использова- ния нескольких перьев в одном приложении. Аналогичные сценарии можно ис- пользовать и для работы приложения с несколькими кистями. Стандартные кисти Стандартные кисти, предоставляемые СВ1, приведены в табл. 2.10. Таблица 2.10. Стандартные кисти Индекс кисти Описание В1_АСК_Вк118Н ОКСНАУ_Вк115Н ОС_ВН115Н Черная кисть Темно-серая кисть Кисть ОС — сплошная цветная кисть; по умолчанию имеет белый цвет; цвет может быть изменен функцией 8еЮСВги5ИСо1ог СНАУ_Вк115Н НО1_1_СМ/_Вк115Н 1_ТСНАУ_Вк115Н М111_1__Вк115Н \Л/Н1ТЕ_ВкО5Н Серая кисть Пустая кисть (заливки нет) Светло-серая кисть То же, что и НОЬ1_СМ/_Вк118Н Белая кисть, которая используется по умолчанию Все кисти, приведенные в таблице, — сплошные, то есть все 8x8 пикселов растра имеют один и тот же цвет. Кисть ОС, реализованная только в АУтйомга 2000, относится к числу новых средств СВ1. Так же как и перо ОС, кисть ОС позволяет изменять свой цвет после ее выбора в контекст устройства. Для этого предназна- чена функция 5е10СВги$НСо1ог. Чтобы получить дескриптор стандартной кисти, достаточно вызвать функцию Се181:оскОЬ]'ес1: с одной из констант, приведенных в табл. 2.10, например: ПВгизИ = (НВКС8Н) беШоскОЬаесШВАУ-ВВиВН); Пользовательские кисти Стандартные кисти хороши простотой своего использования, но их возможности явно недостаточны для нужд многих приложений. Поэтому АУтйомга содержит функции, создающие пользовательские кисти следующих типов: сплошные кисти, штриховые кисти и растровые кисти. Сплошные кисти Сплошная кисть создается вызовом функции Сгеа1е5о11’с1 ВгизН: НВВВ8Н Сгеа^еВоШВгизтСОВОВВЕГ сгСо1ог);
Ее единственному параметру сгСо 1ог передается цвет кисти в виде значения типа СОЮРРЕЕ. Обычно это значение задается при помощи макроса К6В или РАЕЕТТЕКС В1. Первый вариант используется, если устройство вывода поддерживает полный диапазон цветов, определяемый 24-битным КСВ-значением. В этом случае при- ложению нет необходимости работать с палитрой. Второй вариант необходимо использовать, если приложение работает с логической палитрой. В последнем случае преобразует запрошенный в макросе РАЕЕТТЕКСВ цвет в наиболее подходящий индекс палитры. Если подходящий индекс не найден, то устройство имитирует нужный цвет, комбинируя доступные цвета при помощи смешения (сИЛепп§). Примеры использования сплошной кисти можно найти в листингах глав 3 и 6. Штриховые кисти Штриховая кисть создается вызовом функции СгеаЕеНаЕсНВгизГ!: НВК1Э8Н Сгеа^еНаШВгизШгД Гп8Су1е, СОЕСШЕЕ с!ггеГ); Параметр Гп51у1е, задающий стиль штриховки, может принимать значения Н5_Н0Ш0МТА1_, Н5_УЕВТ1САЕ, Н$_В01А60МА1_, Н$_Е01А60МА1_, Н$_СВО55и Н5_01А6СР055. На рис. 2.13 показано, как выглядят эти стили при заливке прямоугольников размером 64 х 64 пиксела (верхний ряд). В нижней части рисунка приведены в увеличенном виде минимальные блоки, соответствующие этим штриховым ки- стям. Каждый минимальный блок представляет собой растр размером 8x8 пик- селов. □□□□□□□□ □□□□□□□□ □□□□□□□□ □□□□□□□□ □□□□□□□□ □□□□□□□□ □□□□□□□□ Н5_В01А60МА1_ □□□□□□□ □□□□□□□ □□□□□□□ □□□□□□□ □□□□□□□ □□□□□□□ Н5_Е01АООЫАЬ □□□□□□□ □□□□□□□ □□□□□□□ □□□□□□□ □□□□□□□ □□□□□□□ Н5_СКО55 Н5_01АОСКО55 □□□□□□в □□□□□□ Рис. 2.13. Стили штриховых кистей В штриховых кистях пикселы делятся на основные и фоновые. На рис. 2.13 ос- новные пикселы показаны более темным цветом. Цвет основных пикселов зада- ется параметром сЕггеГ, а цвет фоновых пикселов определяется атрибутом цвета фона графических элементов в контексте устройства, который можно устанавли- вать при помощи функции 5е1В кСоЕог. Фоновые пикселы выводятся только в том случае, если установлен режим смешивания фона О РАОIIЕ. СН1 позволяет также устанавливать базовую точку штриховой кисти с помо- щью функции $е1Вги$НОгдЕх: ВООЕ 8е^ВгизИ0гдЕх(Н0С Нбс, Ш пХОгд, Ш пХОгд, ЕРРО1МТ 1ррО); Базовая точка (пХОгд, пУОгд), задаваемая в системе координат устройства, оп- ределяет привязку левого верхнего пиксела штрихового узора. Остальные блоки пикселов выстраиваются соответствующим образом. Параметр 1рр1 содержит ад- рес структуры типа РОНХ1Т, в которой запоминается прежняя базовая точка. По умолчанию координаты базовой точки кисти равны (0, 0). 1 См. раздел «Управление цветом. Вывод пиксела».
Предположим, что вы рисуете прямоугольник, имея перо толщиной 1 пик- сел, и используете штриховую кисть для заливки. Если вы хотите, чтобы левый верхний угол первого пиксельного блока штриховой кисти в точности совпадал с левым верхним углом заливаемой области, то этого можно достичь вызовом функции 8е^ВгизН0гдЕх(НВС, хЕеИ + 1, уТор + 1, ШЕЕ); где хЕеГЕ и уТор — координаты левого верхнего угла прямоугольника. Обратите внимание на то, что смещение на единицу обусловлено единичной толщиной ис- пользуемого пера. Растровые кисти Растровая (или узорная} кисть создается при помощи функции Сгеа^еРайегпВгизН: НВкиЗН Сгеа^еРаНегпВгизИСНВПМАР НЬтр); Параметр НЬтр содержит дескриптор растрового объекта СВ1. Поскольку растровый объект СВ1 {ЪИтар), называемый также аппаратно- зависимым растром (ВВВ)1, может содержать произвольное изображение, то про- граммист получает неограниченные возможности создания рисунков для залив- ки замкнутых областей. Давайте проведем следующий эксперимент с растровой кистью. При помощи графического редактора М8 Раш! создайте рисунок размером 32 х 32 пиксела, содержащий изображение пятиконечной звезды, как показано на рис. 2.14. Это изображение следует сохранить в файле 51аг.Ьтр. Рис. 2.14. Изображение звезды, созданное в редакторе М8 Ра1п1 Затем вставьте в код программы Не11о2, как мы это делали раньше, следующий фрагмент: { НВ1ТМАР НВтр = (НВ 1ТМАР) 1_оаб 1таде(ШЕЕ, "Иаг.Ьтр", 1МАОЕ_В1ТМАР, О, О, ЕР-ЕОАВРРОМР1ЕЕ); НВРНЗН НВгизН = Сгеа^еРаНегпВгизН(НВтр); НВРНЗН ШШВгиИ = (НВРНЗН)3е1 есШэ^есД(НОС, НВгизН); РесЦапд!е(НВС, 32, 32, 288, 160); Ве1еН0Ь!есШе1есШэ!есШВС, НО!ПВгизН)); } Функция 1оас11таде в этом фрагменте загружает аппаратно-независимый растр (ЫВ)2 из файла 51аг.Ьтр, затем преобразует его к формату растрового объекта СВ1 и возвращает дескриптор этого объекта НВтр. Полученный дескриптор пе- редается функции Сгеа^еРаНегпВгизН, создающей растровую кисть. Кисть НВгизН 1 Вопросы использования растров рассматриваются в главе 3. 2 Аппаратно-зависимые и аппаратно-независимые растры рассматриваются в главе 3.
выбирается в контекст устройства и после этого используется для заливки внут- ренней области прямоугольника, рисуемого функцией Кес1апд1е1. Чтобы программа смогла загрузить файл 51аг.Ьтр, нужно поместить его в пап- ку с проектом Не Но 2. После компиляции и запуска на выполнение программа долж- на нарисовать прямоугольник, показанный на рис. 2.15. Рис. 2.15. Заливка прямоугольника растровой кистью В системах АУтйомга 95/98 накладывается ограничение на применение рас- тровых кистей. Битовые образы кистей не могут превышать размер 8x8 пиксе- лов. В случае превышения размера используется квадрат 8 х 8 из левого верхнего угла изображения. Пример использования растровой кисти приведен в главе 11. Структура 1_О6В1Ш5Н и функция Сгеа1еВги$Н1пс1|гес1 АУш32 СВ1 предоставляет еще одну функцию — Сгеа1еВгизЫпсНгес1, позволяющую создать логическую кисть любого из трех рассмотренных выше типов: НВКС18Н СгеаЦеВгизМпсН гесЦ(С0М8Т ЬСОЕЮ* 1р1Ь); Параметр 1рШ — это адрес структуры типа Ю6ВК115Н, в которой задаются пара- метры кисти: 1зурес1еТ ЮисД ^адЮбВЕВЗН { ЮТ 1Юу1е; СОЮВВЕГ 1ЬСо1ог; Юб ИэНаШ; } ЮВЮН; Поле 1Ь51у1е задает стиль кисти и может иметь одно из значений, приведенных в табл. 2.11. Таблица 2.11. Возможные значения поля 1Ь51у1е Значение Описание В8_8О1_Ю В5_НАТСНЕ0 В5_Н01_1_0\Л/ В8_М111_1_ В5_РАТТЕНМ Сплошная кисть Штриховая кисть Пустая кисть (заливки нет) То же, что и В5_НОИО\Л/ Растровая кисть, рисунок которой определен растровым изображением в памяти В5_РАТТЕНЫ8Х8 В5_01ВРАТТЕНЫ То же, что и В8_РАТТЕРЫ Растровая кисть, рисунок которой задается аппаратно-независимым растровым изображением (01В — беуюе-1пс1ерепс1еп1: ЬКтар), дескриптор которого содержится в поле 1ЬНа1:сГ| 1 Функции, рисующие замкнутые фигуры, рассматриваются в следующем разделе.
Значение Описание В8_О1ВРАТТЕкЫ8Х8 То же, что и В8_01ВРАТТЕкЫ В8_01ВРАТТЕкЫРТ Растровая кисть, рисунок которой задается аппаратно-независимым растровым изображением (01В). Поле 1ЬНа1сН содержит указатель на 01В- изображение Поле 1ЬСо1ог задает цвет кисти. Его интерпретация зависит от значения поля 1Ь51у1е и поясняется в табл. 2.12. Таблица 2.12. Интерпретация поля 1ЬСо1ог Значения поля 1Ь51у1е Интерпретация поля 1ЬСо1ог В5_Н01_1_0\Л/ или ВЗ.РАТТЕкЫ В8_НАТСНЕ0 или В8_8О1_Ю В8_01ВРАТТЕкЫ или В8_01ВРАТТЕкЫРТ Игнорируется Значение типа СОЮккЕЕ Младшая часть слова 1ЬСо1ог должна иметь значение 01В_РА1__С01_0к8 или 01В_к6В_С01_0к8. Первое значение указывает на то, что в массиве ЬгтнСо1ог5, являющемся членом структуры В1ТМАР1ЫЕ0, содер- жатся 16-разрядные индексы цветов в логической палитре, второе значение — на то, что в массиве ЬгтнСо1ог5 содержатся явные кСВ-коды цветов Поле 1ЬНа1сН задает стиль штриховки. Его интерпретация также зависит от зна- чения поля 1Ь51у1е и поясняется в табл. 2.13. Таблица 2.13. Интерпретация поля 1ЬНа1сИ Значения поля 1Ь51у1е Интерпретация поля 1ЬНа1сН В8_8О1_Ю или В5_Н01_1_0\Л/ Игнорируется В8_РАТТЕкЫ Дескриптор растрового объекта 601 (Ьктар'а) В8_НАТСНЕ0 Одно из значений Н8_НОк12О1\1ТА1_, Н8_УЕкТ1СА1_, Н8_В01А60ЫА1_, Н8_Е01А60ЫА1_, Н8_СкО88, Н8_О1А6СкО88 В8_01ВРАТТЕкЫ Дескриптор упакованного 01В-растра В8_01ВРАТТЕкЫРТ Указатель на упакованный 01В-растр Структура Ю6ВК115Н также используется при создании расширенных перьев, которые рассматривались ранее. Например, третья фигура на рис. 2.11 была нари- сована с использованием данной структуры. Замкнутые фигуры Напомним, что любая замкнутая фигура рисуется в АУтйомга с использованием текущего пера, которым обводится контур фигуры, и текущей кисти, которая ис- пользуется для заливки внутренней области фигуры. По умолчанию в контексте устройства выбрана сплошная кисть белого цвета. Прямоугольники Наиболее важной геометрической фигурой в АУтбом^з является прямоуголь- ник. Прямоугольники применяются при определении окон и клиентских
областей, различных фигур с прямоугольным ограничивающим контуром, при отсечении и при форматировании текста. Поэтому в АУт32 АР1 определены специальная структура данных для представления прямоугольников и много- численные функции как для работы с этой структурой, так и для рисования прямоугольников. Прямоугольник как структура данных Эта структура данных уже рассматривалась в главе 1, но все же напомним ее оп- ределение: 1зурес1еТ ЦадКЕСТ { ЕСЖ 1еП; ЕСЖ 'Ёор; ЕСЖ г1дЩ; ЕСЖ ЬоНот; } ВЕСТ; Поля структуры задают координаты левого верхнего угла (1еТ±, 1ор) и правого нижнего угла (пдЫ:, ЬоНот) прямоугольника. Допустим, что в программе определена переменная этого типа: КЕСТ гесТ; Для установки всех полей структуры гес1 при обычной записи вам пришлось бы написать примерно следующий код: гесТЛеТТ = х_1еТТ; гесТ.Тор = у_Тор; гесТ.п’дИТ = х_г1дЩ; гесТ.ЬоНот = у_ЬоНот; Но если воспользоваться функцией 5е1Кес1:, имеющей прототип: ВОСЕ ЗеТКесТ(ЕРКЕСТ 1ргс, Ш хЕеТТ, Ш уТор, Ш хК1 дИТ, Ш уВоНот); то четыре строчки кода можно заменить одной: ЗеТКесШгесТ, х_1еТТ, у_Тор, х_г1д1тЕ, у_ЬоНот); Кроме этой функции АУш32 АР1 предлагает еще восемь функций для работы со структурами типа КЕСТ. В табл. 2.14 показано использование этих функций. Таблица 2.14. Функции для работы со структурами типа КЕСТ Функция Описание ЗеЩесЦ&гесЦ хЬей, уТор, х^дН, уВойот); Установить координаты прямоугольника в заданные значения ОЛъеЩесЦ&гесЦ дХ, с!У); Переместить прямоугольник по оси X на величину 6Х и по оси У на величину с!У ТпЛа^еКес^&гесС дХ, с!У); Уменьшить или увеличить ширину и высоту прямоугольника 8е1Кес1:Етр1у(&гес1:); Установить все поля структуры гес1: в нуль СоруКеЩ&ОезЩесС &8гсКесЕ); Скопировать один прямоугольник в другой ТпЕегзесЩесЦ&ОезЩесЦ &8гсКесЕ1, &8гсКесЕ2); Получить пересечение двух прямоугольников ипюпКесЦ&ОезЩесЦ &5гскеси, &8гскес1:2); Получить объединение двух прямоугольников ЬЕтр1у = 1зКес1:Етр1у(&гес1:); Определить, является ли прямоугольник пустым ЫпКес1: = РНпКес^&гесС ротЕ); Определить, содержится ли точка ро!п1: внутри прямоугольника гес1:
Рисование прямоугольников Для рисования прямоугольников предусмотрены функции Кес1апд1е, Н11Кес1, ЕгатеВес!, 1пуег1:Кес1: и ОгамЕосизВесС Яес1апд1е Функция Кес1:апд1е имеет следующий прототип: В001_ КесДапд!е(НОС Нбс, Ш х1_еГС Ш уТор, Ш хК1 дНР, Ш уВоШит!): Она рисует прямоугольник, определяемый четырьмя координатами. Парамет- ры х1_еГ1: и уТор задают положение левого верхнего угла, а хК1дН1: и уВоИот — положе- ние правого нижнего угла. Все координаты определяются в логических единицах. Стороны прямоугольника всегда параллельны горизонтальной и вертикальной сто- ронам экрана. Хотя в М8ВХ утверждается, что необходимо выполнять условиях Ее П < хд Н1: и уТо р < у ВоНот, экспериментальная проверка показывает, что если второй и тре- тий параметры задают правый нижний угол, а четвертый и пятый параметры — левый верхний угол, то АУшсЬауз справляется с этой «головоломкой» и рисует требуемый прямоугольник. На процесс рисования влияют многие атрибуты, содержащиеся в контексте устройства: □ графический режим, который может быть совместимым (6М_С0МРАТ1В1_Е) или расширенным (6М_А0\/АМСЕ0); □ стиль и цвет пера; □ толщина пера; □ режим рисования (текущая бинарная растровая операция). Рисунок 2.16 иллюстрирует результаты применения функции Кес1:апд1е при разных атрибутах контекста устройства. □□□□□□□□□□□ □□□□□□□□□□□ □□ □□□□□□□□□□□□ □□□о □□□□□□□□□□□□ □□□□□□□□□□□□ □□□о □□□□□□□□□□□□ □□□□□□□□□□□□ □□□□ □□□□ □□□□□□□□□□□□□□ □□□□□□□□□□а □□□□□□□□□□а □□□□□□□□□□а □□□□□□□□□□□□□а □□□□о □□□□□□□□□□□□□□ □□□□□□□□□□□□□а □□□□о □□□□□□□□□□□□□□ □□□□□□□□□□□□□а □□□□□ □□□□□□□□□□□□□□ □□□□□□□□□□□а □□□□□□□□□□□а СМ_АВ7АИСЕВ Р5_501_10 пУМсМ = 1 9М_С0МРАТ1ВЕЕ Р5_801_Ю = 3 6М_С0МРАТ1В1_Е Р5_1№ЮЕЕРАМЕ пУУИШ = 3 6М_С0МРАТ1В1_Е Р5_5О1_Ю п\Л/1±Н = 1 6М_С0МРАТ1ВЕЕ Р5_ШЫ в = 1 Рис. 2.16. Рисование прямоугольников функцией кес1апд1е Рисование выполнялось черным пером, за исключением второго прямоуголь- ника, нарисованного пустым пером, и серой стандартной кистью (6КАУ_ВК115Е1), выбранной в контекст устройства. Прежде всего отметим, что в совместимом графическом режиме, который ус- тановлен по умолчанию1, прямоугольник рисуется так, что действительное поло- жение его правого нижнего угла определяется точкой (хКтдЫ: х 1, уВоНот - 1). 1 См. раздел «Системы координат и преобразования».
В этом режиме нарисованы первый, второй, четвертый и пятый прямоугольники. И только в расширенном графическом режиме, в котором был нарисован третий прямоугольник, действительное положение правого нижнего угла определяется точкой (хК1’дЫ:, уВоНот). Когда прямоугольник рисуется пустым пером Р$_М1Л_1_, то его размеры умень- шаются на один пиксел по ширине и на один пиксел по высоте. Назовем базовой линией ту линию, которая рисуется при единичной толщине пера. Если толщина пера пМ’сЙН больше единицы, то один пиксел рисуется на ба- зовой линии, (пМ’сЙН - 1)/2 пикселов рисуются снаружи от этой линии и еще столько же пикселов рисуются внутри прямоугольника. Но что происходит, ког- да величина пМсНФ является четным числом и (пМ’сЙН - 1) не делится нацело на два? Остаток в виде половины пиксела трансформируется в утолщение на один пиксел сверху для горизонтальных линий и на один пиксел слева — для верти- кальных линий. Все сказанное выше о толстых линиях справедливо для всех стилей пера, кро- ме стиля Р5_1М5ЮЕЕВАМЕ. Этот стиль задает выравнивание внутри контура. На крайнем справа прямоугольнике хорошо виден механизм его работы. Один пик- сел рисуется на базовой линии, а все остальные — внутри прямоугольника. Текущая растровая операция в контексте устройства распространяется как на периметр, так и на внутреннюю часть прямоугольника. Р||1Яес1 Функция ННКес± имеет следующий прототип: В001_ Е111 ВесЦ(НОС Мс, С0И8Т ВЕСТ* 1 ргс, НВВС8Н ИЬг); Она закрашивает прямоугольник, определяемый структурой типа КЕСТ, адрес которой указан в параметре 1ргс. Для закрашивания используется кисть, дескрип- тор которой передается через параметр НЬг. Действительное положение правого нижнего угла прямоугольника определя- ется точкой (фгс.лдЫ: - 1, фгс.ЬоНот - 1), независимо от используемого графи- ческого режима. Следует обратить внимание на три важных свойства этой функции: □ кисть передается функции непосредственно через третий параметр, поэтому ее не нужно задавать в контексте устройства; □ перо в этой функции вообще не используется; □ функция не использует атрибут бинарной растровой операции в контексте ус- тройства. РгатеЯес± Функция ЕгатеКес!: имеет следующий прототип: ВООЕ ЕгатеВесШОС Мс, СОИЗТ ВЕСТ* 1 ргс, НВВС5Н ИЬг); Она закрашивает периметр прямоугольника 1ргс кистью НЬг. Толщина рисуе- мой рамки равна одной логической единице. Обратите внимание на то, что рамка рисуется кистью, а не пером. Это позволя- ет получать интересные эффекты в случае использования растровой кисти. На- пример, можно создать растровую кисть с «шахматным» узором», когда в растре черные и белые пикселы чередуются, как на шахматной доске. Если передать
дескриптор этой кисти функции ЕгатеВесЕ то периметр прямоугольника будет на- рисован «настоящей» точечной линией1. 1пуег1Яес± Функция 1пуег±Кес± имеет следующий прототип: В001_ ШегШсШОС Мс, С0И8Т ВЕСТ* 1ргс); Она инвертирует цвет каждого пиксела внутри заданного прямоугольника. Если графическое устройство использует палитру, то инвертируются индексы па- литры. В устройствах без палитры черный цвет переходит в белый, белый цвет — в черный, а ВСВ-значение каждого пиксела инвертируется. При двукратном вызове функции 1п7ег±Кес1: с одинаковыми параметрами вос- станавливается первоначальное изображение. ОгашРоси$Яес± Функция ОгамЕоси$Вес1: имеет следующий прототип: ВООЕ ОгамЕосизВесШОС Мс, СОИЗТ КЕСТ* 1ргс); По своему действию она напоминает функцию ЕгатеКесЕ рисуя периметр пря- моугольника шахматной узорной кистью с применением растровой операции «Исключающее ИЛИ». Повторный вызов функции с теми же параметрами вос- станавливает первоначальное изображение. Функция активно используется теми модулями АУш32 АР1, которые отвечают за управление окнами. Например, в диалоговых окнах функция ОгамЕоси$Кес1: ри- сует точечный контур прямоугольника на кнопке, получающей фокус ввода с клавиатуры. Когда фокус переходит к другой кнопке, прямоугольник стирается повторным вызовом функции ОгамЕосизКесЕ Функция ОгамЕоси$Кес1: также может использоваться при выводе «эластичных» прямоугольников. Эллипсы, сегменты, секторы и закругленные прямоугольники АУш32 СВ1 содержит несколько функций для рисования эллипса, его частей и даже гибрида прямоугольника с эллипсом, который выглядит как прямоуголь- ник с закругленными углами. Эллипсы Для рисования эллипсов предназначена функция ЕШрзе, прототип которой при- веден ниже: ВООЕ Е111рзе(Н0С Мс, Ш хЕеТО, Ш уТор, Ш хВ1 дИО, Ш уВоНот); Функция рисует эллипс в ограничивающем прямоугольнике, заданном коор- динатами хкеК, уТор, хКтдЫ:, уВоНот. Все сказанное выше о влиянии атрибутов в контексте устройства на процесс рисования прямоугольников распространяется также и на рисование эллипсов функцией Е1Нр$е. 1 См. замечания по поводу стиля Р$_А1_ТЕКМАТЕ для косметических перьев.
Сегменты В разделе «Линии и кривые» уже рассматривалось рисование дуги эллипса с по- мощью функции Аге. Сегмент эллипса образуется из дуги, если ее концы соеди- нить отрезком, называемым хордой. Для рисования сегментов используется функция СНогс1: В001 СИогсКНОС Нбс, Ш хЕеТР, 1пР уТор, 1пР хВ1дИР, 1пР уВоРРот, 1пР хЗРагР, 1пР уЗРагР, 1пР хЕпб, 1пР уЕпсО; Функция принимает такой же набор параметров, что и функция Аге. Началь- ный и конечный углы дуги задаются двумя точками с координатами (х81а 1±, у5±а1±) и (хЕпс1, уЕпс1). Направление рисования дуги, а следовательно, и вид получаемой фигуры определяются так же, как и для функции Аге. На рис. 2.17 показано при- менение функции СНоМ. Рисование против часовой стрелки Рис. 2.17. Применение функции СНогс! Рисование по часовой стрелке Секторы Сектор эллипса — это фигура, ограниченная дугой и двумя радиусами, проведен- ными к концам дуги. Секторы рисуются функцией Р1’е, прототип которой приведен ниже: В001 Р1е(НВС Мс, 1пР хЕеРР, 1пР уТор, 1пР хВ1 дНР, 1пР уВоРРот, 1пР хЗРагР, 1пР уЗРагР, 1пР хЕпб, 1пР уЕпс1); Функция принимает такой же набор параметров, что и функция Аге. Направ- ление рисования дуги и вид получаемой фигуры определяются так же, как и для функции Аге. На рис. 2.18 показано применение функции Р1’е. Закругленные прямоугольники Прямоугольник с закругленными углами создается при помощи функции Ко и п с1 КесР, имеющей следующий прототип: ВООЕ ВоигкШесР(НОС Мс, 1пР хЕеРР, 1пР уТор, 1пР хВ1 дВР, 1пР уВоРРот, 1пР гоипс11л/1 сИаН, 1пР гоипсШетдИР) ; Функция позволяет нарисовать прямоугольник, эллипс или любую промежу- точную фигуру. Первые пять параметров функции совпадают с параметрами функ- ции КесРапд 1е. Последние два параметра определяют ширину и высоту маленьких эллипсов, используемых для закругления углов прямоугольника, как это показа- но на рис. 2.19.
(хЕпс1, уЕпс!) (хй1дЫ, уВоГГот) Рисование против часовой стрелки Рис. 2.18. Применение функции Р1е (хЕпс!, уЕпс!) (хК|дЫ:, уВоНот) Рисование по часовой стрелке Рис. 2.19. Прямоугольник, нарисованный функцией Ноипс1кес1: Если параметры гоипд\Л/1 д1:Н и гоипсШеп’дЫ: имеют нулевое значение, то функция КоипсШесЕ рисует прямоугольник. Если параметр гоипс1\Л/1’с11:Н равен хКтдЫ: - х1_еГ1: и одновременно параметр гоипс!Неп’дЫ: равен уВоНот - уТор, то функция КоипдКесЕ нарисует эллипс. Многоугольники Прямоугольник является частным случаем многоугольника. Для рисования про- извольного многоугольника предназначена функция Ро1удоп: В001 Ро1удоп(Н0С Нбс, С0И8Т Р01ИТ* 1рРолгДз, Ш пСоигД); Работа этой функции напоминает рисование ломаных линий функцией Ро1у 11 пе. Второй параметр функции принимает адрес массива точек, а третий — количе- ство точек. В отличие от Ро1у11пе, функция Ро1удоп автоматически замыкает фигу- ру. В случае использования геометрического пера функция Ро1удоп, так же как и Ро1у11’пе, оформляет каждую вершину в соответствии с атрибутом соединения. Для выпуклых многоугольников внутренняя область определяется достаточ- но четко. Однако невыпуклый многоугольник может состоять из нескольких частей, как, например, пятиконечная звезда, что затрудняет определение его
внутренней области. Такие же проблемы могут возникнуть при перекрытии од- ного многоугольника другим. В связи с этим АУш32 СВ1 предусматривает два ре- жима заполнения многоугольников: режим АПЕКМАТЕ и режим ММ01М6. Контекст устройства содержит соответствующий атрибут, который можно изменять, вызы- вая функцию 5е1Ро1уЕ1’ИМос1е. По умолчанию установлен режим заполнения мно- гоугольников АПЕКМАТЕ. На рис. 2.20 показано рисование пятиконечной звезды при разных режимах заполнения многоугольников. ☆ ☆ Рис. 2.20. Фигуры, нарисованные в двух режимах закрашивания многоугольника: слева — АЕГЕНЫАТЕ; справа — ХЛ/1М01МС В режиме АНЕКМАТЕ принадлежность некоторой точки внутренней области оп- ределяется при помощи довольно простого алгоритма: 1. Проведем мысленно горизонтальную линию сканирования через интересую- щую нас точку. 2. Пронумеруем точки пересечения этой линии с встречающимися на ее пути ли- ниями многоугольника. 3. Если интересующая нас точка лежит между нечетным и четным пересечения- ми, то она принадлежит внутренней области многоугольника. Это правило иллюстрируется левой фигурой, показанной на рис. 2.21. В режиме \Л/1М 01И 6 принадлежность некоторой точки внутренней области оп- ределяется с учетом направления рисования сторон многоугольника по следую- щему алгоритму: 1. Проведем мысленно горизонтальную линию сканирования через интересую- щую нас точку. 2. Установим нулевое значение счетчика соип! до первого пересечения этой ли- нии с многоугольником. 3. При каждом следующем пересечении линии многоугольника анализируем на- правление рисования этой линии по отношению к линии сканирования. Если это направление по часовой стрелке, то значение счетчика соип! увеличивает- ся на единицу, если против часовой стрелки, то уменьшается на единицу. 4. Если текущее значение счетчика соип! больше нуля, то точка принадлежит внут- ренней области, в противном случае точка считается внешней. Это правило иллюстрируется правой фигурой, показанной на рис. 2.21. Для рисования серии многоугольников за один вызов предназначена функция Ро1уРо1удоп: В001 Ро1уРо1удоп(НОС Мс, С0И8Т Р01ИТ* 1рРо1п1з8, С0И8Т Ш* 1 рРо!уСоигДз, Ш пСоигД); Второй параметр функции содержит адрес массива точек для всех многоуголь- ников. Многоугольники определяются последовательно один за другим. Каждый
многоугольник замыкается автоматически проведением линии от последней вер- шины к первой. соип1 = О Рис. 2.21. Определение принадлежности точки многоугольнику при разных режимах закрашивания многоугольника: слева — АЕГЕНЫАТЕ; справа — \Л/1Ы011\16 Третий параметр функции — адрес массива целых чисел, задающих количе- ство вершин в соответствующем многоугольнике. Каждое число должно быть не менее двух. Если количество вершин равно двум, то многоугольник вырождается в прямую линию. Четвертый параметр функции задает количество рисуемых мно- гоугольников. Регионы и отсечение Регион — это совокупность точек в координатном пространстве. Эта совокупность может быть пустой, а может занимать все координатное пространство. Она может иметь прямоугольную или любую неправильную форму. Регионы так же, как пе- рья, кисти, битовые образы и многие другие элементы, являются объектами СВ1. После создания объекта региона приложение получает его дескриптор, который относится к типу НК6М. Когда объект региона становится ненужным, его следует удалить функцией Ое1е1:еОЬ]’ес1:. Регионы могут использоваться как для рисова- ния, так и для отсечения. Создание регионов Простейший тип региона — это прямоугольник. После объявления объекта ре- гиона: НО НВдп; вы можете создать прямоугольный регион одним из двух способов: НВдп = Сгеа^еКесДКдпСхЬеЧ, уТор, хВ1дНР, уВоШип); или НВдп = Сгеа’ЬеВесЬВдпТпсНгесЬС&гесЬ): Вы может также создать эллиптический регион, используя следующие вызовы: НВдп = СгеаЦеЕ! 11 рР1 сВдп(хЬеЧ, уТор, хВ1дНР, уВоШип); или НВдп = СгеаЦеЕ! 11рР1сВдп1пс11гесД(&гесД);
Прямоугольный регион с закругленными углами тоже формируется довольно просто: ИКдп = СгеаЕеКоигкШесЕКдп(хЕеТЕ, уТор, хК1дИЕ, уВоЕЕот, гоипс11л/1 сИзН, гоипс!Не1 дГ|1з); Создание многоугольного региона похоже на использование функции Ро1удоп: ИВдп = СгеаЕеРо1удопКдп(ро1пЕ, 1СоипЕ, 1Ро1уП11Мос1е); Аргумент рслпЕ содержит адрес массива структур типа Р01МТ. В аргументе 1 Со и п1: задается количество точек, а аргумент 1 Ро1уЕШМоде принимает значение АПЕКМАТЕ или М1М01М6. Также можно создать сложный регион, состоящий из нескольких многоуголь- ников при помощи функции Сгеа1еРо1уРо1удопКдп: ВВдп = СгеаЕеРо1уРо1удоп(ро1пЕ, ро1уСоипР8, 1СоипЕ, 1Ро1уП11Мос1е); Использование этой функции аналогично использованию функции Ро1уРоЕудоп. Операции с объектами регионов Регион представляет собой множество точек двумерного пространства, поэтому многие операции над регионами выглядят как операции над множествами. В табл. 2.15 приведены функции, реализующие эти операции. Таблица 2.15. Функции для работы с регионами Функция Назначение ВООЕ РНпкед10п(НкСЫ Нгдп, 1П1 X, 1П1 V); ВООЕ кесНпкед10п(НкСЫ Нгдп, С0Ы8Т НЕСТ *1ргс); ВООЕ Едиа1кдп(ННСЫ Нкдп1, ИКСЫ Нкдп2); 1П1 СеЩдпВох(ННСЫ Нгдп, ЕРНЕСТ 1ргс); 1П1 СотЫпекдп(ННСЫ НгдпОез!:, ННСЫ НгдпЗгс1, ННСЫ Игдп8гс2, тЪ ГпСотЫпеМос1е); 1П1 ОГГзеЩдп(НкСЫ Нгдп, 1п1: пХОТзеЦ 1п1: пУОГГзеЦ; ОМОКО СеЩедюпОа1:а(ННСЫ НКдп, ОМОКО б\л/Соип1, ЕРНОШАТА 1рНдпОа1а); НН6Ы Ех1Сгеа1еНедюп(С01\18Т ХЕОНМ *1рХГогт, ОМОНО пСоиШ, С01М8Т НОШАТА *1рНдпОа1а); Проверяет, принадлежит ли точка (X, V) региону Нгдп Проверяет, принадлежит ли хотя бы одна точка прямоугольника 1ргс региону Нгдп Проверяет, содержат ли регионы НКдп1 и НКдп2 одинаковые множества точек Возвращает через 1ргс ограничивающий прямоугольник региона Комбинирует два региона, Нгдп8гс1 и Нгдп8гс2, в соответствии с операцией ГпСотЫпеМос1е. Результат помещается в ИгдпОез^ Осуществляет смещение региона Нгдп на пХОГГзе!: единиц по оси Хи на пУОГГзе!: единиц по оси У Заполняет структуру 1рКдпОа1:а типа ПСЫ О АТА данными, описывающими регион НКдп Создает регион по структуре ПСЫ О АТА с возможным аффинным преобразованием, заданным структурой ХЕОНМ Укажем на некоторые возможные применения этих функций. Функция РПпКедтоп может оказаться полезной при реализации некоторых эк- зотических разновидностей кнопок или интерактивных областей, изменяющих цвет под курсором мыши. Приложение должно лишь создать объект региона, со- ответствующий интерактивной области, и вызвать функцию РПпКедтоп при обра- ботке сообщения ММ_М0115ЕМ0\/Е для изменения изображения.
Функция ОГГ$е1:Кдп может применяться для отсечения при многократном ри- совании движущихся объектов. Кроме того, при помощи этой функции можно отслеживать положение движущегося объекта в игре, чтобы фиксировать столк- новения с другими объектами, описываемыми своими регионами. Функция СотЫпеКдп позволяет выполнять с объектами регионов некоторые по- лезные операции, заимствованные из теории множеств. Она комбинирует два ис- ходных региона и строит третий, на который ссылается параметр Нгдп Оез!. Все три дескриптора регионов должны быть действительными еще до вызова функции, од- нако регион, представленный дескриптором НгдпОез!, заменяется новым, сформи- рованным в результате вызова функции. Параметр ГпСотЫпеМос1е определяет опе- рацию, выполняемую с исходными регионами, в соответствии с табл. 2.16. Таблица 2.16. Операции над регионами, выполняемые функцией СотЫпеКдп Значение ГпСотЫпеМос1е Новый регион КСМ.АМО КСМ.ОК КОМ-ХОК Область пересечения двух исходных регионов Объединение двух исходных регионов Объединение двух исходных регионов, за исключением области пересечения КСМ.О1ЕЕ КОМ-СОРУ Часть региона Нгдп8гс1, не входящая в регион Нгдп8гс2 Регион НгдпЗгс! Стоит отметить, что на месте параметров Нгдп5гс1 и НгдпОез! может быть один и тот же объект, например Нгдп5гс1. Функция СотЫпеКдп возвращает целочисленный код сложности сгенериро- ванного региона или код ошибки. Возможные значения кода возврата приведены в табл. 2.17. Таблица 2.17. Коды, возвращаемые функцией СотЫпеКдп Значение Описание М11ИКЕСЮМ Пустой регион 51МР1ЕКЕСЮМ Регион определяется одним прямоугольником СОМРЬЕХКЕСЮМ Регион определяется несколькими прямоугольниками ЕККОК Регион не создан в результате ошибки (недопустимые значения параметров или нехватка памяти) Прорисовка регионов Для прорисовки регионов, заданных дескриптором Нгдп, предусмотрены функ- ции, перечисленные в табл. 2.18. Во всех этих функциях координаты регионов задаются в логической системе координат. Создание объектов регионов и операции с ними связаны со значительными зат- ратами времени и памяти, особенно при усложнении формы региона. Поэтому, если рисуемую фигуру легко воспроизвести другими средствами СВ1 (функциями ри- сования прямоугольников, эллипсов и им подобных), следует отдавать предпочте- ние этому способу. Функции прорисовки регионов следует использовать для заме- ны более дорогостоящих операций, например функций вывода отдельных пикселов. Также они могут быть полезны, когда приложение рисует на экране два перекрыва-
ющихся круга и нужно закрасить общую область некоторой кистью. В этом случае определение двух дуг, ограничивающих общую область, может оказаться не очень простой задачей. В то же время пересечение двух круговых регионов элементарно определяется функцией СотЫпеКдп. Таблица 2.18. Функции рисования регионов Функция Описание ВООЬ ННкдп(НОС Нс1с, ИКСЫ Нгдп, НВН118Н НЬг); ВООЬ Ра1пЩдп(НОС Нс1с, ИКСЫ Нгдп); Закрашивает регион кистью НЬг Закрашивает регион кистью, выбранной в контекст устройства ВООЬ Ргатекдп(НОС Нс1с, ИКСЫ Нгдп, НВН115Н НЬг, 1П1 п\Л/|<Щ1э, 1п1 пНе1д1т1:); Обводит контур региона кистью НЬг. Ширина вертикальных «мазков» кисти равна п\Л/1с1Н1, высота горизонтальных «мазков» кисти равна пНе1дЫ: ВООЬ 1пуегЩдп(НОС Нс1с, ИКСЫ Нгдп); Инвертирует цвет каждого пиксела внутри региона (работает аналогично функции ТпуегЩесЦ Отсечение Отсечение — это ограничение вывода рисуемого изображения в пределах некото- рой заданной области. Напомним, что АУтскпуз использует соответствующие по- нятия, связанные с данной проблемой: □ Обновляемый регион (недействительный регион) — та часть окна, которая требу- ет обновления после тех или иных событий. Обновляемый регион формируется как системой, так и приложением в результате вызова функции 1пуа11’с1а1:еКес1: или 1пуа11’с1а1:еКдп. □ Видимый регион — та часть окна, которую в данный момент видит пользова- тель. В результате перекрытия другим окном видимая часть окна может изме- ниться. □ Регион отсечения — область, внутри которой система разрешает рисование. Ког- да приложение получает контекст устройства, система устанавливает регион отсечения как результат пересечения видимого региона и обновляемого реги- она. Приложение может усилить ограничения, накладываемые на регион отсе- чения, при помощи функции 5е1\Л/1’пс1о\л/Кдп или 5е1ес1СИрКдп. Обратите внимание на то, что при отсечении координаты всех регионов зада- ются в системе координат устройства. Отсечение для всего окна Функция 5еШ1 пс1о\л/Кдп предназначена для установки региона окна. Регион окна определяет область внутри окна, где АУтскпуз разрешает рисование. Функция имеет следующий прототип: Ш 8еШ пс1омКдп ( НШО Шпс1, // дескриптор окна НК6И ИКдп, // дескриптор региона В001 ЬКебгам // флаг перерисовки окна после установки региона
Координаты региона определяются относительно левого верхнего угла окна, а не его клиентской области. Функция может использоваться для изменения формы главного окна прило- жения. Отсечение для клиентской области окна Регион с дескриптором НКдп, созданный при помощи рассмотренных выше функ- ций, может быть использован для отсечения при выводе в клиентской области окна. Для этого он должен быть выбран в контекст устройства при помощи функ- ции 5е1ес1С11’рКдп: 8е1есЮ 1рКдп(ИОС, ИКдп); В этом случае СВ1 создает копию данных региона и связывает ее с контекстом устройства. Поэтому после выполнения функции 5е1ес1СИрКдп объект НКдп может быть удален. Чтобы удалить из контекста устройства выбранный ранее регион отсечения, нужно вызвать функцию 5е1ес1С11’рКдп с параметром МОП: 8е1есЮ 1рКдп(ИОС, М1Л_1_); Чаще всего регионы отсечения применяются для того, чтобы ограничить ри- сование только той областью экрана, где это действительно необходимо. Это осо- бенно актуально для анимационных или игровых приложений, в которых ско- рость рисования может иметь критическое значение, а также для программных имитаторов аппаратуры, требующих рисования в реальном времени (см. главу 12). Примеры использования регионов отсечения содержатся в листинге 2.2 и лис- тингах главы 12. Отображение текста В главе 1 уже рассматривалась задача вывода строки текста в центре клиентской области окна (см. листинг 1.1). При этом текст выводился функцией ОгамТех!. Ка- жущаяся простота, с которой был достигнут нужный результат, объясняется тем, что в программе использовались значения по умолчанию для тех атрибутов кон- текста устройства, которые влияют на рисование текста (табл. 2.19). Таблица 2.19. Атрибуты контекста устройства, влияющие на вывод текста Атрибут Значение по умолчанию Функции для изменения значения атрибута Шрифт 5У5ТЕМ_ЕО1\1Т 5е1есЮЬ]ес1 Цвет текста Черный 5е1Тех1Со1ог Цвет фона графических элементов Белый 5е1ВкСо1ог Режим смешивания фона ОРАС^Е БеШкМобе Режим отображения ММ_ТЕХТ 5е1МарМос1е При необходимости можно изменить указанные значения, вызвав соответству- ющие функции СВ1. Например, можно окрасить выводимый текст любым цве- том, вызвав предварительно функцию 5еНех1:Со1ог. Или, если системный шрифт
$У$ТЕМ_ЕОМТ вас не устраивает, вы можете создать свой логический шрифт и выб- рать его в контекст устройства для последующего использования. Прежде чем рассматривать эти возможности, остановимся на вопросе кодировки символов, так как этот атрибут также влияет на создаваемый шрифт. Наборы символов и кодировки Представление символов в компьютерах базируется на кодовых таблицах, в кото- рых каждому отображаемому на экране символу соответствует некоторый цело- численный код. Кодовую таблицу называют также набором символов (скага&ег зек). Одним из первых стандартов кодовых таблиц был стандарт представления символов А8СП1. В наборе символов А8СП (рис. 2.22) каждому символу со- поставлен 7-битный двоичный код, поэтому общее количество символов равно 27 = 128 символов. □ 12345673дДВСОЕЕ □ : □□□□□□□□□□□□□□□□ 1: □□□□□□□□□□□□□□□□ 2: ! " # $ 96 & ' ()*+,-./ 3: 0123456789: ; < = >? 4: @ДВСОЕРСН13КЬМЫО 5: РСгВ8Т1_1Уи/ХУ7[ \ ] Л_ 6: а Ь с с! е Г д К । ] к I тпо 7: рдг51иУ1чхуг<|>‘-иП Рис. 2.22. Стандарт представления символов А8СП Для обеспечения компактности кодовая таблица изображена на рис. 2.22 в дву- мерном представлении. Номера строк и столбцов даны в шестнадцатеричной нота- ции. Шестнадцатеричный код символа образуется в результате сцепления номера строки и номера столбца. Например, код буквы М равен 0х4Е, что соответствует двоичному коду 01001110 или десятичному коду 78. Первые 32 символа в таблице (коды 0x00 — 0x1 Е) соответствуют неотобража- емым управляющим кодам. К ним относятся «возврат каретки» (код ОхОН)2, «пе- ревод строки» (код ОхОА) и другие неотображаемые символы3. Затем следуют пробел (код 0x20), синтаксические и служебные знаки, знаки математических опе- раций, цифры и буквы английского алфавита в верхнем и нижнем регистрах. Пос- ледний символ (код 0х7Е) используется также как управляющий код. Позже появились различные расширения таблицы А8СП с использованием 8-битной кодировки, позволяющей отображать 256 символов. Во всех расшире- ниях первые 128 позиций повторяют стандарт А8СП. Наиболее распространенной реализацией такой кодировки является расши- ренный набор символов 1ВМ, предложенный производителями 1ВМ РС в начале 80-х годов. В старшей половине кодовой таблицы эта кодировка содержит псев- дографические символы, символы греческого алфавита и некоторые математи- ческие символы. Набор символов 1ВМ растиражирован в миллионах микросхем 1 Атепсап 81апс1агс1 Собе Еог 1п1огта1лоп ПКегсНап^е. 2 Этот код вырабатывается, в частности, при нажатии клавиши ЕгДег. 3 АУшскиуз выводит неотображаемые управляющие символы в виде маленьких квадратов.
ПЗУ, которые установлены в видеоадаптерах, принтерах и микросхемах ВЮ8. Для множества программ, работающих в текстовом режиме и написанных не для М1СГО8ОЙ \Упк1о\\у используется эта кодировка, поскольку в них для вывода ин- формации на экран используются символы псевдографики, имеющие коды от ОхВО до ОхНЕ Известно несколько вариантов кодирования набора символов 1ВМ, которые называются кодовыми страницами (собе ра&ез). Вариант, используемый в США и большинстве европейских стран, называется СР437 (собе ра§е 437). В России получила наибольшее распространение так называемая альтернативная кодировка ГОСТа. Она известна также под именем СР866 и отличается от СР437 тем, что некоторые символы во второй половине таблицы заменены на кириллицу, а псев- дографические символы остались на своих местах. Кодовая страница СР866 по- казана на рис. 2.23. 0123456789АВСВЕГ 0: □□□□□□□□□□□□□□□□ 1: □□□□□□□□□□□□□□□□ 2: ()* + ,- ./ 3: 0123456789:;< = >? 4: @АВСВЕГСН1ЛКЬМИО 5: РрВЗТГУ\УХТ2[\]Л_ 6: ' а Ъ с Л е Г § к 1 ] к 1 т и о 7: рцгз1иутгху1{| } ~ □ 3: АБВГДЕЖЗИЙКЛМНОП 9: РСТУФХЦЧШЩЪЫЬЭЮЯ А: абвгдежзннклмноп В: I I Н =1 1 1 =1 1 Н л л =1 1 С: I- Т I- - + I ₽ ± 5? Г = I ± В: Х Т 1 >= 1= Г + + -1 1.1 I" Е: рстуфхцчшщъыьэюя Е: Ё ё € с I 1 У у 0 -7 № я Рис. 2.23. Кодовая страница СР866 С появлением операционной системы МхсгозоЙ АУшсклуз возникла необходи- мость разработки нового расширения таблицы А8СП, так как текстовый режим М8-НО8, а вместе с ним и символы псевдографики стали уже ненужной архаи- кой. Так появился набор символов АМ511, который, фактически, стал международ- ным стандартом 18О2. Набор символов АХ81 также имеет разные варианты кодировки, реализован- ные в виде кодовых страниц, учитывающих национальные особенности алфави- тов разных стран. Некоторые из этих страниц показаны в табл. 2.20. На рис. 2.24 показано содержимое кодировки АУшсклуз ЬаПп! (кодовая стра- ница 1252), а на рис. 2.25 — содержимое кодировки АУтсклуз СупШс (кодовая стра- ница 1251). Хотя однобайтных кодировок хватает для представления символов большин- ства мировых языков, китайский, корейский и японский языки содержат слишком 1 АИ81 — Атепсап КаЛопа! З^апбагбз ЩзШиЩ. 2 180 — ПЧегпаПопа! Ог^ат/аЛоп 1ог ЗЩпсЬгсП/аЛоп.
много символов, для кодировки которых не хватит одного байта. Поэтому для этих языков используются многобайтные кодировки. Следует отметить, что в ко- довых страницах 932 (японский язык), 949 (корейский язык) и 950 (китайский язык) используются также разные принципы кодировки. Таблица 2.20. Варианты кодирования набора символов А1М51 Кодовая страница Коды 0 .. 127 Коды 128 .. 255 1250 (\Л/1пс1о\л/5 1_айп2) А5СП Восточная Европа 1251 (\Л/1пс1о\л/5 СупШс) А5СП Кириллица 1252 (\Л/1пс1о\л/5 1_абп1) А5СП США и Западная Европа 1253 А5СП Г реция 1254 А5СП Турция □ 1 2 3 4 5 б 0: □□□□□□□ 1: □□□□□□□ 2: ! " # $ 96 & 3: 0123456 4: @ А В С О Е Е 5: Р О В 8 Т □ V 6: ' а Ь с с! е Г 7: р д г 5 1: и V 0: € □ , / .Т 9: .........- А: । ф Е и у | В: о ± г з ' р ц С: ддддаА/е О: В Ы 6 6 б б б Е: а а а а а й аэ Г: 6 п о 6 о б 6 739АВС0ЕЕ □ □□□□□□□□ □ □□□□□□□□ '()*+,-/ 7 8 9: < = > ? С Н I 3 К 1_ М Ы О V/ X У Т [ \ ] Л _ д 11 । ] к I т п о п х у г { | * “ 96(8 < СЕ □ 2 □ — ~ ™ 5 > се □ г V § " © а « ® . 1 О » 1Д 1/2 1 С Ё Ё Ё Ё I I I I хоййООурв С ё ё ё ё I |||' -г0йййиу|эу Рис. 2.24. Кодировка \Л/1пс1о\л/5 1_айп 1 (1252) □ 1 2 3 4 5 б 0: □□□□□□□ 1: □□□□□□□ 2: ! " # $ 9о& 3: 0123456 4: @ А В С О Е Р 5: р о к з т и V б: а Ь с б е Г 7: р д г 5 1 и V 3: Ъ Г г г ....1 9= ............- А: У у 3 и Ё ! В: о ± I । Г р Ч С: ДБВГДЕЖ 0: РСТУФХЦ Е: абвгдеж Е: рстуфхц Рис. 2.25. Кодировка 7В9АВС0ЕЕ □ □□□□□□□□ □ □□□□□□□□ 7 8 9: ; < = > ? С Н I 3 К 1_ М 14 О V/ X V 1 [ \ ] А _ д 11 I ] к I ш п о И X у 2 { I > ~ □ * € 96сГЬ < ЕЬ К Т1 Ц — □ ™ /ь > 1-ь к В р § Ё © е « -1 - ©I ё № е » з 8 5 1 3 ИЙКЛМНОП Ч ШЩЪЫЬ Э Ю Я зийклмноп ч шщъыь э юя \Л/1Пс1о\л/5 СупШс (1251)
Если вы хотите узнать на уровне АУт АР1, какие кодовые страницы установ- лены или поддерживаются в вашей системе, можно воспользоваться функцией Епит5у$1:етСос1еРаде$. На уровне пользователя эту информацию можно получить при помощи приложения Кед1опа1 5е1±1пд$ (Язык и стандарты) панели управления \Упк1о\\ъ. Шп1сос1е Работа с разными кодировками, особенно многобайтными, доставляет програм- мисту немало сложностей, например, в случае преобразования символов из од- ной кодировки в другую. Для решения подобных проблем был предложен стан- дарт 1}п1сос1е. В Консорциум Нтсобе, осуществляющий сопровождение данного стандарта, входят Арр1е, НемйеП-Раскагс!, 1ВМ, МхсгозоЙ, 8пп и другие компа- нии. В этом стандарте каждый символ кодируется двумя байтами, что обеспечи- вает возможность выражения до 65 536 символов. Стандарт Птсобе позволяет представить большую часть символов практически всех языков мира. Символы в стандарте Птсос1е группируются в логические зоны. Например, в первой зоне содержатся символы с кодами от 0x0000 до 0х007Е, и младшие бай- ты этих кодов повторяют таблицу А8СП. Самая большая зона содержит 29 902 иероглифов, используемых в Китае, Японии и Корее. \Упк1о\\ъ поддерживает работу с символами как в традиционной АН81-коди- ровке, так и в кодировке Нтсобе. Почти все функции АУш32 АР1, получающие в качестве аргумента адрес строки, имеют АМ81- и Нтсобе-версии. При соблюдении небольшого количества правил вы можете написать програм- му так, что она будет компилироваться как в А№1-версии, так и в Ншсобе-версии. Если в начале файла имеется директива #с1еГ1пе 1ЛХНС00Е, то компилятор созда- ет Ншсобе-версию, если такой директивы нет — А^8I-версию. Разумеется, в мно- гофайловом проекте эта директива должна одновременно присутствовать или отсутствовать во всех файлах проекта. Вот эти правила: □ Для определения символов и строк вместо типа сНаг используйте тип ТСНАК, вместо типа сНаг* используйте тип ЕРТ5ТК, вместо типа соп$1: сНаг* — тип ЕРСТ5ТК. □ Для определения строковых констант используйте макрос ТЕХТ, то есть вместо нотации "аЬссГ употребляйте нотацию ТЕХТ("аЬссГ). □ В случае использования функций из С-библиотеки, работающих со строками, предусмотрите вызов нужной версии функции. Для этого нужно добавить в начале файла, но после директивы #с1еГ1 пе 11М1С00Е, директивы условной ком- пиляции, подобные следующим инструкциям: Ш1С00Е #с!еТ1пе зргЧпРТ змрнпРТ #с!еТ1 пе ззсапТ змзсапТ #с!еТ1пе з^гсру мсзсру #с!еТ1пе з^гса^ мсзса^ #с!еТ1пе зШеп мс81еп #епсН Т В данной книге все программные примеры даны в АН81-зерсии, чтобы не ус- ложнять программный код.
Шрифты Любой шрифт, с которым имеет дело Ш1пс1()\\у характеризуется несколькими па- раметрами: □ Гарнитура (фурефасе) — это совокупность нескольких начертаний шрифта, объе- диненных стилевыми и другими признаками. Самыми известными гарнитура- ми являются Апа1, Тхшез Не\у Вошап, Соппег □ Размер шрифта — это высота прямоугольника, в котором помещаются все сим- волы шрифта. □ Начертание — это специфические характеристики шрифта. В АУтйомга дос- тупны нормальное (погша!) начертание, курсивное (йаНс), полужирное (ЬоШ), с подчеркиванием символа (ппбегНпе), с перечеркиванием символа (з!пке оп1). Кроме того, эти виды начертаний могут комбинироваться в любом сочетании. □ Фиксированная или переменная ширина символов. Шрифты первой группы на- зывают моноширинными. В них все символы имеют одинаковую ширину. Шриф- ты второй группы называют пропорциональными. В них символ «1» занимает го- раздо меньше места, чем символ «ш». Пропорциональные шрифты более удобны для чтения и поэтому чаще используются в текстовых документах. □ Глиф (&1урк) — это графическая форма отдельного символа при его изображе- нии на бумаге или экране дисплея. Различные гарнитуры шрифтов различа- ются прежде всего глифами символов. На рис. 2.26 показано, как выглядит текст, нарисованный с использованием разных гарнитур. Шрифт Апа1: АВСаЬст123 Шрифт Типе2 ЫеАДА Котап: АВСаЬсплЛ23 Шрифт СошДег Ые™: АВСаЬс1™123 Рис. 2.26. Вид текста при выводе с разными гарнитурами В приведенном примере шрифты Апа1 и Тхшез Вошап являются пропор- циональными, а шрифт Соппег Не\у — моноширинным. Можно также отметить, что глифы шрифтов Тхшез Вошап и Соппег Не\у имеют так называемые за- сечки, а глифы шрифта Апа1 таких засечек не имеют. Шрифты, обладающие общими принципами начертания, принадлежат одному из семейств ^атг1у). Характеристики семейств шрифтов приведены в табл. 2.21. Таблица 2.21. Семейства шрифтов Семейство шрифтов Флаг (константа \Л/|ПС1о\Л/5) Код Описание ОопЧ: саге РР_ОО1\1ТСАкЕ 0x00 Шрифт с произвольными атрибутами (используется шрифт по умолчанию) котап РР.кОМАЫ 0x10 Пропорциональные шрифты с засечками (серифами), например "Птез Ые\л/ котап 8\ЛЛ55 РР_8\Л/188 0x20 Пропорциональные шрифты без засечек, например Апа1 Мобегп РР.МСЮЕкЫ 0x30 Моноширинные шрифты с засечками или без засечек, например Соипег Ые\л/
Семейство шрифтов Флаг (константа \Л/|ПС1о\Л/5) Код Описание 5спр1: ЕЕ_5Ск1РТ 0x40 «Рукописные» шрифты, например Мопо1уре Согз|уа Оесогайуе ЕР_ОЕСОИАТ1УЕ 0x50 Декоративные шрифты, например ОН ЕодИзН Типы шрифтов АУ1пс1о\\-з поддерживает две главные категории шрифтов: «шрифты СВ1» и «шрифты устройства» (р1ес1се фопЬз). Шрифты СВ1 хранятся в файлах, которые обычно расположены в одном из подкаталогов операционной системы. Шрифты устройства соответствуют конкретному устройству вывода. Например, большин- ство принтеров имеет набор встроенных шрифтов устройства. Шрифты СВ1 подразделяются на три типа: □ растровые шрифты; □ векторные шрифты; □ шрифты типа ТгпеТуре. В шрифтах растрового типа символы хранятся в виде растровых картинок — прямоугольных матриц из точек-пикселов. В этих шрифтах каждая точка символа описывается отдельно. Растровые шрифты удобны для вывода текста на экран, осо- бенно при малой высоте букв. Однако для каждого размера шрифта и, возможно, для различных разрешений экрана необходимо хранить свой набор символов. Рас- тровые шрифты плохо поддаются масштабированию, так как при увеличении сим- волов просто дублируются строки или колонки пикселов, что, конечно, приводит к «зазубринам» в очертаниях глифов. Чаще всего растровые шрифты применяются для воспроизведения текстовых элементов интерфейса АУшсклуз. В векторных шрифтах глиф описывается последовательностью линейных от- резков, которые затем рисуются с помощью пера. Векторные шрифты легко мас- штабируются в широких пределах. Однако они имеют более низкую скорость ото- бражения, плохую четкость при маленьких размерах, а при большом увеличении символы выглядят очень бледными, потому что их контуры являются всего лишь тонкими линиями. В настоящее время они применяются в основном только для плоттеров. Векторные шрифты так же, как и растровые шрифты, хранятся в фай- лах с расширением .Гоп. С появлением шрифтов типа ТгпеТуре в версии АУтскпуз 3.1 значительно по- высились возможности и гибкость работы с текстами. ТгпеТуре — это технология контурных шрифтов, которая была разработана Арр1е Сошрп1ег 1пс. и МхсгозоЙ СогрогаПоп. Эта технология поддерживается многими производителями шриф- тов. Отдельные символы шрифтов ТгпеТуре определяются контурами, состоящи- ми из прямых линий и кривых. Таким образом, АУшсЬауз может масштабировать символы, изменяя определяющие контуры координаты. При этом контуры гли- фов остаются плавными и при увеличении размеров символов. Шрифты ТгпеТуре могут быть использованы как для вывода на экран, так и для вывода на принтер, делая возможным режим отображения текста АУ У81АУ VС (АУйа1-Уоп- Зее-Тз-АУйа^-Уоп-Се!)1. 1 Что видите — то и получаете.
Когда программе необходимо использовать шрифт ТгпеТуре определенного размера, АУтскпуз формирует растровое представление этого шрифта. Этот про- цесс называется растеризацией шрифта. Алгоритмы растеризации учитывают не только координаты точек соединения прямых и кривых каждого символа, но и некоторые дополнительные данные, содержащиеся в описании шрифта, чтобы скомпенсировать ошибки округления, которые могли бы привести к искажению формы символа. В результате достигается хорошее качество изображения глифов символов независимо от коэффициента масштабирования. Шрифт ТгпеТуре обычно хранится в одном файле с расширением .Ш. В опера- ционной системе АУшбом^з впоследствии появилась поддержка шрифтов ОрепТуре. Эти шрифты аналогичны шрифтам Роз18спр1, но они закодированы в формате шрифтов ТгпеТуре. Файлы шрифтов ОрепТуре имеют расширение .оН. Технология ОрепТуре также позволяет объединить несколько шрифтов ОрепТуре в один файл. Для таких шрифтов, называемых коллекциями ТгпеТуре, используется расшире- ние .Нс. Установка и удаление шрифтов Чтобы приложение смогло выводить текст, используя глифы некоего конкретно- го шрифта, он должен либо быть установлен в системной таблице шрифтов (зу&ет /огН: кМе), либо присутствовать в используемом графическом устройстве в каче- стве встроенного шрифта. Имена шрифтов, установленных на графическом уст- ройстве и хранящихся во внутренней системной таблице, можно получить при помощи функции ЕпитЕогйЕапйПез или СНоозеЕопЕ Приложение может загрузить шрифт вызовом одной из функций: Ас1с1 ЕогйКезоигсе или Ас1с1 ЕогйКезоигсеЕх. Эти функции загружают шрифт из соответствующего ресурс- ного файла. Однако такая установка является временной, поскольку после рестарта операционной системы шрифт окажется недоступным. Чтобы установленный шрифт присутствовал в системе постоянно, информация о нем должна быть включена в ре- естр \Упк1о\\-з. Если установленный шрифт становится ненужным, то он может быть удален из системной таблицы с помощью функции Кетсл/еЕогйКезоигсе. Приложение, изменяющее системную таблицу шрифтов, должно оповестить об этом все окна верхнего уровня рассылкой сообщения ММ_Е0МТСНАМ6Е. Прило- жения, использующие список установленных шрифтов, должны обрабатывать это сообщение и обновлять содержимое списка шрифтов. Более подробную информацию об установке шрифтов можно найти в книге [6]. Внедрение шрифтов При передаче текстовых документов на другие компьютеры нередко возникают се- рьезные проблемы со шрифтами. Установив на своем компьютере нужные шрифты и подготовив документ с их использованием, вы можете столкнуться с ситуацией, когда на другом компьютере с другим набором установленных шрифтов ваш доку- мент будет выглядеть совершенно иначе. Технология внедрения шрифтов (/опк етЪесШпрд позволяет включить шриф- ты прямо в документ. При открытии документа внедренные шрифты автомати- чески устанавливаются на другом компьютере, благодаря чему документ сохра- няет прежний вид.
Внедрение шрифтов должно соответствовать лицензионным правилам исполь- зования шрифтов. Для определения лицензионного статуса шрифтов ТгпеТуре/ ОрепТуре следует вызвать функцию беЮиРПпеТехРМеРлсз и затем проверить зна- чение поля сйтГзТуре структуры О11ТИМЕТЕХТМЕТК1С. Метрики шрифтов При форматировании текста АУш32 СВ1 использует метрики шрифта, которые поясняются на рис. 2.27. 11тЕх1егпа11_еас1|пд 1тНе1дЫ Рис. 2.27. Метрики шрифта Отсчет всех размеров выполняется от так называемой базовой линии шрифта. На ней находится нижняя граница глифов большинства прописных букв. Все сим- волы шрифта размещаются в прямоугольных ячейках. □ Высота шрифта Рт Н е1 дЫ складывается из надстрочного интервала и подстроч- ного интервала. □ Надстрочный интервал РтАзсепР — это расстояние от базовой линии до верх- ней границы ячейки символа. □ Подстрочный интервал РтОезсепР — это расстояние от базовой линии до ниж- ней границы ячейки символа. □ Внутренний зазор 1гп1п1:егпа11еас11 пд определяет пространство для размещения диакритических знаков, таких как знак акцента или умляут. □ Внешний зазор 1т Ех1егпа11еасПпд определяет минимальный интервал между со- седними строками для многострочного текста. В полиграфии размер шрифта измеряется в пунктах. Один пункт равен 0,01389 дюйма (1/71,99424). В компьютерной верстке пункт округляется до 1/72 дюйма. Еще один типографский термин «кегль» определяется как разность высоты шриф- та и внутреннего зазора. Для получения информации о шрифте, выбранном в контекст устройства, пред- назначена функция БеРТехРМеРп’сз: В001_ 6еРТехРМеРг1сз(Н0С Мс, 1РТЕХТМЕТВ1С 1рРт); Возвращаемая функцией информация размещается в структуре типа ТЕХТМЕТК1С с адресом 1рРт. Структура ТЕХТМЕТК1С имеет следующее определение: ЦуребеТ зРгисР РадТЕХТМЕТЕЮ { ИЖ РтНе1дИР; // высота символов ЕСЖ РтАзсепР; // надстрочный интервал Е0М<3 РтОезсепР; // подстрочный интервал ИЖ Ргп! пРегпа! ЕеасИ'пд; // внутренний зазор продолжение &
ЕСЖ ШЕх^егпаНеасНпд; ЕСЖ Шл/еСИагШс1Ш ЕСЖ 1тМахСЖгШс1Ш ЕСЖ Ш\1е1 дбб; ЕСЖ ШОуегИапд; ЕСЖ 1т01д1112ес1А8рес1Х; ЕСЖ Ш)1 д1 11 хебАзресбХ; ТСНАЕ РшПгзЮИаг; ТСНАЕ РтЬазШаг; ТСНАЕ РтОеГаиЖИаг; ТСНАЕ РтВгеакСИаг; ВУТЕ РшИаПс; ВУТЕ РгШпс1ег11пес1; ВУТЕ бтЗбгискСШ; ВУТЕ РтРПсТАпс1Еат11у; ВУТЕ бггОагЗеб; } ТЕХТМЕТВ1С; // внешний зазор // средняя ширина символов // максимальная ширина символов // вес (насыщенность) шрифта // дополнительный промежуток для синтеза //некоторых шрифтов // горизонтальный аспект устройства // вертикальный аспект устройства // первый символ, для которого в шрифте имеется //глиф // последний символ, для которого имеется глиф // символ для замены символов, не имеющих глифа // символ для вставки в промежутки слов при //выравнивании текста // признак курсива // признак подчеркивания // признак перечеркивания // шаг, технология и семейство шрифта // набор символов Первые семь полей структуры определяют основные метрики шрифта (см. рис. 2.27) в логических единицах, которые зависят от режима отображения, выбранного в контексте устройства. Поле ±т\Л/е1дН± определяет вес (жирность) шрифта. Эта величина может изме- няться от 0 до 1000, но чаще всего она равна или 400 для нормального начертания шрифта, или 700 для полужирного начертания. В поле 1т0уегНапд содержится величина, на которую увеличивается ширина символов для синтезированных шрифтов, например для наклонных или жирных шрифтов, получаемых из нормального шрифта. Шрифты ТгпеТуре обычно не ис- пользуют это поле, так как в них для каждого начертания создается свой шрифт. Поля 1тО1’д1’112ес1А$рес1Х и 1тО1’д1’112ес1А$рес17 прокомментированы в М8^^ тер- минами горизонтальный аспект устройства (копгопий азрес1; о/Ле НеЫсе) и вер- тикальный аспект устройства (регйса1 азрес1; о/ Ле <1еЫсе) соответственно, для которых был создан текущий шрифт. По существу, они содержат те же значения, которые возвращает функция СеЮеукеСарз с аргументами ЮСР1ХЕЕ5Х и ЮбР1ХЕ157, то есть «число пикселов на 1 логический дюйм по горизонтали» и «число пиксе- лов на 1 логический дюйм по вертикали». При синтезе шрифтов имеют значение не абсолютные значения этих величин, а их соотношение 1т О1’д1’112ес1А$рес17 / 1т О1’д1’!12ес1А$рес!Х, называемое аспектным соотношением (азресЪ га1ю). Поле 1т0еГаи11СНаг имеет довольно простой смысл. Если приложение пытает- ся вывести неотображаемый символ, код которого отсутствует в шрифте, то вме- сто него будет выведен символ с кодом, содержащимся в поле 1т0еГаи11СНаг. На рис. 2.22-2.25 показано, что АУшбомгз выводит в таких случаях контурный пря- моугольник. Поле 1тВгеакСНаг обычно содержит код пробела (0x20). В поле 1тР1’1сНАпс1Еат1’1у содержится информация о шаге, технологии и семей- стве шрифта. Четыре младших бита указывают на шаг и технологию. Соответ- ствующие константы приведены в табл. 2.22. Четыре старших бита обозначают семейство шрифта. Возможные значения в виде констант АУшсклуз были приведены в табл. 2.21.
Таблица 2.22. Шаг и технология шрифта Константа Код Описание ТМРЕ_Е1ХЕО_Р1ТСН 0x01 Если этот бит установлен, шрифт является пропорциональным, в противном случае — моноширинным. Обратите внимание на то, что трактовка значения бита противоположна имени константы ТМР1=_7ЕСТ0к 0x02 Если этот бит установлен, шрифт — векторный ТМРЕ_ТкОЕ7УРЕ 0x04 Если этот бит установлен, используется шрифт Тгие Туре ТМРЕ_ОЕУ1СЕ 0x08 Если этот бит установлен, используется шрифт устройства В поле 1тСНаг5е1: находится код используемого набора символов. Возможным значениям кода сопоставлены константы, определенные в файле \ллпдс11.Н. Неко- торые из них показаны в табл. 2.23. Таблица 2.23. Коды наборов символов Константа Код Примечание АЫ81_СНАК8ЕТ 0 ОЕРА111_Т_СНАк8ЕТ 1 8УМВО1__СНАК8ЕТ 2 5Н1РТЛ5_СНАК5ЕТ 128 НАМ6111__СНАк8ЕТ 129 6В2312_СНАК8ЕТ 134 СН1ЫЕ8ЕВ165_СНАК8ЕТ 136 6КЕЕК_СНАК8ЕТ 161 Только для \Л/1Пс1о\л/з ЫТ/2000 Т1ЖК18Н_СНАК8ЕТ 162 Только для \Л/1Пс1о\л/з ЫТ/2000 НЕВРЕ\Л/_СНАР5ЕТ 177 Только для \Л/1Пс1о\л/з ЫТ/2000 АИАВ1С_СНАК5ЕТ 178 Только для \Л/1Пс1о\л/5 ЫТ/2000 К11881АЫ_СНАк8ЕТ 204 Только для \Л/1Пс1о\л/5 ЫТ/2000 ЕА8ТЕ1ЖОРЕ_СНАК8ЕТ 238 Только для \Л/1Пс1о\л/з ЫТ/2000 ОЕМ_СНАК8ЕТ 255 Чтобы использовать функцию беНех^МеЫсз, сначала нужно определить струк- турную переменную типа ТЕХТМЕТК1С, например: ТЕХТМЕТЕ1С Ш ; Затем необходимо получить дескриптор контекста устройства и вызвать функ- цию беПехВЧе^лсз: ПОС = 6еШ(Шпс1) ; 6еГГех^Ме^г1с8(Н0С, &Ш ; После этого метрики текущего шрифта будут находиться в соответствующих полях переменной 1т. В завершение не забудьте освободить контекст устройства: 0е1еазе0С(Шг1с1, НОС) ; Приведем пример. Допустим, что текущее разрешение экрана составляет 96 фл1, а в приложении используются: а) шрифт с гарнитурой «Апа1», нормальным начер- танием и высотой 18 логических единиц; б) режим отображения ММ_ТЕХТ, в кото- ром логические единицы совпадают с физическими единицами, то есть пикселами. 1 с1р1 — (1оЕ рег шсЬ (число точек на дюйм).
Тогда после вызова функции беНех^МеЫсз поля переменной 1т будут заполнены следующими значениями: {18, 15, 3, 2, 1, 7, 43, 400, 0, 96, 96, 30, 255, 31, 32, 0, 0, 0, 39, 204} Обратите внимание на то, что интерпретация поля 1тРИсНАпдЕагтпТу связана с его побитовой кодировкой, поэтому десятичное значение 39 стоит преобразо- вать к шестнадцатеричному коду 0x27. Логические шрифты К счастью, при выводе текста приложениям не приходится напрямую общаться с физическими шрифтами. С физическими шрифтами работают шрифтовые драй- веры, находящиеся в системе на одном уровне с драйверами графических уст- ройств. В программах же используются так называемые логические шрифты. Логический шрифт представляет собой объект СВ1, описывающий требования к шрифту со стороны приложения. СГ)1 анализирует запрошенные параметры и под- бирает наиболее подходящий шрифт из тех, которые зарегистрированы в системе. Объект логического шрифта находится под управлением СВ1 вместе с други- ми логическими объектами. Приложения работают с логическими шрифтами толь- ко через их дескрипторы, имеющие тип НГО МТ. Стандартные шрифты По умолчанию в контексте устройства выбран системный шрифт 5У5ТЕМ_ЕОМТ, основным и почти единственным преимуществом которого является то, что он всегда доступен для использования. Системный шрифт является растровым, со- держит буквы переменной ширины, не имеющие засечек, и для него используется кодировка АМ81. Однако в некоторых случаях может понадобиться шрифт с фиксированной ши- риной букв или шрифт в кодировке ОЕМ1. Вы можете получить дескриптор од- ного из стандартных (встроенных) шрифтов при помощи функции 6е151оскОЬ]’ес1, передав в качестве аргумента одно из значений, указанных в табл. 2.24. Таблица 2.24. Идентификаторы встроенных шрифтов Идентификатор Код Описание ОЕМ_Р1ХЕО_РО1\1Т 10 Шрифт в кодировке ОЕМ с фиксированной шириной букв АЫ81_Р1ХЕО_РО1\1Т 11 Шрифт в кодировке АЫ81 с фиксированной шириной букв АЫ81_7Ак_РО1\1Т 12 Шрифт в кодировке АЫ81 с переменной шириной букв 8У8ТЕМ_РО1\1Т 13 Системный шрифт в кодировке АЫ81 с переменной шириной букв, используется для отображения текста в меню, в заго- ловках окон, в элементах управления диалоговых окон, а также в клиентской области окна ОЕ\/1СЕ_ОЕРА111_Т_РОМТ 14 Шрифт, который используется для данного устройства по умолчанию 8У8ТЕМ_Р1ХЕО_РО1\1Т 16 Шрифт в кодировке АЫ81 с фиксированной шириной букв. Использовался как системный шрифт в старых версиях (до \А/1Пс1о\л/з 3.0) ОЕРА1ЛТ_С1Л_РОМТ 17 Шрифт по умолчанию для объектов пользовательского интерфейса, таких как меню и диалоговые окна (только для \Л/1пбо\л/5 ЫТ/2000) 1 ОЕМ — Оп§ша1 Едшртеп! Мапи€ас1игегз
Получив дескриптор встроенного шрифта, необходимо выбрать его в контекст устройства при помощи функции 5е1ес1ОЬ]ес1. После окончания работы с встро- енным шрифтом его удаление не требуется. Создание логических шрифтов Область применения стандартных шрифтов обычно ограничивается простым выводом элементов пользовательского интерфейса. Для любых других целей при- ходится создавать собственные логические шрифты. В СВ1 для этого предусмот- рены функции Сгеа^еЕоп!, Сгеа1еЕоп11пс11’гес1 и Сгеа1еЕопНпсНгес^Ех. В этом разделе не будет рассматриваться функция Сгеа^еЕопИпсНгес^Ех, кото- рая поддерживает использование шрифтов нового формата ОрепТуре фирмы МхсгозоЙ. При необходимости найти нужную информацию можно в М8ВК Функция Сгеа1еЕоп! использует для описания логического шрифта 14 парамет- ров, поэтому она не слишком удобна в использовании. Вместо нее лучше пользо- ваться функцией Сгеа1еЕоп11пс11’гес1. Эта функция получает указатель на структу- ру Ю6Е0МТ, в которой упакованы эти же 14 параметров. Функция имеет следующий прототип: НГО1Х1Т СгеаЦеКопШпбп гесЬ(СОИЗТ Ю6Е0МТ* 1р1Т); Структура Ю6Е0МТ определена в файле мпдсН.Н следующим образом: 1урес1еТ 81гис1 1адЕ06Е0МТ { 1п1 ПТНеадЕгЬ: 1п1 1 Т1л/1 сИП; 1п1 1ЕЕзсаретегЛ; 1п1 1 ЕОгчегЛаИоп; 1п1 1 Т1л/е1 дИгЬ: ВУТЕ 1 ТИаИс; ВУТЕ 1 Щпс1ег11 пе; ВУТЕ 1Т8Тг1кеОиТ; ВУТЕ Юаг8е1; ВУТЕ 1ТОиТРгеса81оп: ВУТЕ 1ТС11рРгеса81оп; ВУТЕ 1ЩиаШу; ВУТЕ 1ТР1 ТсНАпс1Еат11у; сНаг 1ТЕасеМатеЕЕЕ_ЕАСЕ812Е]; } Ю6Е0МТ; Рассмотрим назначение ее полей: □ [ЖетдЫ: — желательная высота шрифта в логических единицах. Поле может иметь положительное, нулевое или отрицательное значение. Положительное значение определяет требуемую высоту ячеек, то есть величину 1т Е1 е1 дН1 на рис. 2.27. Если указано нулевое значение, то выбирается шрифт размером 12 пунктов (значе- ние по умолчанию). Абсолютная величина отрицательного значения определя- ет высоту символов, то есть расстояние 1тЕ1е1’дН1 - 1т1п1егпа11_еас11’пд. □ Шс11Н — желательная средняя ширина символов в логических единицах. Если поле 1Е\Л/1 с11Н имеет нулевое значение, то аспектное соотношение устройства вы- вода сопоставляется с аспектным соотношением доступных шрифтов в поиске наиболее подходящего шрифта по этому параметру. □ 1ГЕ$саретеп1 — угол между базовой линией текста и осью X устройства в деся- тых долях градуса. Отсчет ведется против часовой стрелки.
□ 1Г0п еп^айоп — угол между базовой линией каждого символа и осью X устрой- ства в десятых долях градуса. Отсчет ведется против часовой стрелки. Если устройство работает в расширенном графическом режиме (6М_А0\/АМСЕ0), до- ступном лишь в Ш1пс1о\\^ ?<Т/2000, то наклон 1ГЕ$саретеп1: и ориентация [ГОлегйаТп’оп задаются независимо друг от друга. В совместимом графическом режиме (6М_С0МРАТ1В1_Е) поле ГГЕзсаретепЕ задает одновременно и наклон, и ориентацию символов. М8^^ рекомендует в последнем случае устанавли- вать одно и то же значение для полей ГГЕзсаретепЕ и [ГОлегйаВоп. □ 1ГМе1’дН1: — желательный вес (жирность) шрифта в интервале от 0 до 1000. На- пример, значение 400 создает нормальный шрифт, а 700 — полужирный шрифт. В табл. 2.25 приведены константы, которые можно использовать для установ- ки данного поля. Значение ЕМ_ООМТСАКЕ предписывает выбрать вес шрифта по умолчанию. Таблица 2.25. Константы для определения веса шрифта1 Константа Значение НЛ/_ОО1\1ТСАкЕ Р\Л/_ТН1Ы Р\Л/_ЕХТНА1_ЮНТ ЕУ\/_111_ТКА1_1СНТ Р\Л/_ЫСНТ Р\Л/_1\ЮкМА1_ Р\Л/_кЕ6111_Ак Р\Л/_МЕОШМ Е\Л/_5ЕМ1ВОЮ Е\Л/_ОЕМ1ВОЮ Е\Л/_ВОЮ Е\Л/_ЕХТКАВОЮ Р\Л/_иЕГКАВОЮ Е\Л/_НЕА\/У НЛ/_В1_АСК 0 100 200 200 300 400 400 500 600 600 700 800 800 900 900 □ [ГИаПс — запрашивается курсивный шрифт, если значение поля равно ТРОЕ. □ 1Г11 пдегИпе — запрашивается шрифт с подчеркиванием букв. □ 1Г51Л кеОи! — запрашивается шрифт с перечеркнутыми буквами. □ 1ГСГ|аг5е1 — набор символов шрифта. Вы можете использовать одну из констант, приведенных в табл. 2.23. □ 1Юи1Ргес181’оп — требуемая степень соответствия параметров выбираемого шрифта запрашиваемым характеристикам. Это поле указывает СВ1 критерии выбора между имеющимися в системе шрифтами. Наиболее часто используются значе- ния из табл. 2.26. 1 Можно использовать любое из указанных значений, однако следует иметь в виду, что многие шрифты содержат описания символов только для веса ЕУУ_М0КМА1_, Е\А/_КЕ(31Л_АК и ЕУУ_В01_0.
Таблица 2.26. Константы для поля 1ГОи1Ргес18ЮП Константа Код Описание О1П"_ОЕРА111_Т_РкЕС18 0 О11ТТТ_РкЕС18 4 ОС1Т_ОЕУ1СЕ_РКЕС18 5 ОС1Т_ИА8ТЕК_РКЕС18 6 О11Т_ТТ_О1\11_У_РКЕС18 7 ОС1Т_ОС1Т1_1ЫЕ_РкЕС18 8 Стандартный способ подбора шрифтов Отдается предпочтение шрифтам Тгие Туре Отдается предпочтение шрифтам устройства вывода Отдается предпочтение растровым шрифтам Используются только шрифты Тгие Туре Отдается предпочтение шрифтам Тгие Туре и другим контурным шрифтам □ 1ТС11 рРгес151ОП — способ отсечения символа, частично попавшего за пределы ре- гиона отсечения. Поле может иметь одно или несколько значений из табл. 2.27, объединенных побитовой операцией ИЛИ. Из всех флагов к отсечению, соб- ственно, относится только константа СИР_0ЕЕА1Л_Т_РКЕС15. Таблица 2.27. Константы для поля 1ГС1|рРгес15ЮП Константа Код Описание С1_1Р_ОЕЕА1ЛТ_РКЕС18 0 С1_1Р_1_Н_А1\1С1_Е8 0x10 Стандартная процедура отсечения Если этот флаг установлен, то направление вращения для всех шрифтов зависит от ориентации координатной системы (левосторонняя или правосторонняя). Если флаг не установлен, то шрифты устройств вращаются всегда против часовой стрелки, но вращение других шрифтов зависит от ориентации координатной системы С1_1Р_ЕМВЕООЕО 0x80 Запрашивается использование внедренных шрифтов, доступных только для чтения □ 1ТОиа11±у — качество вывода глифов. Можно использовать одну из констант, приведенных в табл. 2.28. Для шрифтов ТгпеТуре константы ОКАЕТ_СЦ1А1_1ТУ и РКООЕ_СЦ1А1_1ТУ не имеют значения, поскольку контуры глифов свободно мас- штабируются до нужной величины. Таблица 2.28. Константы для поля 1^иаК1у Константа Код Описание ОЕРАиьт_(2иА1_пу о ОКАРТ_(211А1_ПУ 1 Внешний вид символов несущественен Внешний вид символов менее важен по сравнению с другими атрибутами шрифта. Для растровых шрифтов это позволяет СО1 синтезировать разные размеры шрифта, но с потерей качества РКООР_(211А1_1ТУ 2 Внешний вид символов более важен по сравнению с другими атрибутами шрифта, поэтому масштабирование растровых шрифтов запрещено ЫОЫАЫТ1А1_1А8ЕО_(211А1_1ТУ 3 АЫТ1А1_1А8ЕО_(211А1_1ТУ 4 Сглаживание (апУа1|а51пд) запрещено Выполнять сглаживание (апУа1|а51пд), если оно поддержи- вается шрифтом и если размер шрифта не слишком велик и не слишком мал □ [ГРйсНАпс1 Еатйу — задает шаг и семейство шрифта. Возможные значения опреде- ляются как объединение (логическая операция ИЛИ) одного из значений, при- веденных в табл. 2.29, и одного из значений, приведенных в табл. 2.21.
Таблица 2.29. Константы, указывающие шаг шрифта Константа Код Описание ОЕ1=А1ЛТ_Р1ТСН 0x0 Шаг по умолчанию Е1ХЕО_Р1ТСН 0x1 Моноширинный шрифт УАК1АВ1_Е_Р1ТСН 0x2 Пропорциональный шрифт □ ГЕасеМате — строка с завершающим нулевым байтом, задающая имя гарниту- ры шрифта. Размер строки, включая завершающий нуль, не должен превы- шать 32 байта. Имена гарнитур шрифтов, установленных в системе в настоя- щий момент, можно получить при помощи функции ЕпитЕогйЕапйПезЕх. Если поле ГЕасеМате содержит пустую строку, то СВ1 выбирает первый попавшийся шрифт, который удовлетворяет другим заданным атрибутам. Перед вызовом функции СгеаЕе ЕогтЫпсИгее! вы должны определить переменную типа Е1ЕОМТ для создаваемого шрифта: НЕОМТ ИЕоггЬ; а также переменную типа ЮбЕОМТ: Ш6Е0МТ 1Г; После этого заполните ее поля нужными значениями, определяющими пара- метры шрифта. Неиспользованные поля следует установить в 0. Более эффектив- ным вариантом является объявление переменной 1Г со спецификатором 81аБс: 5Ш1с Ю6Е0МТ 1Г; так как в этом случае компилятор обеспечит инициализацию всех полей структу- ры 1Г нулевыми значениями. Если вы проектируете приложение, в котором пользователь должен иметь воз- можность выбора используемого шрифта, то для заполнения полей структурной переменной 1Г следует воспользоваться функцией СНоозеЕоп!:. Она вызывает стан- дартное диалоговое окно для выбора шрифта. Эта возможность будет рассмотре- на в главе 7 (раздел «Диалоговые окна общего пользования»). После того как переменная 1Г подготовлена, вызовите функцию Сгеа^еЕопЫпсНгес!:: НЕогтЬ = Сгеа'ЬеЕо1тЫпс11гес'Ь(&1Г); Полученный дескриптор Н ЕопЕ созданного логического шрифта выберите в кон- текст устройства: НЕОИТ И01с1ЕопЦ = (НЕ0МТ)8е1есШэаесШ0С, ИЕопЩ; Далее вы можете работать с этим шрифтом в функциях вывода текста, а после того, как он перестанет быть нужным, верните в контекст устройства предшеству- ющий шрифт с одновременным удалением шрифта Н ЕопЕ: 0е1еШЬаесШе1есШэаесШ0С, Н01 бЕопЦ)); Пример работы с логическими шрифтами приведен в листинге 2.3. Подстановка шрифта Новый логический шрифт, созданный функцией СгеаЕеЕоп!: или СгеаЕеЕопНпсНгес!:, не ассоциируется ни с каким физическим шрифтом, пока он не будет выбран в контекст устройства вызовом функции 5е1есЮЬ]есЕ Процесс поиска и выбора нуж- ного физического шрифта, реализуемый системой в ходе выполнения функции 8е1ес1:0Ь]'ес1:, называется подстановкой или отображением шрифта (фоп1: таррт§).
Процесс отображения логического шрифта довольно сложен. СВ1 сравнивает параметры, заданные в структуре ЮБЕОМТ, с параметрами различных шрифтов, доступных для графического устройства, выбирая наиболее подходящий шрифт. Для сравнения используются штрафные очки, которые имеют разные весовые коэффициенты. Выбирается тот шрифт, для которого штрафная сумма будет наи- меньшей. Наиболее важным фактором при подборе физического шрифта является набор символов, который задается в поле 1ГСНаг5е1. При несовпадении этого атрибута очень велика вероятность, что символы будут выводиться совершенно неверными гли- фами. Следующее по важности поле — это 1ГОи1:Ргес1$1оп. Этот показатель ограничи- вает рассматриваемые наборы символов определенными типами шрифтов. Затем оценивается поле 1ГЕасеМате, а после него — поле [ГРИсНАпс1Еагт 1у. Моноширинные шрифты сильно отличаются по внешнему виду от пропорциональных, поэтому этот атрибут также является важным при подстановке. После сравнения указанных по- лей СВ1 сравнивает высоту символов, заданную в поле 1ГН е1д НЕ а затем поля 1ЛЛ/1’с11:Н, [ШаИс, 1Г11пс1ег11’пе, 1Г$1:пкеОи1:. Получение информации о выбранном физическом шрифте После того как логический шрифт выбран в контекст устройства, приложение может получить дополнительную информацию о подобранном физическом шриф- те и его метриках. Функция БеНех^МеЫсз, позволяющая выполнить эту задачу, уже рассматрива- лась ранее. Но можно использовать и другую функцию, прототип которой приведен ниже: Ш 6еГГехШсе(Н0С НОС, Ш пСоигД, СРТЗТО 1рЕасеМате); Эта функция получает имя гарнитуры физического шрифта. Оно может отли- чаться от имени гарнитуры, которое было задано в структуре ЮБЕОМТ. Искомое имя в виде строки с завершающим нулевым символом записывается в символь- ный буфер с адресом фЕасеМате. Длина буфера (количество символов) определя- ется параметром пСоипЕ В случае успешного завершения функция возвращает количество символов, скопированных в буфер, включая и завершающий нулевой символ. В случае ошибки возвращается нулевое значение. Функцию БеНех1:Еасе можно вызвать, указав на месте третьего параметра значение МОП. В этом случае она просто вернет количество символов в имени гарнитуры. Функция БеНех1:СНаг$е1:(НОС НОС) возвращает идентификатор набора символов для текущего шрифта, выбранного в контекст устройства. Есть и другие функции для получения дополнительной информации о теку- щей реализации физического шрифта, к которым относятся Бе1:Еоп1:1_апдиаде1пГо, БеПехКНагЗеПпГо и БеЮиФ’пеТехВЧеФ’сз. Они используются при нетривиальном форматировании текста, например при непосредственной работе с глифами. Опи- сание этих функций вы можете найти в М8^^ или в книге [6]. Вывод текста АУш32 СВ1 обеспечивает полный набор функций для форматирования и рисова- ния текста в клиентской области окна или на бумажной странице принтера. Эти функции могут быть разделены на те, которые форматируют текст, подготавли- вая его для вывода, и те, которые действительно отображают текст. Форматиру-
ющие функции выравнивают текст, устанавливают межсимвольные промежутки, изменяют протяженность разделительных символов, устанавливают цвет текста и цвет фона графических элементов. Рисующие функции выводят отдельные сим- волы или целые строки текста. Простой вывод текста Простейшая функция вывода текстовой строки ТехЮи!: имеет следующий про- тотип: В001_ Тех^СШ ( НОС Ыс, Ш пХЗ^агТ, Ш пХЗ^агТ, ОРСТЗТВ 1р8^г1пд, Ш сЬ8^г1пд ); // дескриптор контекста устройства // х-координата стартовой позиции // у-координата стартовой позиции // указатель на символьную строку // число символов в строке Функция обеспечивает вывод строки с адресом 1р51п'пд, размещая текст в за- данной позиции с учетом текущего режима выравнивания. При выводе использу- ются текущие значения атрибутов контекста устройства — шрифт, цвет текста и цвет фона графических элементов, режим смешивания фона и многие другие. Функция не распознает конец строки 1р51п'пд по завершающему нулевому симво- лу, поэтому количество выводимых символов задается параметром сЬ51лпд. Сим- волы строки должны входить в набор символов текущего шрифта. Позициониро- вание текста зависит от текущего режима выравнивания. Текущий режим выравнивания текста реализован как атрибут контекста уст- ройства, определяющий правила позиционирования текста. Он указывает, что считать опорной точкой (гефегепсе ротйф. точку (пХ51аг1:, пУ51:аг1:) или текущую по- зицию пера в контексте устройства. Также режим выравнивания определяет, как позиционировать строку текста (обрамляющий прямоугольник) относительно опорной точки и как выводить текст: слева направо или справа налево. Значение этого атрибута можно изменить при помощи функции 5еНехШ1дп: 01 ИТ ЗеГГехШ 1дп(Н0С Мс, 01 ИТ ГМоОе); передавая второму параметру битовую маску, образованную объединением1 фла- гов, перечисленных в табл. 2.30. При этом из каждой группы флагов можно ис- пользовать только один флаг. Флаги с кодом 0x00 являются значениями по умол- чанию. Таблица 2.30. Флаги выравнивания текста Г руппа Флаг Код Описание Что считать опорной точкой. Обновлять или не обновлять текущую позицию пера ТА_1\Ю11РОАТЕСР 0x00 В качестве опорной точки использовать точку (пХЗЩЦ, пУ51агЬ). Текущая позиция пера не обновляется после вывода текста ТА.11РОАТЕСР 0x01 В качестве опорной точки использовать текущую позицию пера. Текущая позиция пера обновляется после вывода текста 1 Поразрядная логическая операция ИЛИ.
Г руппа Флаг Код Описание Выравнивание ТА_1_ЕГГ 0x00 Опорная точка задает левую границу по горизонтали обрамляющего прямоугольника ТА_Н1СНТ 0x02 Опорная точка задает правую границу обрамляющего прямоугольника ТА_СЕ1\1ТЕк 0x06 Горизонтальный центр обрамляющего прямоугольника совмещается с опорной точкой Выравнивание ТА_ТОР 0x00 Опорная точка задает верхнюю границу по вертикали обрамляющего прямоугольника ТА-ВОТТОМ 0x08 Опорная точка задает нижнюю границу обрамляющего прямоугольника ТА_ВА5Е1_1ЫЕ 0x18 Базовая линия текста совмещается с опорной точкой Направление ТА_кТ1_кЕА011\16 0x100 Текст выводится справа налево (применяет- вывода текста ся только для арабского языка или иврита) Пример использования функции ТехЮи! можно найти в листинге 2.1, а также в некоторых других фрагментах программного кода. Вывод текста с табуляцией К сожалению, функция ТехЮи! не обрабатывает управляющие символы, в том чис- ле символы перевода строки \п и табуляции \1. Встретив любой такой символ, она просто выведет глиф • (для системного шрифта $У$ТЕМ_ЕОМТ) или глиф □ для несистемного шрифта. В то же время табуляция широко используется в простейших текстовых ре- дакторах для выравнивания текста по столбцам, облегчающего восприятие ин- формации. Для обеспечения работы с табулированным текстом АУтйомга содер- жит функции ТаЬЬесПехЮи!: и 6еНаЬЬес1Тех1:Ех1:еп1:. Функция ТаЬЬесПехЮи!: имеет следующий прототип: 1ОМВ ТаЫэесГГех^СШ(НОС НОС, Ш X, Ш V, 0РСТ8Т0 ТрЗШпд, Ш пСоигД, Ш ЩитТаЬз, С0И8Т ОР11Х1Т 1рпТаЬ8^орз, Ш хТаЬ0г1д1п): Первые пять параметров имеют то же значение, что и у функции ТехЮи!. Шес- той параметр, 1’МитТаЬ$, определяет количество позиций табуляции. Седьмой па- раметр, ЕрпТаЬЗЕорз, содержит указатель на массив позиций табуляции, заданных в логических единицах. Позиции табуляции должны быть отсортированы в воз- растающем порядке. Если шестой параметр равен нулю и одновременно седьмой параметр равен МОП, то позиции табуляции устанавливаются через одинаковые промежутки, равные восьмикратной средней ширине символов. Если шестой параметр равен единице, то первый элемент массива ЕрпТаЬЗЕорз содержит число символьных позиций, кото- рое каждый раз прибавляется для определения следующей позиции табуляции. Последний параметр, хТаЬОлдш, задает логическую координату по горизонтали точки отсчета позиций табуляции. Часто бывает удобно определить эту точку так, чтобы она совпадала с начальной позицией вывода строки, поскольку в этом случае массив ЕрпТаЬЗЕорз перестает быть зависимым от конкретной позиции вывода. Если выводимая строка содержит символы табуляции \1, то СВ1 отображает начало строки, пока не обнаружит символ табуляции. После этого СВ1 просмат- ривает массив позиций табуляций, и если первая позиция подходит для продол-
жения вывода (то есть она находится правее границы последнего выведенного символа строки), то вывод продолжается с этой позиции. В противном случае СВ1 берет следующую позицию из массива и проверяет ее и т. д. То же самое происхо- дит при обнаружении следующего символа табуляции. Таким образом, функция не гарантирует, что символы после т?-го символа табуляции будут выводиться в тг-й позиции табуляции. Позиции табуляции в массиве 1рпТаЬ51:ор$ могут быть отрицательными. В этом случае СВ1 использует абсолютное значение указанной величины, но выравни- вает текст по правому краю перед заданной позицией, вместо выравнивания по левому краю после нее. В случае успешного завершения функция ТаЬЬесИехЮи!: возвращает 32-разрядное число, старшее слово которого содержит высоту, а младшее — ширину выведенной строки. В случае неудачного завершения работы возвращается нулевое значение. Функция Се1ТаЬЬес1Тех1:Ех1:еп1: возвращает размеры табулированного текста, не выводя его. Следующий фрагмент кода показывает пример использования функции ТаЬЬедТехЮи!. Оконная процедура, код которой приводится ниже, обеспечива- ет вывод на экран небольшой таблицы: СКЕ81ЛТ САЕЕВАСК Шс1Ргос(НШО Шпс1, 1Л1Х1Т иМзд, ПРАВАМ мРагат, КРАКАМ 1 Рагапа) { НОС НОС; РА1МТ8ТК0СТ рз; 1пЬ ЦаЬзЦорСП = { -130, 150, 250 }; 1 ггЕ 1; сопбС. сНаг* 11пез[] = { "Огоир""И" "РезиИ" "И" "ЕипсЫоп" "И" "РагатеЬегз", "РопГ "И" "НЕОМТ" "И" "СгеаЬеЕопЬ" "И" "(1пЬ пНеадРтЬ, "ТехГ "И" "СОЕОВВЕР""И" "8еЬТехШ)1ог""И" "(НОС Обе, ...)", "ТехЬ" "И" "ВООЕ" "И" "ТехООиО" "И" "(НОС НЬс, ...)" }; 1пЬ х = 50, у = 50; змПсН (иМзд) { сазе Ш_РА1МТ: НОС = ВедапРаап0(Н1л1пс1, &рз); Сог (1 =0; 1 <4; 1++) { 10 (1 == 1) у += 10; у += НШОВО(ТаЬЬесРГехЬОиЬ(НОС, х, у, 11пез[1], зЬг!еп(11пез[1 ]), з1хеоС(ЬаЬзЬор)/з1хео1?(1пЬ), ЬаЬзЬор, х)); } Епс1Ра1пШ1л1пс1, &рз); Ьгеак; сазе Ш_0Е8ТВ0У: РозШПОМеззаде(О); Ьгеак; ЬеГаиИ: геЬигп ОеНл/а псЫРгос(Н1л1пс1, иМзд, мРагат, 1Рагат); } геЬигп 0;
Таблица формируется в цикле Гог. Обратите внимание на то, что значение, воз- вращаемое функцией ТаЬЬесИехЮи!:, мы используем для вычисления смещения, что- бы получить координату у следующей строки выводимого текста. Последнему па- раметру функции передается то же значение х, которое задает абсциссу начала вывода строки. Поэтому, определяя абсолютные значения позиций табуляции, СВ1 прибавляет к величинам, извлекаемым из массива ГаЬзГор, величину х. Результат работы этого кода показан на рис.2.28. Заметим, что горизонтальная линейка, показывающая физические координаты по горизонтали, нарисована соответствующими вызовами функций Мсл/еТоЕх, ИпеТо и ТехЮиГ. Чтобы не загромождать пример, эти инструкции в программе не пока- заны. Обратите внимание на выравнивание по правому краю второго столбца таб- лицы относительно позиции 180, вычисленной в результате сложения 50 + 130. 0 100 200 300 400 I .... I .... I .... I .... I .... I .... I .... I .... I Сгоир НезиН Рипсйоп Рзгзте1егз Роп1 Тех1 Тех( НЕОМТ Сгез1еЕоп1 (т! пНе!дМ, ...] СОЬОНПЕЕ 5е1Тех1Со1ог (НОС Ьс1с, ...] ВОО1_ ТехЮШ (НОС Ьс1с, ...] Рис. 2.28. Вывод табулированного текста Межсимвольные интервалы В контексте устройства имеется специальный атрибут, управляющий расстоя- нием между символами, — межсимвольный интервал (ехёга зрасе). Межсимволь- ный интервал добавляется к каждому символу, включая символы пробела, ког- да СВ1 выводит строку текста. По умолчанию этот атрибут равен нулю. Функция ЗеГТехГСНагасГегЕхГга присваивает ему новое целочисленное значение в логичес- ких единицах, возвращая предыдущее значение. Функция беГТехГСНагасГегЕхГга возвращает текущее значение межсимвольного интервала. Межсимвольные интервалы могут использоваться как для разрядки, так и для уплотнения текста. Чтобы почувствовать, какие возможности дает функ- ция ЗеГТехГСНагасГегЕхГга, модифицируйте предыдущую программу следующим образом. Фрагмент с циклом Гог надо заменить таким кодом: 8еГГехШагасТегЕхРга(И0С, 4); Тог (1 =0; 1 <4; 1++) { 1Т (1 == 1) { У += Ю; 8еПехШагасТегЕхРга(И0С, 0); } у += Н1ШЕ0(ТаЬЬесГГехР0иШЕ)С, х, у, ИпезШ, зШеп(1 апезЕпИ), 81хеоШаЬ8^ор)/81хеоШпЩ, РаЬа^ор, х)); } После этого запустите программу и посмотрите, как изменится отображение текста на экране. Выравнивание по ширине (выключка1) Функция ТехГОиГ обеспечивает выравнивание текста по левому или правому краю, а также по центру. Вид выравнивания зависит от текущего режима выравнива- 1 Типографский термин, означающий увеличение межсловных интервалов в строке для выравнива- ния по ширине.
ния. Но при этом функция не поддерживает выравнивание по ширине (выключ- ку). Выравнивание по ширине означает, что левый край текста должен быть вы- ровнен по левой границе области, а правый край — по правой границе. Это дости- гается равномерным увеличением протяженности пробелов, разделяющих слова в строке. Для решения указанной проблемы СВ1 позволяет использовать функции Се1Тех1:Ех1:еп1:Ро1’п1:32 и ЗеНех^изВЛсаВоп. Допустим, что надо реализовать выключку при выводе строк текста в область с левой границей х и правой границей х + Предполагается, что протяжен- ность строк не превышает \л/1с11:Н логических единиц. План решения обычно содер- жит следующие шаги: 1. С помощью инструкций 812Е зые: 6еГГех^Ех^егДРо1гД32(И0С, 1р8^г1пд, пСоигД, &8ые); получить размеры обрамляющего прямоугольника для строки 1р51лпд, содер- жащей пСоип! символов. Эти размеры возвращаются в виде значений полей параметра зые. Поле 81'ге.сх будет содержать ширину обрамляющего прямоу- гольника, а поле 81'ге.су — высоту обрамляющего прямоугольника. 2. Подсчитать количество разделительных символов пВгеак в строке 1р51ппд. Раз- делительным символом обычно является пробел, но он может быть переопреде- лен в каком-либо шрифте на другой символ. Если нужно уточнить, какой сим- вол на самом деле является разделительным, используйте функцию беНехЦИеФ’сз с последующим анализом поля Ет ВгеакСНаг структуры ТЕХТМЕТК1С. 3. Вычислить размер пространства, которое необходимо распределить между пВгеак разделительными символами: Ш ЬгеакЕх^га = - зые.сх; 4. Вызвать функцию 8еЕГехШ8Ш1саШп(1ас1с, ЬгеакЕхШ, пВгеак); передав ей определенные выше величины ЬгеакЕхЕга и пВгеак. Функция 5еНех1:- ЗизВЛсаВоп присвоит атрибуту контекста устройства, отвечающему за выключ- ку текста, величину дополнительного интервала, используемую после этого функциями ТехЮи! и ЕхНехЮиЕ 5. Позаботиться о том, чтобы текущий режим выравнивания текста содержал флаг ТА_1_ЕЕТ (значение по умолчанию). После этого уже можно вызвать функцию ТехЮи!: для отображения строки текста. Следует отметить, что при каждом вызове функции ЗеПехЮизВ’ЛсаВ’оп накап- ливается погрешность, если величина ЬгеакЕхЕга не делится нацело на пВгеак. Что- бы эта погрешность не повлияла на последующую работу функции беНех^Ех^еп^- РО1П132 при выводе многострочного текста, рекомендуется перед обращением к 6еНех1:Ех1:еп1:Ро1’п1:32 сбросить накопленную погрешность вызовом функции 8еЕГехШ8Ш1саШп(1ас1с, 0, 0); В листинге 2.1 приводится программа, в которой демонстрируется описанная технология выравнивания текста по ширине.
Листинг 2.1. Проект ТехОиз!: ////////////////////////////////////////////////////////////////////// // ТехбВизб.срр #1пс1ис1е <\л/1 пбомз. В> #1пс1ис1е "К1л/пс1.В" 1КЕ81ЛТ САЕЕВАСК ШбРгос(НШО, 1ЛМТ, ПРАВАМ, ЕРАВАМ); ВООЕ ТехбВизбОибСНВС Нс1с, Ш х, Ш у, ЕРСТЗТЕ 1 рЗВзг, Ш \л/1 сЕВ, сВаг ЬгеакСВаг = ' '); ////////////////////////////////////////////////////////////////////// 1пР ШИАР1 ШпМапп(НIИЗТАМСЕ В1пзбапсе, Н1М5ТАМСЕ ВРгеу1пзбапсе, ЕРЗТЕ 1рСтсИ1пе, Ш пСтс13Но\л/) { М86 тзд; К1л1пс1 та1п1л1пс1("ТехР ВизбЮсаб1оп", В1пзбапсе, пСтс13Но\л/, 1л1пс1Ргос); \л/Еп'1е (СебМеззадеС&тзд, МВЕЕ, 0, 0)) { ТгапзШеМеззадеС&тзд); Втзра'ЕсВМеззадеС&тзд); } гебигп тзд.мРагат; } ////////////////////////////////////////////////////////////////////// ЕЕЕЗОЕТ САЕЕВАСК ШбРгос(НШВ Шпс1, О1ИТ иМзд, ПРАВАМ мРагат, ЕРАВАМ 1Рагат) { НОС ВВС; РА1М1ТЗТЕ0СТ рз; сВаг* РехР[6] = { "Рассуждай", "Рассуждай токмо", "Рассуждай токмо о том", "Рассуждай токмо о том, о чем понятия", "Рассуждай токмо о том, о чем понятия твои тебе", "Рассуждай токмо о том, о чем понятия твои тебе сие дозволяют." }; Ш х = 20, у = 20; 317Е зые; 1 пб п1л11 сЕВ, 1 ; 8\л/ПсИ (иМзд) { сазе Ш_РА1МТ: ВВС = ВедипРаппб(В1л1пс1, &рз); 6ебТехбЕхбепбРо1пб32(ВВС, бехб[5], збг1еп(бехб[5]), &з1хе); п1лМббВ = зые.сх; Тог (1 =0; 1 <6; 1++) { ТехбВизбОиШВС, х, у, РехШЪ пШсЕВ); у += зые.су + 2; } Епс1Ра1пШ1л1пс1, &рз); продолжение
Листинг 2.1 (продолжение) Ьгеак; сазе Ш_0Е8ТШ: РозЬОьп ЬМеззаде(О); Ьгеак; с1еГаи!Ь: геЬигп ОеГШ псЫРгос(Шпс1, иМзд, мРагат, 1Рагат); } геЬигп 0; } ////////////////////////////////////////////////////////////////////// ВООЕ ТехЬЬизЬОиЬ(НОС Мс, 1 пЬ х, 1 пЬ у, ЬРСТЗТВ 1р8Ьг, 1 пЬ \л/1 сПзИ, ТСНАЕ ЬгеакСЬаг) { 812Е з1хе; 1пЬ пСоипЬ = зЬг!еп(1р8Ьг); 8еЬТехЬЬизЫГ1саЫоп(Ьс1с, 0, 0); 6еЬТехЬЕхЬепЬРо1пЬ32(Ьс1с, 1р8Ьг, пСоипЬ, &з1хе); 1пЬ пВгеак = 0; Гог (1пЬ 1 = 0; 1 < пСоипЬ; ++1) 1Г (1р8Ьг[1] == ЬгеакСЬаг) пВгеак++; 1 пЬ ЬгеакЕхЬга = мШИ - зые.сх; 1Г (ЬгеакЕхЬга < 0) ЬгеакЕхЬга = 0; 8еЬТехЬЗизЫГ1саЫоп(Ьс1с, ЬгеакЕхЬга, пВгеак); геЬигп ТехЬОиЬ(Мс, х, у, 1р8Ьг, пСоипЬ); } ////////////////////////////////////////////////////////////////////// В проекте используется класс К\Л/пс1, поэтому не забудьте скопировать в каталог проекта файлы К\Л/пс1.Г| и К\Л/пд.срр, код которых приведен в листинге 1.2, и подклю- чить их в состав проекта при помощи окна Рго]'ес1з Могкзрасе интегрированной среды. Код, отвечающий за выравнивание по ширине, инкапсулирован в функцию ТехЮизЮи!. В оконной процедуре функция ТехЮизЮи! вызывается, чтобы вывести шесть строк из массива 1ех1:. Ширина области вывода попределяется по самой длинной строке с адресом 1ех1:[5]. Результат работы программы показан на рис. 2.29. Рассуждай Рассуждай токмо Рассуждай токмо о том Рассуждай токмо о том, о чем понятия Рассуждай токмо о том, о чем понятия твои тебе Рассуждай токмо о том, о чем понятия твои тебе сие дозволяют. Рис. 2.29. Выравнивание текста по ширине Абзацное форматирование в прямоугольнике В пользовательском интерфейсе АУшсЬауз часто требуется вывести длинный текст в прямоугольнике, способном вместить несколько строк. Или, напротив, короткий текст поместить в прямоугольнике с учетом заданного позиционирования. Для ре- шения подобных проблем АУш32 СВ1 предоставляет функции ОгамТехЬ и Ога\л/Тех±Ех.
Функция Огахл/Тех! имеет следующий прототип: Ш ОгамТехЦ НОС НОС, 0РСТ8Т0 1р8^г1пд, Ш пСоиЩ, ЬРОЕСТ 1рКесД, ШМТ иРопШ // дескриптор контекста устройства // указатель на текстовую строку // длина текста // прямоугольник, в котором размещается текст // флаги форматирования Так же как и другие функции вывода текста, функция ОгамТехЕ принимает в ка- честве параметров указатель на символьную строку и длину строки. Однако функ- ция ведет себя более интеллектуально по отношению к строкам с завершающим нулевым символом. Вы можете передать значение -1 на месте параметра пСоипЕ, и функция сама определит длину строки. Если же строка не имеет завершающего нулевого символа, то благоразумнее будет указать ее длину в аргументе пСоипЕ. Функция ОгамТехЕ выводит текстовую строку в прямоугольной области, задан- ной параметром [рКесЕ. Последний параметр, иЕогтаЕ, определяет метод формати- рования текста. Его значение представляет собой битовую маску, образованную объединением флагов форматирования. Наиболее часто употребляемые флаги приведены в табл. 2.31 (полный перечень флагов см. в М8В?<). Таблица 2.31. Флаги форматирования для функции ОгамТехЕ Флаг Код Описание ОТ_1_ЕРТ 0x0000 Выравнивание текста влево ОТ_СЕ1\1ТЕк 0x0001 Центрирование по горизонтали ОТ_Н1СНТ 0x0002 Выравнивание текста вправо ОТТОР 0x0000 Начало размещения — в верхней части прямоугольника ОТ-УСЕЫТЕк 0x0004 Центрирование по вертикали (используется только вместе с ОТ_511\1С1_Е1_1МЕ) ОТ-ВОТТОМ 0x0008 Размещение внизу прямоугольника (используется только вместе с 0Т_51М61_Е1_1ЫЕ) ОТ_\/\/ОкОВкЕАК 0x0010 Перенос текста на следующую строку после окончания очередного слова, если оставшаяся часть текста не помеща- ется по ширине прямоугольника вывода. Флаг работает, только если не указан флаг 0Т_811\161_Е1_1ЫЕ 0Т_51М61_Е1_1ЫЕ 0x0020 Вывод текста только в одну строку. Символы возврата каретки и перевода строки не воспринимаются как управляющие сим- волы (выводятся как • или □) ОТ_ЕХРАЫОТАВ8 0x0040 Интерпретация символов \Е как управляющих символов, зада- ющих табуляцию. По умолчанию шаг табуляции равен восьми- кратной средней ширине символов ОТ-ЫОРНЕЕ1Х 0x0800 Отменяет обработку префиксов &. Обычно Ога\л/ТехЕ интерпре- тирует префикс & как директиву «подчеркнуть следующий за префиксом символ», а последовательность && - как дирек- тиву вывести одиночный символ &. С флагом ОТ_1\ЮРкЕР1Х символ & воспринимается наравне с другими символами, а не как префикс ОТ-РАТН-ЕШР818 0x4000 Если строка не помещается по ширине прямоугольника, часть символов в середине строки замещается многоточием (используется только вместе с 0Т_811\161_Е1_1ЫЕ) ОТ_ЕЫО_Е1_1_1Р515 0x8000 Если строка не помещается по ширине прямоугольника, часть символов в конце строки замещается многоточием (исполь- зуется только вместе с 0Т_81М61_Е1_1ЫЕ)
Если параметр иЕогта!: имеет нулевое значение, то \Упк1о\\ъ интерпретирует текст как ряд строк, разделенных символами возврата каретки ('\г' или ОхОН) или символами конца строки ('\п' или ОхОА). Вывод текста производится, начиная с верхнего левого угла прямоугольника. Возврат каретки или конец строки вос- принимаются как команда перейти на новую строку. Вывод новой строки начина- ется под предыдущей строкой с интервалом, равным высоте символа в строке. Любой текст, в том числе и части букв, попадающие при выводе правее или ниже границ прямоугольника, отсекаются. Можно предотвратить потерю текста от отсечения по правой границе, задав флаг ОТ_МОВОВВЕАК. Если выводится короткий текст, то рекомендуется указывать флаг 0Т_ 51М 61_Е И И Е. Это позволяет использовать все флаги позиционирования текста в прямоугольнике. В случае успешного завершения функция Отахл/ТехЕ обычно возвращает высоту выведенного текста. Для многострочного текста возвращается суммарная высота всех строк. Исключение составляют случаи использования функции с флагом 0Т_7СЕМТЕВ или ОТ-ВОТТОМ. В этих двух случаях функция возвращает смещение нижней границы текста относительно верхней границы прямоугольника. Если функция по каким-либо причинам не смогла нормально выполнить свою работу, то возвращается нулевое значение. Покажем примеры использования функции ОгамТехЕ На рис. 2.30 приведен результат трехкратного вызова функции ОгамТехШОС, ^ехН, -1, &гесЬ, иГоптМ); для вывода строки сИаг ^ехЩ] = "&Ореп С:\\1д/1 ММТ\\8у81ает32\\дс1132.с111"; со следующими значениями параметра иЕогтаЕ а) 0; б) 0Т_51М61_Е1_1МЕ | ОТ.СЕМТЕВ | ОЦ/СЕМТЕВ | 0Т.М0РВЕЕ1Х; в) 0Т_51М61_Е1_1МЕ | 0Т_В16НТ | ОТ.ВОТТОМ. Прямоугольник гее! отображен на рисунке дополнительным вызовом функ- ции КесЕапдЕе. а Ореп С:\?7ЖГ\1Т\$у51:ет32\дс1!32.с111 б &Ореп С:\?7ЖГ\1Т\$у51:ет32\дс1!32.с111 в Ореп С:\У/1ММТ\5у5{ет32\дсЛ32.с111 Рис. 2.30. Вывод строки 1ех11 с разным позиционированием в прямоугольнике гес1 На рис. 2.31 демонстрируется вывод этого же текста в более узком прямо- угольнике, который не может уместить весь текст. Трем вызовам функции соот- ветствуют значения параметра иЕогтаЕ а) 0; б) 0Т_51М61_Е1_1МЕ | ОЦ/СЕМТЕВ | 0Т_ЕМ0_ЕШР515; в) 0Т_51М61_Е1_1МЕ | ОТ.ВОТТОМ | 0Т_РАТН_ЕШР515. Обратите внимание на то, что при первом вызове функции 0 га хл/ТехЕ конец стро- ки отсекается, при втором — хвостовой фрагмент строки заменяется многоточием, при третьем — замена многоточием осуществляется в средней части, чтобы пока- зать завершающую часть строки.
На рис. 2.32 показан результат вызова функции ВгамТехШВС, ЦехЦ2, -1, &гесД, 0Т_51 МО1Е11МЕ | ОТ_УСЕМТЕЕ | 0Т_ЕХРАМ0ТАВ8); для вывода строки сИаг ТехТ2[] = "&Ореп ИС:\\ШММТ\\8у8Тет32\\дс1132.с111"; содержащей символ табуляции У\Г. а Ореп С:\У/1ММТ\5у51ет32\дс1|3 б Ореп С:\У/1ММТ\5у51ет32\д... в Ореп С:\\У1ММТ\5у5...\дс!|32.с111 Рис. 2.31. Вывод строки ЕехМ в прямоугольнике, имеющем недостаточную ширину для размещения текста Ореп С:\\А/1ЫЫТ\5у51:ет32\дс1|32.с111 Рис. 2.32. Вывод строки ЕехЕ2, содержащей символ табуляции На рис. 2.33 приведен вывод текста сИаг ТехТЗ[] = "Магтпд: Ш’8 сотрыТег ргодгат \п 18 ргоТесТес! Ьу соруп’дИР 1 а\л/ \п апб 1 пТегпаТ1 опа! ТгеаТ1ез": в результате вызова функции ВгамТехШВС, ТехТЗ, -1, &гесД, ОТ_СЕМТЕЮ; У/агшпд: Н115 сотрЫег ргодгат 15 рго1ес1ес1 Ьу сорупдЫ 1аш апс! нКетаНопа!1геаНе5 Рис. 2.33. Вывод текста, содержащего символы конца строки (\п), с флагом ОТ_СЕ1\1ТЕк Наконец, аналогичный текст, но не содержащий символов конца строки сИаг РехР4[] = "Магтпд: Ш’8 сотрыТег ргодгат 18 ргоТесТес! Ьу соруг1д1тЕ 1 а\л/ апс! 1пТегпаЫопа! ТгеаЫеа"; выводится с помощью вызова ОгамТехШОС, ТехТ4, -1, &гесТ, 0Т_ШВ0ВВЕАК); так, как это показано на рис. 2.34. ЬУагшпд: Н115 сотрите г ргодгат 15 рго1ес1ес! Ьу сорупдЫ 1а^ апс! !п1егпаНопа1 (геайез Рис. 2.34. Преобразование однострочного текста в многострочный при помощи флага ОТ_\Л/ОкОВНЕАК Функция Ота\л/Тех1:Ех имеет следующий прототип: 1пТ ОгамТехТЕх(НОС ВВС, ЕРТЗТЕ 1 рсИТЕхТ, 1 ггЕ ссИТехТ, ЕРЕЕСТ 1 ркесЦ, ШТ сЮТЕогтаТ, ЕРОРАМТЕХТРАРАМЗ 1рОТРагат8); Она отличается от ОгамТехТ наличием дополнительного параметра, который содержит указатель на структуру 0КАМТЕХТРАКАМ5. Эта структура определяет рас- стояния между позициями табуляции, левые и правые поля для вывода текста,
а также используется для возвращения длины выведенной строки. Более подроб- ная информация об этой функции приведена в М8В№ Функция ЕхСТехЮи! Функция ЕхБГехЮиБ представляющая собой расширенную версию функции ТехЮиБ имеет следующий прототип: В001_ Ех-ЬТех-ЬОиЦ(НОС Мс, 1гД X, 1гД V, 01 ИТ ГиОрШпз, С0И8Т ВЕСТ* 1ргс, ЕРСТЗТВ 1р8^г1пд, 1Л1Х1Т сЬСоипР, С0И8Т 11Х1Т* 1рОх); Параметры Ндс, X, V, 1р51ппд, сЬСоигй имеют тот же смысл, что и аналогичные параметры функции ТехЮиБ Поэтому следующий вызов: ЕхРТехРОиШОС, х, у, О, МОП, 1р8^г1пд, сЬСоипР, МОП); эквивалентен вызову функции ТехРОиШОС, х, у, 1р8Рг1пд, сЬСоипР); Остается рассмотреть три новых параметра. Параметр ГиОрБопз содержит на- бор флагов, которые управляют интерпретацией других параметров. Необязатель- ный параметр1 1ргс указывает на прямоугольник, который исполняет роль прямо- угольника отсечения, или прямоугольника фона для выводимого текста, или того и другого вместе. Необязательный параметр 1рОх содержит указатель на массив расстояний между ячейками символов. В табл. 2.32 перечислены допустимые зна- чения флагов, которые могут объединяться в битовую маску, используемую в ка- честве значения параметра ГиОрБопз. Таблица 2.32. Флаги функции ЕхПехЮи! Флаг Код Описание ЕТО_О РАСИНЕ 0x0002 Перед выводом текста прямоугольник 1ргс закрашивается цветом фона графических элементов ЕТО-СИРРЕО 0x0004 Текст отсекается по прямоугольнику 1ргс ЕТ0_61_УРН_1Ы0ЕХ 0x0010 Параметр 1р81ппд указывает на массив индексов глифов (вместо кодов символов), который можно получить вызовом функции Се1СНагас1:егР1асетеп1:. Индексы глифов всегда являются 16-разрядными величинами ЕТ0_кТ1_кЕА011\16 0x0080 Для шрифтов арабского языка и иврита текст выводится справа налево ЕТО_1\111МЕК1С81_ОСА1_ 0x0400 Числа выводятся символами национальных алфавитов ЕТО_ЫиМЕН1С81_АТ1М 0x0800 Числа выводятся арабскими цифрами ЕТО_161\ЮКЕ1_А1\1611А6Е 0x1000 Не выполнять дополнительную языковую обработку ЕТО.РОУ 0x2000 Параметр 1рОх указывает на массив пар, в которых первое число определяет шаг по горизонтали между двумя смеж- ными ячейками символов, а второе — относительное сме- щение этих ячеек по вертикали Одним из возможных применений функции ЕхНехЮи!: является вывод текста в ячейки некоторой таблицы. Если вы хотите заполнить фон ячеек таблицы цве- том фона графических элементов, который используется при выводе текста, то можно передать прямоугольник ячейки функции ЕхПехЮи!: и установить флаги ЕТО_ОРАСШЕ и ЕТ0_С1_1РРЕ0. 1 Может иметь значение МОП.
Также функция позволяет более точно размещать глифы символов при исполь- зовании флага ЕТ0_61_УРН_1М0ЕХ. Такое размещение бывает необходимо для гра- фических приложений, имеющих режим отображения текста АУУ81АУУС. В этом режиме обеспечивается печать документа на принтере точно в таком виде, в ка- ком он представлен на экране. Более подробную информацию об использовании функции ЕхНехЮи!: для решения таких проблем можно найти в книге [6]. Нетривиальный вывод текста В современных текстовых редакторах и других графических пакетах решаются различные нетривиальные задачи вывода текста. Неполный перечень таких задач приведен в следующем списке: □ У правление кернингом (контекстная регулировка межсимвольных расстояний). □ Закраска текста кистью. □ Работа с текстом в растровом формате. □ Применение траекторий (ЗВ1 при выводе текста. Эти вопросы не рассматриваются в книге, так как это привело бы к неоправданно- му увеличению объема издания. Достаточно подробно они освещаются в книге [6]. Полосы прокрутки и вывод текста При выводе текста может возникнуть ситуация, когда в клиентской области окна недостаточно места для его размещения. Например, длина строк может оказаться больше ширины окна, а количество строк слишком большим, чтобы поместиться в окне с данной высотой. Полосы прокрутки (зсгоИ Ъагз) являются самым удоб- ным решением этой проблемы. Они просты в использовании и обеспечивают удоб- ный просмотр информации. Следует различать два вида полос прокрутки: □ полоса прокрутки окна’, □ полоса прокрутки — элемент управления типа 5сгоII Ьаг. Второй вид полос прокрутки рассматривается в главе 7. Полосы прокрутки окна, называемые также стандартными полосами прокрут- ки, размещаются вдоль правой и нижней рамок окна, как показано на рис. 2.35. Чтобы они появились, достаточно при вызове функции Сгеа1е\Л/1 пс1о\л/ указать фла- ги М$_\/$СКО1_1_ (вертикальная прокрутка) и М$_Н$СКО1_1_ (горизонтальная прокрут- ка) в параметре д\л/51:у 1е. Вертикальная полоса прокрутки позволяет пользователю просматривать информацию в окне, прокручивая ее вниз или вверх. Горизонталь- ная полоса прокрутки позволяет перемещать информацию вправо или влево. Тер- минология полос прокрутки, связанная с направлениями прокрутки, ориентиро- вана на пользователя. Фактически программа перемещает документ в окне в противоположном направлении. На рисунке поясняется также рекомендуемое использование вертикальной полосы прокрутки для просмотра текста. Текущее положение информации в окне относительно документа в целом отображается положением движка {ЛитЬ'). Кроме щелчков мышью в указанных местах пользователь может также при помо- щи мыши переместить движок в любое необходимое положение. Использование горизонтальной полосы прокрутки осуществляется аналогичным образом.
н 5сго11 Ьагх Щелкните здесь для прокрутки на одну строку вверх Щелкните здесь для прокрутки _ на одну страницу вверх Переместите движок для перехода в нужное место Щелкните здесь для прокрутки__ на одну страницу вниз Щелкните здесь для прокрутки на одну строку вниз Рис. 2.35. Полосы прокрутки окна Следует помнить, что, когда у окна появляются полосы прокрутки, клиентс- кая область окна не включает в себя пространство, занятое полосами прокрутки. \Упк1о\\ъ обеспечивает всю логику работы мыши с полосами прокрутки. Од- нако у полос прокрутки нет интерфейса клавиатуры. Если необходимо дублиро- вать некоторые функции полос прокрутки клавишами управления курсором, то следует реализовать эту логику самостоятельно (этот вопрос будет рассмотрен в главе 4). Параметры полосы прокрутки Каждая полоса прокрутки характеризуется несколькими параметрами: □ Диапазон (гап§е), задаваемый двумя целыми числами, отражающими минималь- ное (пМ1’п) и максимальное (пМах) значения в «единицах данных» проблемной области. При выводе текста «единицами данных» для вертикального измерения обычно считают строки текста, а в горизонтальном измерении1 — отдельные сим- волы. Величина гапде определяется по формуле гапде = пМах-пМ1’п + 1. □ Положение (розйлоп) — целое число внутри диапазона, отражающее положе- ние движка. Когда движок находится в крайней верхней или крайней левой позиции на полосе прокрутки, то его положение соответствует минимальному значению диапазона. Крайнее нижнее или крайнее правое положение движка соответствует максимальному значению диапазона. □ Размер страницы (рарр) — целое число, отображающее количество «единиц данных», которые могут разместиться в клиентской области окна при его те- кущих размерах. АУтсЕпуз использует размер страницы и диапазон полосы про- крутки для управления длиной движка. При этом длина движка выглядит про- порционально той части документа, которую пользователь видит в окне. Кроме того, если указан размер страницы, то система берет на себя управление види- 1 При выводе растровых изображений «единицей данных» является 1 пиксел.
мостью полосы прокрутки при изменении размеров окна. Как только размер страницы становится больше диапазона, полоса прокрутки становится неви- димой и недоступной. □ По умолчанию для полосы прокрутки установлен диапазон от 0 до 100. Но вы можете переопределить этот диапазон. Так, если отображаемый документ содержит пИпе строк, то можно использовать два варианта определения диапа- зона. В первом случае для границ диапазона задаются значения пМш = 0 и пМах = = пИпе - 1. Во втором случае задаются пМ1’п = 1 и пМах = пИпе. Первый вариант считается более предпочтительным, так как он согласуется с индексацией эле- ментов в массиве, а отображаемый текст часто хранится в виде массива строк. Для установки диапазона полосы прокрутки, размера страницы и текущего положения движка предназначена функция 5е15сго111пГо: Ш 8еб8сго111пТо(НШ0 Ыпс1, Ш ТпВаг, 1РС8СК0ШМГ0 1рз1, В001_ ТВебгам); Параметр Н\л/пс1 содержит либо дескриптор окна, если функция применяется для стандартных полос прокрутки, либо дескриптор элемента управления 5сго11 Ваг. Интерпретация зависит от значения второго параметра — Гп Ваг. Параметр ГпВаг определяет тип полосы прокрутки. Он может принимать зна- чения 5В_\/ЕКТ (стандартная вертикальная полоса), 5В_Н0В2 (стандартная гори- зонтальная полоса) и $В_СТ1_ (элемент управления 5сго11 Ваг). Параметр 1р$1’ содер- жит указатель на структуру 5СВ0И1МЕ0, которая будет рассматриваться ниже. Параметр Ь Веская определяет, должна ли система перерисовать полосу про- крутки сразу после выполнения функции 5е15сго111пГо. Структура 5СВ0И1МЕ0 определена в файле \лл п изег.Н следующим образом: УуребеТ збгысб Рад8СВ0ШМГ0 { Ы1МТ сЬ8ые: // размер структуры в байтах ЩИТ ТМазк; // маска устанавливаемых/возвращаемых параметров Ш пМ1п; // минимальное значение диапазона Ш пМах; // максимальное значение диапазона ЩИТ пРаде; // размер страницы Ш пРоз; // положение движка Ш пТгаскРоз; // текущее положение движка, перемещаемого пользователем } 8СВ0ШГ0; Поле ГМазк задает параметры, которые будут устанавливаться функцией 5е15сго111пГо или возвращаться функцией СеГЗсгоШпГо. Значением этого поля может быть комбинация флагов, перечисленных в табл. 2.33. Для получения информации о текущих значениях параметров полосы прокрут- ки приложение может воспользоваться функцией 6е15сго111пГо. Важно понимать, что если в программе используются полосы прокрутки, то вместе с АУшсЬмгз вы берете на себя ответственность за их поддержку и обновле- ние положения движка. Так, АУшсклуз отвечает за следующие аспекты: □ управление логикой работы мыши с полосой прокрутки; □ обеспечение временной инверсии цвета при щелчке кнопкой мыши в зоне про- крутки на одну страницу; □ перемещение движка, когда пользователь захватывает его мышью и передви- гает по полосе; □ отправка сообщений полосы прокрутки в оконную процедуру;
□ переключение видимости полосы прокрутки в зависимости от того, помеща- ется ли документ по своим размерам полностью в окне. Таблица 2.33. Значения флагов для параметра ГМазк Флаг Описание 81Р_РАСЕ Поле пРаде содержит размер страницы для пропорциональной полосы прокрутки 81Р_РО8 Поле пРоз содержит позицию движка, которая не обновляется при перемещении движка пользователем 8И=_ИА1\16Е Поля пМ1п и пМах содержат минимальное и максимальное значения для диапазона прокрутки 81Р_ТКАСКРО8 Поле пТгаскРоз содержит текущую позицию движка, когда пользова- тель перетаскивает его (поле доступно только для функции Се18сго1ПпГо) 81Е_А1_1_ Объединение 81Е.РА6Е | 81Е.РО8 | 81Р_КА1\16Е | 81Е_ТИАСКРО8 81Е_О18АВ1_Е1\Ю8СкО1_1_ Это значение используется только при установке параметров полосы прокрутки. Если новые параметры делают полосу прокрутки ненуж- ной, то вместо ее удаления система делает ее недоступной Код вашей программы отвечает за следующие аспекты: □ инициализацию диапазона полосы прокрутки; □ задание размера страницы для «пропорциональной» полосы прокрутки; □ обработку сообщения полосы прокрутки; □ обновление положения движка. Сообщения полос прокрутки ХУшс1спу8 посылает оконной процедуре синхронные сообщения ЩМ_У5СК0И И УУМ_Н5СКО1_1_, когда пользователь щелкает мышью на различных зонах вертикаль- ной или горизонтальной полосы прокрутки либо перемещает движок. Когда оконная процедура получает эти сообщения, параметр доРагат содержит в своем младшем слове некоторый код, по которому можно узнать, какое событие произошло. Возможным значениям кода соответствуют определенные идентифи- каторы, начинающиеся с префикса 5В_. На рис. 2.36 показано, какие коды сообщений вырабатывает АУтйомга при тех или иных действиях пользователя. С каждым действием мыши связаны как ми- нимум два сообщения. Одно сообщение создается при нажатии кнопки мыши, а второе — когда пользователь отпускает ее. Оконная процедура приложения мо- жет получить множество сообщений с кодами 5В_ПМЕ11Р и 5В_РА6Е11Р, если кнопка мыши остается нажатой в соответствующей позиции полосы прокрутки. Сообще- ние с кодом 5В_ЕМ05СК0П показывает, что кнопка мыши отпущена. Как правило, сообщения 5В_ЕМ05СК0П можно игнорировать. При перемещении движка (ЛитЬ) мышью система вырабатывает серию сооб- щений с кодом $В_ТН11МВТКАСК. Если младшее слово параметра доРагат имеет зна- чение $В_ТН11МВТРАСК или $В_ТН11МВРО5ШОМ, то старшее слово параметра мРагат определяет текущее положение полосы прокрутки. Во всех остальных случаях старшее слово параметра мРагат можно не учитывать. Также можно игнориро- вать параметр 1Рагат, который обычно используется для полос прокрутки, созда- ваемых в окнах диалога.
Н 8сго11 Ьагх Т[о]х| Нажата: 8В.ЫМЕ11Р Отпущена: 8В_ЕМО8СКО1_1_ Нажата: 8В_РА6Е11Р Отпущена: 8В_ЕЫ08СР01_1_ Нажата: 8В_ТНиМВТНАСК Отпущена: 8В_ТН0МВРО81Т1ОМ Нажата: 8В_РА6Е00\Л/Ы Отпущена: 8В_ЕМО8СРО1_1_ Нажата: 8В_ЫМЕ00™ Отпущена: 8В_ЕЫ08СРЮ1_1_ Рис. 2.36. Значения младшего слова параметра \л/Рагат для сообщений полосы прокрутки Получив сообщение от полосы прокрутки, оконная процедура должна его обра- ботать. Для этого сначала определяется приращение текущей позиции: у1пс для вер- тикальной полосы или х1пс для горизонтальной полосы. Приращение выражено в «единицах данных». Оно может быть как положительным, так и отрицательным. Затем для прокрутки содержимого окна вызывается функция 5сго11М1 пПоуу: В001 8сго11Шпс1ом( НШО Шпс1, 1пб ХАгтюыпб, 1пб ХАгтюипб, С0И8Т ВЕСТ* 1рКесТ, СОИЗТ КЕСТ* 1рС11рКесТ // дескриптор окна // горизонтальный скроллинг // вертикальный скроллинг // указатель на прямоугольник клиентской области // указатель на прямоугольник отсечения Параметры ХАтоигй и УАтоигй задают величину прокрутки в пикселах. Пара- метры [рКес! и 1рСПрКес1: чаще всего получают значение МОП, что означает про- крутку для всей клиентской области. Таким образом, обработка сообщения ММ_\/$СК01_1_ сопровождается следующим вызовом: 8сго111л11пс1о\л/(И1л1пс1, 0, -у8бер * у!пс, ШЕЕ, ШЕЕ); Обработка сообщения ММ_Н$СКО1_1_ реализуется следующим вызовом: Зсго!Пл/лпс1о\л/(Н1л/пс1 , -схСбаг * х!пс, О, ШЕЕ, ШЕЕ); В указанных вызовах величина у51:ер задает шаг между строками в пикселах, а величина схСНаг равна средней ширине символов в пикселах. Знак «минус» пе- ред приращением отражает тот факт, что фактическое перемещение документа в окне противоположно направлению прокрутки с точки зрения пользователя. После прокрутки содержимого окна вы должны позаботиться об обновлении позиции движка на полосе прокрутки. Например, для вертикальной полосы мо- гут быть использованы следующие инструкции: 81.пРоз += у!пс; 8еб8сго1 11пТо(Ыпс1, 8ВДОТ, &з1, ТЕВЕ); где $1 — переменная типа 5СК0ШМЕ0.
Заметим, что в результате выполнения функции ЗсгоШЛй пс1о\л/ часть клиентс- кой области освобождается от информации. Для того чтобы обеспечить вывод в освободившуюся зону новой порции текстового документа, приложение обыч- но вызывает инструкции 1пуа11(ШеКесШ1л1пс1, М1Л_1_, ТРОЕ); СрсШеШ пбо\л/( Шпс1); Напомним, что функция IIрс!а1:е\/71 пдом вызывает передачу сообщения ММ_РА1МТ непосредственно оконной процедуре. Это важно, если вы хотите обеспечить не- медленную реакцию приложения на действия пользователя, когда тот быстро пе- ремещает движок полосы прокрутки (сообщение $В_ТН11МВТКАСК). Дело в том, что при обычном порядке обработки функция 1пуа11’с1а1:еКес1: вызывает постановку со- общения ММ_РА1МТ в очередь приложения, а там это сообщение обрабатывается с самым низким приоритетом. Наконец, в блоке обработки сообщения ММ_РА1МТ вы должны предусмотреть код для вывода той части текстового документа, которая определяется текущим положением вертикального и горизонтального движков. Пример работы с полосами прокрутки приводится в листинге 2.2. Примеры программ Просмотрщик текстовых файлов Приложение ТехВЛемег предназначено для чтения и вывода на экран текстового файла. В приложении демонстрируются: использование полос прокрутки, вывод текста при помощи функций ТехЮи!: и ТаЬЬесИехЮи1, а также использование реги- она отсечения. В этом примере также показано, что использование идей ООП вряд ли ухуд- шает качество программного кода, скорее — наоборот. С этой целью разработан класс КОоситеп!: для решения тех подзадач, которые связаны с загрузкой, хране- нием и выводом документа в клиентскую область окна с учетом его прокрутки. Интерфейс класса содержится в файле КОоситегй.Н, а его реализация — в файле КОоситегй.срр. Программа представляет собой многофайловый проект, приведенный в лис- тинге 2.21. Листинг 2.2. Проект ТехВАемег ////////////////////////////////////////////////////////////////////// // КОоситепР.И #1пс1ис1е <\л/1 пс1о\л/8. И> #1пс1ис1е <уесДог> #1пс1ис1е <8^г1пд> из1пд патезрасе зЦс!; с!азз КОоситегД { 1 Как всегда, не забудьте скопировать в каталог проекта файлы КУУпс1.Н и КУУпс1.срр (из листинга 1.2 в главе 1) и включить их в состав проекта при помощи окна Рго]ес1: УУогкзрасе интегрированной среды.
риЫ 1с: В001 Ореп(сопзР сбап* С11е); уо1с1 1п1Р1а11 ге(1РТЕХТМЕТВ1С Рт); уо1с1 8спо118еРР1пдз(НШ0 Ыпс1, 1 пр \л/1 сНаН, 1 пр Ие1дИР) : уо1с1 НрбаРеНзспо! 1 (НШО Ыпс1, 1 пр х!пс); уо1с1 Нрс1аре\/8спо11 (НШО Ыпс1, 1 пр у1пс); уо1с1 РиРТехР (НШО Ыпс1, НОС Нс1с): 1 пр схСЬап; 1 пр у8Рер; 1 пр ПпеЬепМах; 8СК0ШМР0 781: 1 пр уепРКапде; 8СК0ШМР0 081; 1 пр ЬопгКапде; рг1уаРе: уесРоп<зРп1пд> Ипез; }; // средняя ширина символа // высота (шаг) строки // максимальная длина строки // вертикальный скроллинг // диапазон вертикальной полосы прокрутки // горизонтальный скроллинг // диапазон горизонтальной полосы прокрутки // вектор для хранения строк документа ////////////////////////////////////////////////////////////////////// // КОосытепР.срр #1пс1ис1е <\л/1 пс1о\л/8. Н> #1пс1ис1е <СзРпеат> #1пс1ис1е "КОосытепР.Н" ВООЕ КОосытепР::0реп(соп8Р сбап* Р11е) { ИзРпеат Р1пр(Р11е); сбап ЬиС[200]; 1Р( !Р1пр.доос1()) { МеззадеВох(МОП, "Не найден входной файл", "Еппоп", МВ_ОК); пеРипп ЕАЕ8Е; } // Прочитать файл, сохранив информацию в векторе строк И’пез мЬ11е( !Р1пр.еоРО) { Р1пр.деР11пе(ЬиР, 199); ЬиР[199] = 0; 11пез.ризЬ_Ьаск(зРп1пд(ЬиР)); } // Вычислить максимальную длину строки ПпеЬепМах = 0; Рог (1 пр 1 =0; 1 < 11пез.з1ге(); ++1) { 1 пр ИпеЬеп = 11пез[1 ].з1ге(): // Корректировка, если строка содержит символы табуляции 1 пр ПаЬРоз = 0; \л/М1е (1) { ПаЬРоз = 11пез[1] ,Р1псК ' \Р', ПаЬРоз); И (ПаЬРоз != -1) { ИпеЬеп += 8; 1ТаЬРоз++; } е!зе Ьгеак; } И (ИпеЬеп > ПпеЬепМах) ПпеЬепМах = ИпеЬеп; } пеРипп ТВОЕ; }
Листинг 2.2 {продолжение} схСИаг = Рт->РтАуеСВагШ с1РГ|; у8Рер = Рт->РшНе1дИР + Рт->РтЕхРегпа1Ееас11пд; 781 . пМ1п = 781 .пРо8 = 0; Г181 . пМ1 п = Г181 . пРо8 = 0; } уо1с1 КОоситепР: :8сго118еШпд8(ШМ0 Ыпс1, 1пР ШРЕ 1пР Ре1дИР) { // Вертикальный скроллинг 781,сЬ81хе = 81хеоГ(У81); У81.РМа8к = 81Е_РАМ8Е | 81Е_РАСЕ | 81Е_Р08; 781. пРаде = Ре1дИР / у8Рер - 1; узъпМах = Ипез.зыеО - 1; 1Г (781.пРаде > узъпМах) 781 . Г1Р08 = 781 . пМ1 П : уегРРапде = УзЪпМах - У81.пМ1п + 1; 8еР8сго111гтГо(Г|\л/пс1, 8В_\/ЕРТ, &781, ТРЕЕ); // Горизонтальный скроллинг П81 ,сЬ81хе = 81хеоР(8СР0ШМЕ0); Г|8П .РМазк = 81Е_РАМ8Е | 81Е_РАСЕ | 81Е_Р08; Г181.пРаде = мШИ/схСИаг - 2; П81. пМах = 11пеЕепМах; 1Г (Г|81.пРаде > (ы’.пМах) П81.пРо8 = П81.пМ1п; ВопхРапде = (ы’.пМах - Г|81 .пМ1п + 1; 8еР8сго111гтГо(Г|\л/пс1, 8В_Н0Р2, &Г|81, ТРЕЕ); } уо1с1 КОоситепР: :Ррс1аРе\/8СГ011 (НШО Ыпс1, 1 пр у1пс) { // ограничение на положительное приращение у!пс = т1п(у1пс, уегРРапде - (1пР)у81 .пРаде - узъпРоз); // ограничение на отрицательное приращение у!пс = тах(у1пс, У8П .пМпп - узъпРоз); 1Р (у1пс) { 8сго11Шпс1ом(Ыпс1, 0, -у8Рер * у!пс, МЫЕЕ, ШЕЕ); 781.пРо8 += у!пс; 8еР8сго111пРо(Ыпс1, 8В_УЕРТ, &У8П , ТРЕЕ); 1пуа11 ВаРеРесР(Ыпс1, ИЕ1ЕЕ, ТРЕЕ); Ррс1аРеШ пс!о\л/ (Ыпс1); } } уо1с1 КОоситепР:: Е1рбаРеЕ18СГ011 (НЕМО Ыпс1, 1 пр х!пс) { // ограничение на положительное приращение х!пс = гп1п(х1пс, ВопхРапде - (1 пр)Изп.пРаде - ИзъпРоз); // ограничение на отрицательное приращение х!пс = тах(х1пс, Г|8п .пМпп - Г18Л .пРоз): 1Р (х1пс) { 8сго11Е1 пс!о\л/(Г|\л/псЕ -схСВаг * х!пс, 0, МЕ1ЕЕ, МЕ1ЕЕ); Г18Л. пРо8 += х1пс; 8еР8сго111пГо(И\л/пс1, 8В_Н0Р2, &Г181, ТРЕ1Е); 1пуа11 ВаРеРесР(Ыпс1, МЕ1ЕЕ, ТРЕЕ); НрбаРеЕп пс1о\л/ (Вшс1); }
} уо1б КОоситегЕ:: РиТТехТ(НШО Ыпб, НОС Нс1с) { КЕСТ гесТ; СеЮ 1 епТКесТ (Ыпс1, йгесТ); гесТЛеТТ += схСНаг; гесТ.г1д1тЕ -= схСНаг; НК6М НКдп = СгеаТеКесТКдп1пс11гесТ(&гесТ); 8е1 есТС11 рКдп(Нс1с, НКдп); 1пТ х = схСНаг * (Нз1 .пМ1п - 1181. пРоз + 1); Ш у = уЗТер; 1пТ агтюипШпез = 11пез . з1ге(); 1пТ 1 Вед = У81.пРо8; 1пТ 1 Епс1 = (уз1 ,пРо8+781 .пРаде < атоипШпез)? уз1 .пРоз+уз! .пРаде : атоипШпез; Тог (1гЕ 1 = 1 Вед; 1 < 1 Епс1; ++1) { Ш ПаЬРоз = ИпезШ .Т1псК 'И'); 1Т (-1 == 1ТаЬРоз) ТехТОиТ(Нбс, х, у, ИпезШ .с_зТг(), ИпезШ .зыеО); е!зе ТаЫэесГГехТОиТ(Нбс, х, у, 11пез[1 ] ,с_зТг(), ИпезИ] .зыеО, 0, 0, х); у += уЗТер; } $е!ес1С11 рКдп(Мс, М1Л_1_); } ////////////////////////////////////////////////////////////////////// // ТехШемег.срр #1пс1ис1е <\л/1 пс1о\л/8. Н> #1пс1ис1е "КИпО" #1пс1ис1е "КОоситегЕ.Н" #с1еТ1пе Г1ЕЕ_МАМЕ "0:\\Ргодгат Т11е8\\М1сго8(Ж \/18иа1 8Тис11о\\\/С98\\МЕС\\8КС\\КЕА0МЕ .ТХТ" ЕВЕЗНЕТ САЕЕВАСК ШНРгос(НШО, Н1МТ, АРАРАМ, ЕРАКАМ); КОоситегЕ бос; //========================================== 1гЕ ШМАР1 1л11пМа1п(Н1М8ТАМСЕ ШзТапсе, Н1МЗТАМСЕ НРгеуТпзТапсе, ЕРЗТК 1рСгпбЕ1пе, Ш пСтсЕНом) { М86 тзд; 1Т (!бос.Ореп(ЕIЕЕ_МАМЕ)) геТигп 0; К1л1пс1 та1п1л1пб("ТехТ \Ле\л/ег", НТпзТапсе, пСтсВНом, МпбРгос, МЕ1ЕЕ, СМ-НЗЕОЕЕАНЕТ, 0, СЕМЕЗЕОЕРАНЕТ, 0, С8_НКЕ0КАИ | С8_7КЕ0КАИ, И8_07ЕКЕАРРЕ0И1М00И | 1л/3_73СК0ЕЕ | 1л/3_НЗСК0ЕЕ); мМ1е (СеТМеззадеС&тзд, ИНЕЕ, 0, 0)) { Тгап81аТеМе88аде(&т8д); ОтзраТсНМеззадеС&тзд); } геТигп тзд.мРагат; } //====================================================================
Листинг 2.2 {продолжение} 1КЕ81ЛТ САЕЕВАСК ШбРгос(НШО Шпб, 011\1Т иМзд, ПРАВАМ мРагат, 1РАКАМ 1Рагат) { НОС НОС; РА1МТ8ТР0СТ рз; ТЕХТМЕТР1С Ш 1пЬ схС11епЬ=0, суС11епЬ=0; зЬаЫс 1пЬ х!пс, у1пс; змПсИ (иМзд) { сазе Ш-СРЕАТЕ: НОС = СеЬОССИШб); СеЬТехЬМеЬг1сз(И0С, &Ьт); бос. 1тЫа11хе(&Ьт); Ре1 еазеОС(ИШб, НОС); Ьгеак; сазе Ш_812Е: НОС = СеЬОС(ИШб); схСИепЬ = Е0ШР0(1 Рагат); суСИепЬ = НШ0Р0(1 Рагат); 1Г (схСИепЬ > 0) бос.Зсго! 18еЬЫпдз(Ыл1пб, схСИепЬ, суСИепЬ); Ре1 еазеОС(Н1л1пс1, НОС); Ьгеак; сазе 1л/М_У8СК0ЕЕ: зтЬсИ(ЕОШРО(мРагат)) { сазе 8В-ЫМЕ0Р: у1пс = -1; Ьгеак; сазе 8В-ЫМЕ00Ш: у1пс = 1; Ьгеак; сазе 8В_РА6Е0Р: у1пс = -(1пЬ)бос.уз1 .пРаде; Ьгеак; сазе 8В_РАСЕ00Ш: у1пс = (1пЬ)6ос.уз1 .пРаде; Ьгеак; сазе ЗВ-ТНОМВТЕАСК: у1пс = НШОРОЫРагат) - бос.уз1.пРоз; Ьгеак; беГаиИ: у1пс = 0; } бос.брбаЬеХ/зсго! 1 (Ыл1пб, у1пс); Ьгеак; сазе 1л/М_Н8СК0ЕЕ: зтЬсИ(ЕОШРО(мРагат)) { сазе 8В-ЫМЕЕЕЕТ: х1пс = -1; Ьгеак; сазе 8В-ЫМЕР16НТ: х1пс = 1; Ьгеак; сазе 8В-РАЗЕЕЕЕТ: х1пс = -0.8 * (1пЬ)бос.Из1.пРаде; Ьгеак; сазе 8В_РАСЕР1СНТ: х1пс = 0.8 * (1пЬ)бос.Из1.пРаде; Ьгеак; сазе 5В-ТН6МВТЕАСК: х1пс = НШОРОЫРагат) - бос.Нз1 .пРоз; Ьгеак; беГаиИ: х1пс = 0; }
бос.СрсШеНзсго!! (Шпс1, х1пс); Ьгеак; сазе 1л/М_РА1МТ: НОС = Вед1 пРа1 пЬ(Шпс1, &рз); бос.РиЬТехЬ(Шпс1, НОС); Епс1Ра1пШ1л1пс1, &рз); Ьгеак; сазе Ш_0Е8ТР0У: РозЩпЬМеззаде(О); Ьгеак; ЬеТаиИ: геЬигп ОеТШпЬомРгос(Шпс1, иМзд, мРагат, 1Рагат); } геЬигп 0; } ////////////////////////////////////////////////////////////////////// Обратите внимание на следующие особенности реализации класса КОоситепГ: □ Для хранения текстового документа используется член класса Ппез, который объявлен как вектор строк типа $Гппд. □ В методе Ореп заданный входной файл читается по строкам. Предполагается, что строки разделяются символом \п. Каждая прочитанная строка добавляет- ся в конец вектора Ппез. Затем в цикле Гог вычисляется максимальная длина строки ПпеЬепМах с учетом возможного присутствия символов табуляции. При- сутствие символа табуляции в строке Ипе$[1] проверяется методом Г1 пс1 класса зГлпд, который возвращает позицию обнаруженного символа \Г или значение -1, если символ табуляции не обнаружен. В методе ЗсгоИЗеВп’пдз величина 11’пе1_епМах используется для установки максимального значения диапазона го- ризонтальной полосы прокрутки. □ В методе Тш’ПаИне вычисляются размеры «единиц данных» для скроллинга. Ве- личина схСНаг задает шаг изменения в горизонтальном измерении, а величина у5Гер — шаг изменения в вертикальном измерении. Для вычислений исполь- зуются метрики текста, полученные в параметре Гт. □ Метод ЗсгоИЗеГВпдз предназначен для установки параметров вертикальной и горизонтальной полос прокрутки при заданных ширине хллд'ЬН и высоте НетдНГ клиентской области окна. □ Метод 11рс1а1:е\/$сго11 выполняет итоговую обработку всех сообщений от верти- кальной полосы прокрутки. В нем же производятся прокрутка содержимого окна при помощи функции 8сго11\ЛНпс1о\л/, обновление положения движка с по- мощью 5еГ5сгоИ1пГо и обновление клиентской области при помощи функции II рдаГе\Л/1 пдо\л/. Более подробные комментарии по этому этапу можно найти в разделе «Полосы прокрутки и вывод текста». Обратите особое внимание на первые две инструкции в теле функции. Они предваряются комментариями «ограничение на положительное приращение» и «ограничение на отрицательное приращение». Первая инструкция обеспе-
чивает прекращение прокрутки при продвижении документа к его концу, как только позиция движка достигнет значения 7еП:Капде - (1пГ)7з1.пРаде. В этом положении движка последняя страница документа полностью видна в ок- не и дальнейшая прокрутка теряет смысл. Заметим, что преобразование к типу 1 п± для поля пРаде необходимо для корректного выполнения макро- са пип. Вторая инструкция обеспечивает прекращение прокрутки при продвижении документа к его началу, как только позиция движка достигнет значения 7з1.пМ1П. □ Метод IIрс1а1:еНзсгоИ производит итоговую обработку всех сообщений от гори- зонтальной полосы прокрутки. Реализация метода аналогична реализации ме- тода ОрдаЕеХ/зсгоП. □ Метод РиНех!: предназначен для вывода фрагмента текста из документа в кли- ентскую область окна. Для создания полей слева и справа от видимого в окне текста (в данном при- мере шириной в один символ) используется регион отсечения НКдп. Он создается вызовом функции Сгеа1еКес1:Кдп1пс11’гес1:, причем в качестве аргумента передается адрес прямоугольника гее!, который в вертикальном измерении совпадает с пря- моугольником клиентской области, а в горизонтальном измерении уменьшен слева и справа на величину схСНаг. Созданный регион выбирается в контекст устрой- ства функцией 5е1ес1С11рКдп. Фрагмент текста выводится в цикле Гог в соответствии с текущим положением движка 781*. п Роз на вертикальной полосе прокрутки и текущим положением движ- ка НзъпРоз на горизонтальной полосе прокрутки. Если в текущей строке Ипез[1] нет символов табуляции, то вызывается функция ТехЮиГ. В противном случае ис- пользуется функция ТаЬЬесПехЮиЕ По умолчанию в контексте устройства установлен режим отображения ММ_ТЕХТ, поэтому начало координат совмещено с левым верхним углом клиентской области. Координата у для вывода каждой следующей строки вычисляется с учетом сме- щения уЗГер. Если значение НзЕпРоз совпадает со значением Нз1.пМ1п, то координата х, вычисляемая по формуле схСНаг х (Нз1.пМ1п - НзЕпРоз + 1), имеет значение схСНаг. В этом случае строка выводится в окно, начиная с первого символа. А поскольку в контексте устройства выбран регион отсечения с дескриптором НКдп, то первый видимый символ в окне нельзя выводить левее, чем х = схСНаг. В случае НзЕпРоз > 0 координата х будет принимать сначала нулевое, а потом отрицательные значения, абсолютная величина которых возрастает по мере про- крутки документа по горизонтали вправо. То есть начало вывода строки прихо- дится на точку экрана, лежащую слева за пределами окна приложения. Разуме- ется, за счет отсечения, за которым следит АУтс1о\78, пользователь будет видеть только ту часть строки, которая попадает в клиентскую область окна. А еще точ- нее, только ту часть строки, которая попадает в регион отсечения. Перед выходом из функции инструкция 5е1ес1:С11’рКдп(Нс1с, МОП) удаляет из кон- текста устройства выбранный ранее регион отсечения.
Теперь следует рассмотреть особенности кода, приведенного в файле ТехТйемег.срр: □ Для работы с документом объявлен глобальный объект дос класса КОосигпеп!. □ В функции УУ1 пМа1 п вызывается метод Ореп объекта дос, выполняющий загруз- ку документа из файла, имя которого задается макросом Е11_Е_МАМЕ. В данном случае используется файл КЕАОМЕ.ТХТ, входящий в состав МхсгозоЙ Ухзпа! С++ 6.01. Конечно, в этом макросе вы можете определить путь к любому другому текстовому файлу. □ Обратите внимание на флаги М$_\/$СКО1_1_ | М$_Н$СКО1_1_, переданные конструк- тору класса К\Л/пд в составе параметра \л/1пдо\л/51:у1е. Именно они обеспечивают появление у главного окна приложения вертикальной и горизонтальной по- лос прокрутки. □ В оконной процедуре \Л/пдРгос метод ТтдаНне объекта дос вызывается при об- работке сообщения ММ_СКЕАТЕ, а метод 5сго115е1±'пд8 — при обработке сообще- ния \Л/М_$12Е. □ Обработка сообщения ММ_\/$СКО1_1_ завершается вызовом метода дос.11рда1е- \/8СГо11(Н\Л/пдл у1пс). А обработка сообщения ММ_Н$СКО1_1_ завершается вызовом метода дос.Орда1еН8Сго11(Н\Л/пдл х1пс). □ Сам вывод текста происходит в блоке обработки сообщения ММ_РА1МТ при по- мощи метода дос.Ри1Тех1:(Н\Л/пдл НОС). Выполните компиляцию этого проекта и поэкспериментируйте с изменения- ми размеров окна приложения и прокруткой текста в окне. Вывод временной диаграммы напряжения переменного электрического тока Приложение 11_Т_01’адгат демонстрирует использование страничной системы ко- ординат, создание логических перьев, рисование различных линий, создание ло- гических шрифтов, вывод текста с применением разных шрифтов. В программе решается задача формирования и вывода на экран временной диаграммы напряжения переменного электрического тока. Действующее значе- ние напряжения — 220 В, частота — 50 Гц. Диаграмма строится для промежутка времени, в котором происходят ровно два периода колебаний. На рис. 2.37 показано окно приложения 11_Т_01’адгат после его запуска. К сожалению, черно-белый рисунок трансформирует различные цвета в оттенки серого и поэтому не передает всей красоты созданной диаграммы. Например, ли- нии координатной сетки на самом деле имеют зеленоватый цвет, а синусоида гра- фика — малиновый цвет. Если вы хотите все-таки насладиться этой красотой, то придется ввести в компьютер прилагаемый ниже текст программы, откомпили- ровать и запустить ее. Период синусоидальных колебаний Т связан с их частотой / соотношением Т= 1//, поэтому для частоты 50 Гц период составляет 0,02 с, или 20 мс. Также по- лезно вспомнить, что действующее значение напряжения переменного тока I/ свя- зано с его амплитудным значением 1/т соотношением 1} = 0,707 • 1/т. Из этого следу- ет, что амплитудное значение напряжения равно 311,17 В. 1 На вашем компьютере путь к этому файлу может быть другим.
Для улучшения внешнего вида графика используются не только разные цвета линий, но и разная толщина. Линии координатной сетки рисуются толщиной 1 пиксел, оси Хи У — толщиной 3 пиксела, а сама синусоида — толщиной 5 пиксе- лов. Прежде чем привести листинг программы, сделаем некоторые пояснения по поводу выбора и настройки координатной системы. Размышления над сутью ре- шаемой задачи приводят нас к убеждению, что здесь можно обойтись без аффин- ных преобразований и, следовательно, нас вполне устроит страничная система координат. Физическую область вывода (егеюрогб) стоит привязать к клиентской области окна, размеры которой можно получить при помощи функции беКИегйКес!:. Бла- годаря этому рисунок будет автоматически масштабироваться при изменении размеров основного окна приложения. Теперь подумаем, как нам организовать логическую область вывода (шпбою). Казалось бы, здесь удобно использовать логические единицы, присущие данной проблемной области, то есть по оси абсцисс — миллисекунды, а по оси ординат — вольты. Но если выбрать эти логические единицы, то возникнет очень неприят- ная проблема. Фактически, мы потеряем контроль над толщиной пера, использу- емого для прорисовки линий. Это раз. Кроме того, толщина пера окажется разной при рисовании по горизонтали и по вертикали, так как она будет определяться масштабными коэффициентами тх и ту1. Это два. Причиной перечисленных неприятностей являются особенности реализа- ции в АУт32 АР1 объекта логического пера. Дело в том, что в вызове функции Сгеа1:еРеп(реп51:у1е, пМ’сКН, сгСо1ог) параметр пУУтсИН задает толщину пера имен- но в логических единицах. Исключением является нулевое значение пМтсЙН, тх = х\/Е / хУУЕ, ту = у\/Е / уУУЕ, где х\/Е, у\/Е — экстенты физической области вывода, а хУУЕ, уУУЕ — экстенты логической области вывода.
определяющее перо толщиной 1 пиксел независимо от установленных коор- динатных преобразований. Например, если для физической области вывода размеры экстентов равны хУЕ = 952, уУЕ = 7021, а для логической области будут выбраны значения экстентов х\Л/Е = 60 (мс), у\Л/Е = 1000 (В), то логическая единица по горизонтали будет соот- ветствовать тх = 952 / 60 = 15,86 или, округленно, 16 пикселам. Логическая еди- ница по вертикали будет соответствовать ту = 702 / 1000 = 0,72 или, округленно, одному пикселу. Чтобы все-таки нарисовать график так, как это показано на рис. 2.37, будем придерживаться следующего плана действий. Установим режим отображения ММ.150ТК0Р1С, а значения экстентов для логической области вывода сделаем совпа- дающими со значениями экстентов для физической области вывода. В этом слу- чае будет получен полный контроль над толщиной пера. Проблему же масштаби- рования придется решать «вручную», используя соответствующие масштабные коэффициенты: пР1хРегУоН = у7Е / 1000.0; пР1хРегМз = хУЕ / 60.0; Код проекта приведен в листинге 2.3. Листинг 2.3. Проект 11_Т_О!адгат2 ////////////////////////////////////////////////////////////////////// // 0_Т_01адгат.срр #1пс1ис1е <\л/1 псЕмз.Н> #1пс1ис1е <таРН.Н> #1пс1ис1е "К1л1пс1.6" ЕКЕ86ЕТ САЕЕВАСК 1л1пс1Ргос(НШО, ЮТ, АРАРАМ, ЕРАкАМ); уо1с1 0гаО1 адгат(НШ0, НОС); #с1еГ1пе Р1 3.14159265 //========================^ 1пР ШИАР1 ШпМа1п(Н1М8ТАМСЕ ЩпзТапсе, Н1М5ТАМСЕ НРгтМпзТапсе, ЕРЗТк 1рСтс1Е1пе, Ш пСтсЕНом) { М8С тзд; К1л1пс1 та1п1л1пс1("Временная диаграмма напряжения переменного электрического тока", МпзТапсе, пСтсЕНом, 1л1пс1Ргос); мМОе (СеРМеззадеС&тзд, МНЕЕ, 0, 0)) { ТгапзШеМеззадеС&тзд); ОтзраТсНМеззадеС&тзд); } геРигп тзд.мРагат; } //==================================================================== продолжение & 1 Эти значения являются шириной и высотой клиентской области окна, созданного с размерами по умолчанию. 2 Не забудьте добавить к проекту файлы КУУпсЕН и КУУпсЕсрр (см. листинг Е2 в главе 1).
Листинг 2.3 {продолжение} 1КЕ81ЛТ САЕЕВАСК ШЬРгос(НШО Шпс1, 01ИТ иМзд, АРАРАМ мРагат, 1РАКАМ 1Рагат) { НОС НОС; РА1МТ8ТВНСТ рз; з\л/1ТсН (иМзд) { сазе Ш-РА1ИТ: НОС = ВедапРаа ггЕ(Н1л1пс1, &рз); 0гаО1адгат(Н1л1пс1, НОС); Епс1Ра1пШ1л1пс1, &рз); Ьгеак; сазе Ш_ВЕ8ТВ0У: РозТОьп ТМеззаде(О); Ьгеак; с1еТаи!Т: геЬигп ВеТШ пЬомРгос(Ыл1пс1, иМзд, мРагат, 1Рагат); } геЬигп 0; } /============================= уо1с1 0гам01адгат(НШ0 Ишс1, НОС Ис1с) { ВЕСТ гесТ; 6еТС11епТВесТ(Ишс1, йгесТ); сопзТ 1 пТ х\/Е = гесТ.п’дИТ - гесТ.1еТТ сопзТ 1 пТ у\/Е = гесТ.ЬоТТот - гесТ.Тор сопзТ 1 пТ х1л/Е = х\/Е; сопзТ 1 пТ у!л/Е = у\/Е; ЬоиЫе пР1хРег\/о1Т = у\/Е / 1000.0; ЬоиЫе пР1хРегМз = х\/Е /60.0; // п Р1хе1з Рег МПИзес 8еТМарМос1е (Нс1с, ММ_180ТВ0Р1С); 8еШпЬомЕхТЕх (Ис1с, х!л!Е, у1л/Е, ИНЕЕ); 8еТ\/1емрогТЕхТЕх (Ис1с, х\/Е, -у\/Е, ИНЕЕ); 8еТ\/1емрогТ0гдЕх (Ис1с, 10*пР1хРегМз, у\/Е/2, ИНЕЕ); сопзТ 1пТ ТМ1п = 0; // тз сопзТ 1пТ ТМах =40; // тз сопзТ 1пТ иМ1п = -400; // V сопзТ 1пТ иМах = 400; // V сопзТ 1 пТ Т6г1с18Тер = 5; сопзТ 1 пТ и6г1с18Тер = 100; 1пТ х, у; сИаг* хМагкЕО = { "0", "5", "10", "15", "20", "25", "30", "35", "40"}; сИаг* уМагкЕ] = { "-400","-300","-200","-100", "0", "100", "200", "300", "400"}; // Сетка НРЕИ ИРепО = СгеаТеРеп(Р8_80Ы0, 1, В6В(0, 160, 0)); НРЕИ ИО1с1Реп = (НРЕМ)8е1есТОЬ0есТ(ИЬс, ИРепО); 1пТ и = иМ1п;
1пЕ хМ1п = ЕМ1п * пР1хРегМз; 1пЕ хМах = ЕМах * пР1хРегМз; Тог (1 п1а 1 =0; 1 <9; ++1) { у = и * пР1хРег\/о1Е; МоуеТоЕх(Вс1с, хМ1п, у, МОЕЕ); ЫпеТоШс, хМах, у); ТехЕОиШОс, хМ1п-40, у+8, уМагк[1], Ш1еп(уМагк[1 ])); и += ибг1Шер; } 1гЕ 1: = ЕМ1 п; 1пЕ уМ1п = иМ1п * пР1 хРег\/о1 Ы; 1пЕ уМах = иМах * пР1 хРег\/о1 Ы; Тог (1 =0; 1 <9; ++1) { х=1* пР1хРегМз; МоуеТоЕхШс, х, уМ1п, ШЕЕ); Е1 пеТо(Ис1с, х, уМах); ТехРСШ(1пс1с, х-6, уМ1п-10, хМагк[1], Ш1еп(хМагк[1 ])); 1: += Ш1 Шер; } // Ось "х" НРЕМ Е|Реп1 = СгеаЕеРеп(Р8_80Е10, 3, РОВ(0, 0, 0)); 8е!есШэ0есШс1с, МРеп1); МоуеТоЕхШс, 0, 0, ИЕ1ЕЕ); Е1 пеТо(Ис1с, хМах, 0); 8-Еа-Еас ЕООЕОМТ 1Т; 1Т. 1 ТШсМАпОЕатЫу = Е1ХЕО_Р1ТСН | ЕЕ_МООЕРМ; 1Т. 1 Ша11с = ТРОЕ; 1Т. 1 теадЕгЕ = Ш_В0Е0; 1Т.1ТНе1дЫ = 20; 1Т.1ГО1агШ = 0ЕЕА0ЕТ_СНАР8ЕТ; Шгсру( (ЕРЗТГк)&1 Т. 1 ТЕасеМате, "Аг1а1" ); НЕОМТ МЕопЕО = СгеаШопШсПгесШИ); НЕОМТ ПОЮЕопЕ = (НЕОМТ)Зе!есЕОЬОесЕ(Нс1с, НЕопЕО); ЗеЫТехЫСо! ог (Ис1с, РОВ(0, 0, 200)); ТехЕОиШОс, хМах+10, 10, Ч (те)", 6); // Ось "у" МоусТоЕх(Ыс, 0, уМ1п, МОЕЕ); Е1 пеТо(Ис1с, 0, уМах); ТехЕОиШсЕ, -10, уМах+30, "и (V)", 5); // График НРЕИ ВРеп2 = СгеаЕеРеп(Р8_80Е10, 5, Р6В(200, 0, 100)); 8е!есЕОЬ0есШс1с, ВРеп2); 1пЕ Шер = 1; ОоиЫе гасПапРегМз = 2 * Р1 /20; сопЧ ОоиЫе иАшрИЕ = 311.17; // уоН Е = ЕМ1п; МоуеТоЕхСМс, 0, 0, МОЕЕ); 4111е (О <= ЕМах) { и = иАшрИЕ * з1п( О * гасНапРегМз); ЫпеТо(Ыс, О * пР1хРегМз, и * пР1хРег\/о1Ы; О += Шер; } // Заголовок сВаг* Ш1е = "Диаграмма напряжения переменного электр. тока"; продолжение
Листинг 2.3 {продолжение} 1Г. 1Ша11 с = ЕА18Е; 1Т.Ше1дГД = ™_В01_0; 1Г.1ГНе1д1тЬ = 30; НЕОИТ НЕопН = Сгеа-ЕеЕогтЫпсИ гесЕ(&1 Г); 8е1 есШбесЕ(Ыс, НЕопШ; 8еЕТехЕСо1ог(Нс1с, Р6В(0, 200, 0)); ТехЕСШ(Ыс, 0, уМах + 70, Ш1е, зШеп(Ш1е)); 8е1 есЬОЬОесЕ(Ыс, Н01с1Реп); 8е1 есЬОЬОесЕ (Ыс, Н01 бЕогтЕ); } ////////////////////////////////////////////////////////////////////// Основная работа в приведенной программе сосредоточена в функции Ога\л/- 01’адгат, которая вызывается в блоке обработки сообщения ММ_РА1МТ. Обратите внимание на то, как реализована настройка страничной системы ко- ординат. В выбранной нами стратегии решения логические единицы совпадают с физическими единицами, поэтому введем новый термин — металогические еди- ницы, которые отражают проблемную область. Одна металогическая единица по горизонтали соответствует 1 мс, а одна металогическая единица по вертикали со- ответствует 1 В. Заметим, что вызов функции ЗеШемрогЕЕхЕЕх (Ибс, х\/Е, -у\/Е, ШЕЕ); реализует преобразование отражения, благодаря которому значения по оси ор- динат возрастают снизу вверх, как это принято в проблемной области. Другой вызов: ЗеШемрогДОгдЕх (ббс, 10*пР1хРегМ8, у\/Е/2, ШЕЕ); обеспечивает установку начала координат строго посередине по вертикали и с отступом на 10 металогических единиц слева по горизонтали. В программе используются металогические величины ЕМ1’п, ЕМах, иМ1’п, иМах, Еблс151:ер, и6лс151:ер, являющиеся константами. Также используются и металоги- ческие переменные и. Переход к соответствующим логическим (и совпадаю- щим с ними физическим) величинам осуществляется через коэффициенты пР1’хРегМ$, пР1хРег\/о1Л:. Для построения графика в цикле мНйе применяется кусочно-линейная апп- роксимация функции 51*п(х). После очередного приращения аргумента вычисля- ется значение функции, по которому вычисляется текущее значение и. Затем при помощи функции ИпеТо проводится отрезок линии, соединяющий предыдущее и текущее значения и. Так как функция $1 п требует в качестве аргумента значение угла в радианах, то используется понятие угловой частоты со = 2- я- /= 2- я/ Г для вычисления соответствующего масштабного коэффициента: гасНапРегМз = 2 * Р1 /20; После компиляции и запуска на выполнение можно поэкспериментировать с окном программы, изменяя его размеры, чтобы увидеть, как будет масштабиро- ваться изображение.
3 601. Палитры, растры, метафайлы В третьей главе будет продолжено рассмотрение графического интерфейса уст- ройства — СВ1. На очереди рассмотрение таких графических объектов, как палит- ры, битовые образы, или растры, и метафайлы. Даже если вы не планируете исполь- зовать палитры в создаваемых приложениях, беглое знакомство с ними может оказаться полезным, так как понятие цветовых таблиц используется в форматах НТВ-растров и НТВ-секций. Палитры Если устройство вывода поддерживает полный диапазон цветов, определенных 24- битным ВСВ-значением1, то палитра вам не нужна. К сожалению, еще не вышли из употребления старые модели дисплеев, которые поддерживают только 256 цветов. Другие графические устройства, такие как принтеры и плоттеры, тоже имеют огра- ничения по количеству воспроизводимых цветов. Графические объекты, именуемые в АУшсТом^ палитрами, обеспечивают над- лежащее согласование цветов при работе приложений на разных аппаратных плат- формах. Чтобы узнать, поддерживает ли конкретное устройство работу с палит- рой, нужно вызвать функцию БеЮеукеСарз, передав значение индекса КА5ТЕКСАР5, и убедиться, что возвращаемое значение содержит флажок КС_РА1_ЕТТЕ. Для упрощения изложения в следующих разделах, посвященных палитре, под устройством вывода будет подразумеваться дисплей. Основные принципы управления палитрами Цветовая палитра представляет собой таблицу (массив) цветов, которые способ- но воспроизвести устройство. Вход в таблицу для доступа к каждому цвету осу- ществляется по индексу. В АУш32 СВ1 используется несколько типов палитр. Системная палитра (зузШп ра1еЫе) определяет все цвета, которые могут быть одновременно отображены устройством. Диапазон цветов зависит от аппаратной 1 24-битное значение позволяет поддерживать 16 777 216 цветов.
палитры, реализуемой видеоадаптером, и от настроек экрана. В любом случае сис- темная палитра является неким «программным представителем» аппаратной палит- ры. Приложение не может непосредственно модифицировать системную палитру. Системная палитра обычно содержит 256 элементов (входов), из которых 20 заре- зервировано для так называемых статических цветов, которые никогда не изменя- ются. Эти цвета используются всеми приложениями для вывода меню, кнопок, фона окна, текстовых надписей и других элементов стандартного интерфейса. Логическая палитра (^0&са1ра1еЫе) — это палитра уровня приложения. Она со- здается приложением и закрепляется за контекстом устройства. Логическая палитра позволяет определять и использовать цвета, которые соответствуют по- требностям приложения. После создания логической палитры с помощью функ- ции Сгеа1еРа1е1±е или Сгеа1е На Йопе Ра1е1±е она должна быть реализована вызовом функции Кеа Иге Ра1ейе. В процессе реализации АУтйомга заполняет неиспользуемые элементы в системной палитре цветами из логической палитры. Если неиспользо- ванных элементов меньше, чем нужно, то АУтйомга распределяет оставшиеся цвета из логической палитры, либо подыскивая ближайший цвет в аппаратной палитре, либо определяя подходящее смешение (ЛЛепп§) цветов, если применяются сплош- ные кисти. Для успешного применения логической палитры нужно учесть еще один ню- анс, о котором в М8^^ практически ничего не говорится. Во всех функциях, тре- бующих передачи аргумента типа СОЮ КРЕЕ, соответствующее значение должно быть определено с помощью макроса РА1_ЕТТЕ КС В, а не при помощи макроса КСВ. Если приложение явно не создает логическую палитру, то используется па- литра по умолчанию {Ае/аик ра1еие\ содержащая только те 20 цветов, которые имеются в системной палитре. В этом случае для аппроксимации цвета, отсут- ствующего в палитре, будут использоваться указанные 20 цветов. Чтобы обеспечить одновременную работу нескольких приложений, использу- ющих различные логические палитры, АУтсЕпуз предоставляет активному окну1 приоритет в установлении цветов для логической палитры. Неактивным окнам приходится пользоваться оставшимися цветами. Неактивные окна используют все незадействованные элементы в логической палитре и применяют ближайшие согласованные цвета для всех невыполненных запросов к палитре. Однако эта проблема обычно не очень существенна, поскольку в большинстве приложений используются только системные цвета. Для координации функционирования активных и неактивных окон при работе с палитрами, прежде чем предоставить фокус ввода приложению, которое исполь- зует логическую палитру, АУхпбом^з отправляет ему сообщение ММ_(Ю ЕКУМ ЕМРАЕЕТТЕ. Это сообщение дает возможность приложению еще раз реализовать палитру и вос- становить цвета, которые могли быть изменены другими приложениями, пока окно было неактивным. Сообщение ММ_РА1_ЕТТЕСНАМ6Е0 отправляется всем окнам, когда одно из прило- жений реализует свою логическую палитру. Для неактивных окон это сообщение несет информацию о том, что некоторые цвета в палитре могли быть изменены другим окном, имеющим фокус ввода. 1 Окну, имеющему фокус ввода.
Системная палитра Системная палитра является графическим объектом уровня операционной систе- мы. Ранее уже говорилось о том, что диапазон цветов, которые одновременно вос- производятся устройством, зависит как от аппаратной палитры, реализуемой ви- деоадаптером, так и от настроек экрана. Чтобы посмотреть или изменить настройки экрана, нужно щелкнуть правой кнопкой мыши на свободной части рабочего стола экрана, после чего появится всплывающее меню. В этом меню следует выполнить команду Свойства, что при- ведет к отображению диалогового окна Свойства: Экран. В окне нужно выбрать вкладку Настройка и найти на ней групповое поле Цветовая палитра. Элемент уп- равления типа СОМВОВОХ внутри этого поля позволяет выбирать количество одно- временно отображаемых цветов на экране. Если у вас достаточно современный дисплей, то доступными, скорее всего, окажутся следующие установки: □ 256 цветов; □ Н1 Со1ог (16 бит); □ Тгие Со1ог (24 или 32 бит). В 256-цветном режиме каждый пиксел представлен в кадровом буфере видео- адаптера одним байтом. Один байт позволяет закодировать до 256 разных цветов, одновременно отображаемых на экране. Точный состав цветов определяется па- литрой видеоадаптера, которая предоставляется пользовательским приложени- ям в виде системной палитры. Системная палитра представляет собой таблицу из 256 структур типа РА1_ЕТТЕЕМТКУ: буресМ збгысб { ВУТЕ реЕеб; ВУТЕ ребгееп; ВУТЕ реВ1ие; ВУТЕ реПадз; } РАЕЕТТЕЕМТВУ; Структура РА1_ЕТТЕЕМТКУ определяет цвет конкретного элемента в массиве по его компонентам КС В. Поле реЕ1ад$ используется при создании логических палитр. Приложение не имеет непосредственного доступа к системной палитре. Одна- ко оно может получить текущее состояние системной палитры при помощи функ- ции 6е15у$1етРа1е1:1:еЕп1:пе$, имеющей следующий прототип: ШИТ 6еб8у8бетРа1еНеЕпбг1е8( НОС Нс1с, // дескриптор контекста устройства Ы1МТ 1ЗЦагШпбех, // начальный вход в таблицу Ы1МТ пЕпШес, // количество входов ЕРРАЕЕТТЕЕМТРУ 1рре // указатель на массив структур РАЕЕТТЕЕМТРУ Параметр 1рре задает адрес массива, в который помещается информация о па- литре. Если этот параметр равен МОП, то функция возвращает общее количество цветов в системной палитре. Системную палитру можно условно разделить на две секции. Первая секция содержит фиксированные, или статические, цвета, а вторая — цвета, которые мо- гут изменяться приложением при помощи логической палитры. По умолчанию в системной палитре определена группа из 20 статических цве- тов. Размещение статических цветов в системной палитре показано в табл. 3.1.
Таблица 3.1. Статические цвета в системной палитре Индекс Значение КОВ Цвет Примечание 0 0x00, 0x00, 0x00 Черный В любом режиме 1 0x80, 0x00, 0x00 Темно-красный В любом режиме 2 0x00, 0x80, 0x00 Темно-зеленый В любом режиме 3 0x80, 0x80, 0x00 Темно-желтый В любом режиме 4 0x00, 0x00, 0x80 Темно-синий В любом режиме 5 0x80, 0x00, 0x80 Темно-малиновый В любом режиме 6 0x00, 0x80, 0x80 Темно-голубой В любом режиме 7 ОхСО, ОхСО, ОхСО Светло-серый В любом режиме 8 ОхСО, ОхОС, ОхСО Денежный зеленый В режиме Тгие Со1ог 9 ОхАб, ОхСА, ОхЕО Небесный В режиме Тгие Со1ог 246 ОхРР, ОхРВ, ОхРО Очень светло-серый В режиме Тгие Со1ог 247 ОхАО, ОхАО, 0хА4 Средне-серый В режиме Тгие Со1ог 248 0x80, 0x80, 0x80 Темно-серый В любом режиме 249 ОхРР, 0x00, 0x00 Красный В любом режиме 250 0x00, ОхРР, 0x00 Зеленый В любом режиме 251 ОхРР, ОхРР, 0x00 Желтый В любом режиме 252 0x00, 0x00, ОхРР Синий В любом режиме 253 ОхРР, 0x00, ОхРР Малиновый В любом режиме 254 0x00, ОхРР, ОхРР Г олубой В любом режиме 255 ОхРР, ОхРР, ОхРР Белый В любом режиме Стоит обратить внимание на необычное размещение статических цветов. Так, первая группа из десяти цветов располагается в начале таблицы, а вторая группа из десяти цветов — в конце таблицы. Это связано с тем, что наиболее употреби- тельная растровая операция ХОК часто используется для инвертирования цветов и последующего их восстановления. Поэтому важно обеспечить инвертирование цвета при помощи инвертирования значения индекса в цветовой таблице. Напри- мер, инверсия нулевого значения индекса дает значение 255 (ОхЕЕ), при этом чер- ный цвет трансформируется в белый. Позицию 1 занимает темно-красный цвет К6В(0х80, 0x00, 0x00). Если инвертировать индекс, то он переходит в 254 (ОхЕЕ), что соответствует голубому цвету. Он не совсем совпадает с цветом, дополняющим темно-красный цвет в цветовом пространстве КОВ (К6В(0х7Е, ОхЕЕ, ОхЕЕ)), но все же достаточно близок к нему. Первые 8 цветов и последние 8 цветов никогда не изменяются, независимо от цветового режима. Реализация цветов с индексами 8,9,246,247 зависит от режима. В таблице указаны КСВ-значения этих цветов для режима Тгие Со 1ог. В 256-цветном режиме ВОВ-значения этих цветов будут другими. Чтобы увидеть, как выглядит системная палитра на экране, можно создать маленькую экспериментальную программу, код которой приведен в листинге 3.1. Листинг 3.1. Проект 5у81етРа1еНе ////////////////////////////////////////////////////////////////////// // 8у8Ш1Ра1еие.срр #1пс1ис1е <\л/1 пс1о\л/8. И> #1пс1ис1е "К1л1пс1.И" ЬРЕЗЮ САИВАСК ШбРгос(НШО, С1МТ, ПРАВАМ, КРАКАМ);
//==================================================================== 1пЬ ШИАР1 ШпМа1п(Н1М8ТАМСЕ Шнапсе, Н1М5ТАМСЕ ИРгеу1пзЬапсе, ЕРЗТР 1рСтс1Е1пе, 1пЬ пСтс15Р|0\л/) { М8С тзд; К1л1пс1 та1 п1л1пс1 ("8узЬет ра1еЬЬе", ЫпзЬапсе, пСтОЗИом, МпОРгос, МОЕЕ, О, О, 524, 543); \л/М1е (СеЬМеззаде(&тзд, ИЕ1ЕЕ, 0, 0)) { Тгапз1аЬеМеззаде(&тзд); 01зраЬсНМеззаде(&тзд); } геЬигп тзд.мРагат; } //========================= ЕРЕЗЕЕТ САЕЕВАСК Шс1Ргос(НШ0 Шпс1, Е1МТ иМзд, АРАРАМ мРагат, ЕРАРАМ 1Рагат) { НОС НОС; РА1МТЗТР0СТ рз; РЕСТ гО; РЕСТ гесЬ; НВРОЗН ЬгизИ; 1 пЬ (М ОН, 1, ] ; 1пЬ 1 пс1ех = 0; РАЕЕТТЕЕМТРУ ра1[256]; змПсИ (иМзд) { сазе Ш_РА1МТ: НОС = Вед1 пРа1 гтЕ(Н1л1пс1, &рз); СеЬ8узЬетРа1еЬЬеЕпЬг1ез(И0С, 0, 256, ра!); СеЬС11епЬРесШ1л1пс1, &г0); с11л/ = (гО.п’дИЬ - 4) / 16; ОН = (гО.ЬоЬЬот - 4) / 16; 8еШе\л/рогЕ0гдЕх(Н0С, 2, 2, МЕ1ЕЕ); Сог (1 =0; 1 <16; ++1) Тог (] = 0; ] < 16; ++]) { ЗеЬРесШгесЬ, ]*сЕ1, 1*0Н, (]+1)*сЕ1 - 1, (1+1)*с1Н - 1); ЬгизН = СгеаШо] 1с1ВгизН(Р6В(ра1 [1пс1ех] .реРесН ра1 [1пс1ех] .ребгееп, ра1 [1пс1ех] ,реВ1ие)); Е111Рес^СНОС, &гес^, ЬгизН); ОеШеОЬ]есШгизН); 1пОех++; } ЕпОРа]пШ1л1пс1, &рз); Ьгеак; сазе Ш-РАЬЕТТЕСНАМСЕО: 1пуа1 ЮаЬеРесЬСНШО, ИНЕЕ, ТРОЕ); Ьгеак;
Листинг 3.1. {продолжение} Ро8Ь0иПМе88аде(0); Ьгеак; с1еГаи!Ь: геЬигп ОеГШ псЫРгос(Шпс1, иМед, мРагат, 1Рагат); } геЬигп 0; } ////////////////////////////////////////////////////////////////////// Основная работа происходит в блоке обработки сообщения ММ_РА1МТ. Здесь вызовом функции 6е15у$1:ет Ра1ейе ЕпЫез содержимое системной палитры копируется в массив ра I. Затем при помощи функции беКПеп^Кес± координаты клиентской области окна про- граммы запоминаются в переменной гО, которая является структурой типа КЕСТ. По- скольку для клиентской области всегда гО. 1еН: и гО.1юр равны нулю, то величины гО.пдН! и гО.ЬоНют представляют собой ширину и высоту клиентской области соответственно. При заданных в УУ1пМа1П размерах основного окна клиентская область представляет собой квадрат 516 х 516 пикселов. Поэтому переменные д\/\/ и с1Н, определяющие разме- ры и положение очередного маленького квадратика гес1, заполняемого очередным цве- том из системной палитры при помощи кисти ЬгизН, получают значение 32. В блоке обработки сообщения ММ_РА1_ЕТТЕСНАМ6Е0 вызывается функция 1пуаПс1а1:еКес1, которая, в свою очередь, заставляет систему послать оконной проце- дуре сообщение ММ_РА1МТ для обновления содержимого клиентской области. Создайте соответствующий проект и откомпилируйте программу1. Если в на- стройках экрана задан 256-цветный режим, то будет отображена картинка, пока- занная на рис. 3.1. И снова приходится сожалеть, что на черно-белом рисунке поте- ряно много важной информации, характеризующей палитру, поэтому мы надеемся, что вы видите эту картинку на экране монитора2. Обратите внимание на размеще- ние статических цветов. Десять из них находятся в начале таблицы, и еще десять — в конце. Остальные 236 нестатических цветов более или менее равномерно распре- делены в цветовом пространстве КС В, образуя несколько радужных последовательно- стей с разной интенсивностью цветов. Теперь, не закрывая данную программу, запус- тите на выполнение какое-нибудь графическое приложение, например М8 РашЕ Вы увидите, что среди нестатических цветов произошли изменения. Это говорит о том, что приложение М8 Раш! перестроило логическую палитру по своим внутренним критериям. Проведем другой эксперимент. Измените настройки экрана, переключившись в режим Тгие Со1ог3. Окно программы 5у81ет ра1еН:е примет вид, показанный на рис. 3.2. Легко заметить, что статические цвета остались на своих местах, а неста- тические исчезли. Точнее, нестатические цвета оказались заполненными значе- ниями КСВ(0х00, 0x00, 0x00). Дело в том, что в режиме Тгие Со 1ог цветовая палит- ра приложениям не нужна, поэтому система не заботится о ее заполнении. 1 Не забудьте добавить к проекту файлы ЮЛ/пс1.К и КУУпд.срр, код которых приведен в листинге 1.2. 2 Данный проект можно создать самостоятельно или загрузить его исходный код из файлов к книге, которые можно найти на сайте издательства «Питер» (ммм.рйег.сот). 3 Предполагается, что ваш дисплей поддерживает этот режим.
Рис. 3.1. Системная палитра в 256-цветном режиме экрана Рис. 3.2. Системная палитра в режиме Тгие Со1ог
Логическая палитра При работе с логической палитрой следует помнить, что все цвета в приложении, которые реализованы как значения типа СОЮ КРЕЕ, должны быть определены с помощью одного из следующих макросов: СОЕОВВЕЕ РА1_ЕТТЕ1М0ЕХ(1лЮК0 мРа!еНе1пс1ех); СОЕОРРЕР РАЕЕТТЕР6В(ВУТЕ ЬРес1, ВУТЕ ЬСгееп, ВУТЕ ЬВ1ие); Макрос РАЕЕТТЕ1М ОЕХ получает индекс и возвращает 32-разрядное описание эле- мента палитры. В контексте устройства оно используется как элемент логической палитры контекста. Макросу РАЕЕТТЕ Кб В, как и макросу ВСВ, передаются значения красной, зеленой и синей составляющих искомого цвета. При использовании этих макросов в контексте устройства без логической палитры их возвращаемые значения ин- терпретируются одинаково. Но если макрос РАЕЕТТЕВСВ используется в контексте устройства с логической палитрой, то для указанных в нем КСВ-составляющих система ищет ближайшее совпадение в логической палитре, словно приложение указало индекс в палитре. Этот макрос используется чаще, так как он является очевидной заменой макроса ВС В во всех функциях, требующих передачи значения типа СОЮККЕЕ. Логическая палитра по умолчанию Ранее уже говорилось, что если приложение не создает явным образом логическую палитру, то в нем используется логическая палитра по умолчанию, содержащая толь- ко 20 статических цветов из системной палитры. Для экспериментов с логической палитрой по умолчанию создадим приложе- ние, выводящее в свое окно 8 разных цветов (листинг 3.2). Листинг 3.2. Проект Со1огз\Л/В:ЬОеГаикРа1 ////////////////////////////////////////////////////////////////////// // Со1ог8ШШеТаиИРа1 .срр #1пс1ис1е <\л/1 пс1о\л/8. И> #1пс1ис1е "К1л1пс1.И" ЕкЕЗЕЕТ САЕЕВАСК ШбРгос(НШО, 1ЛМТ, ПРАВАМ, ЕРАВАМ); //========================================== 1пР ШИАР1 ШпМа 1 п (НIИЗТАМСЕ Шнапсе, Н1М5ТАМ1СЕ Г1Ргеу1п8^апсе, ЕРЗТВ 1рСтс1Е1пе, 1пР пСтсЗНсм) { М8С тзд; К1л/пс1 та1 п1л1пс1 ("Со1 огз беСаиИ ра!еНе", КТпзРапсе, пСтсЗНсм, МпбРгос, ШЕЕ, 0, 0, 800, 200); \л/М1е (СММеззадеС&тзд, ИЕ1ЕЕ, 0, 0)) { ТгапзШеМеззадеС&тзд); Отзра’ЕсНМеззадеС&тзд); } ге^игп тзд.мРагат; } //========================= ЕВЕЗЕЕТ САЕЕВАСК ШбРгос(ШИВ Шпс1, ЕIИТ иМзд, ПРАВАМ мРагат, ЕРАВАМ 1Рагат) { НОС ВВС; РА1ИТЗТВЕСТ рз;
КЕСТ гО; КЕСТ гесТ[8]; 1 пТ с11л1, с1Н, 1 ; зТаЫс НВКСЗН ЬгизЬ[8]; СОЬОККЕЕ гдЬСо!ог[8] = { КСВ(128,0,0), КСВ(192,0,0),КСВ(255,0,0), КСВ(255,192,0), К6В(176,250,133), КСВ(245,197,137), КСВ(255,128,128), КСВ(255,128,255)}; змПсЬ (иМзд) { сазе Ш_СКЕАТЕ: Тог (1 =0; 1 <8; ++1) ЬгизИШ = СгеаТе8о11с1ВгизЬ(гдЬСо1ог[1]); Ьгеак; сазе 1л/М_РА1МТ: ВВС = Вед1 пРа1 пТ(Шпс1, &рз); СеЮ 1 епТКесТ (Шпс1, &г0); с11л/ = гО.п’дЬТ / 8; с1Н = гО.ЬоТТот; Тог (1 =0; 1 <8; ++1) { 8еТКесТ(&гесТ[1], 1 *с11л1, 0, (1+1)*с11л1, с1Н); Е111 КесШОС, &гесТ[1], ЬгизИШ); } Епс1Ра1 пТ (Шпс1, &рз); Ьгеак; сазе Ш_0Е8ТК0У: Тог (1 =0; 1 <8; ++1) Ве1еТеОЬаесТ(ЬгизЬ[1]); РозТОьп ТМеззаде(0); Ьгеак; ЬеТаиИ: геТигп ОеТШпсЫРгос(Ыл1пс1, иМзд, мРагат, 1Рагат); } геТигп 0; } ////////////////////////////////////////////////////////////////////// Если ваш экран остался в режиме Тгие Со1ог, то программа выведет на экран окно, показанное на рис. 3.3. Рис. 3.3. Использование палитры по умолчанию (режим Тгие Со1ог) В окне слева направо представлены 8 цветов, закодированные в массиве гдЬСо 1ог: темно-красный, темновато-красный, красный, оранжевый, салатный, бежевый, кремовый, сиреневый. Первые три цвета являются вариациями чистого красного
цвета с разной интенсивностью, остальные цвета образованы подходящими комби- нациями ВСВ-составляющих цвета. Теперь переключитесь в 256-цветный режим экрана и снова запустите про- грамму. Вывод программы показан на рис. 3.4. Рис. 3.4. Использование палитры по умолчанию (256-цветный режим) Особенно заметно исказились оранжевый, салатный и бежевый цвета. Причи- ной этого является использование только двадцати статических цветов из сис- темной палитры. Поскольку для указанных трех цветов не нашлось ближайшего подходящего цвета, то система попыталась создать запрошенный цвет при помо- щи смешения статических цветов. Полутоновая палитра Чтобы увеличить количество имеющихся цветов в палитре, можно воспользо- ваться полутоновой палитрой. Для этого необходимо выполнить следующие шаги: 1. Создать полутоновую палитру (функция Сгеа1еНа1Л:опеРа1ейе). 2. Выбрать созданную палитру в контекст устройства (функция 8е 1ес1Ра 1еВ:е). 3. Реализовать палитру при помощи функции КеаИгеРа1еВ:е. Функция 5е1ес1:Ра1е1:1:е имеет следующий прототип: НРА1ЕТТЕ 8е1ес^Ра1еНе( НОС Мс, // НРАЕЕТТЕ Нра1, // ВООЕ ЬГогсеВаскдгоипс! // дескриптор контекста устройства дескриптор логической палитры режим (фоновый или основной) Последний параметр функции определяет, в каком режиме — фоновом (ТРОЕ) или основном (ЕА15Е) — будет реализована палитра при вызове функции КеаПге РаЕейе. Процесс реализации палитры в общих чертах был описан выше. Следует уточнить, что в фоновом режиме функция КеаПге Ра1е1±е пытается раз- местить логическую палитру привязкой к уже существующим цветам в систем- ной палитре, подыскивая ближайшее соответствие. Это размещение делается все- гда, независимо от того, активно ли данное приложение. В основном режиме функция КеаПнеРаЕейе копирует логическую палитру в системную, за исключеним статических цветов, только тогда, когда приложе- ние активно. В листинге 3.3 приведен код приложения, которое выполняет те же действия, что и предыдущее приложение, но работает с полутоновой палитрой. В нем пока- зана также обработка сообщений ММ_О11ЕКУМЕМРА1_ЕТТЕ и ММ_РА1_ЕТТЕСНАМ6Е0.
Листинг 3.3. Проект Со1ог8\МРИНа1ГРопеРа1 ////////////////////////////////////////////////////////////////////// // Со1огзШРЬНа1ТРопеРа1 .срр #1пс1ис1е <\л/1 пВомз. Ь> #1пс1ис1е "КМпб.б" 1КЕ81ЛТ САЕЕВАСК ШбРгос(НШО, 1ИМТ, ПРАВАМ, ЬРАВАМ); //========================================== 1пР ШИАР1 ШпМаШНЖТАМСЕ Шнапсе, Н1М5ТАМСЕ ЬРгеу1пзРапсе, ЕРЗТВ 1рСтбЫпе, 1пР пСтс15Но\л/) { М8С тзд; К1л1пс1 та1 п1л!пб ("Со1 огз т’РЬ Ьа1Тропе ра!еРРе", ИТпзРапсе, пСтсЕВом, Шс1Ргос, МОП, 0, 0, 800, 200); \л/Н11е (СеРМеззаде(&тзд, МВЕЕ, 0, 0)) { Тгап81аРеМе88аде(&тзд); В18раРсИМе88аде(&тзд); } геРигп тзд.мРагат; } //========================= ЕВЕЗРЕТ САЕЕВАСК ШВРгос(НШВ Шпс1, Р1МТ иМзд, ПРАВАМ мРагат, ЬРАВАМ 1Рагат) { НОС ВВС; РА1МТЗТЕВСТ рз; ВЕСТ гО; ВЕСТ гесРЕ8]; 1 пР с11л1, с1Н, 1; СОЕОВВЕЕ ра1еРРеСоОогЕ80 = { РАЕЕТТЕВЗВ(128,0,0), РАЕЕТТЕВЗВ(192,0,0), РАЬЕТТЕВСВ(255,0,0), РАЬЕТТЕВСВ(255,192,0), РАЬЕТТЕВСВ(176,250,133), РАЕЕТТЕВЗВ(245,197,137), РАЕЕТТЕВЗВ(255,128,128), РАЕЕТТЕВСВ(255,128,255)}; зРаРас НВВВ8Н ЬгизИЕВЦ; зРаРпс НРАЕЕТТЕ ЬРа! ; НРАЕЕТТЕ Н01бРа1; з\л/1РсИ (иМзд) { сазе Ш_СВЕАТЕ: Топ (1 =0; 1 <8; ++1) ЬгизЬ[1] = СгеаРеЗо!1бВгизИ(ра!еРРеСо!огЕ1И): ВВС = ЗеРВС(Шпб); ЬРа! = СгеаРеНа1ТРопеРа1еРРе(ЬВС); Ве1еазеВС(Шпс1, ВВС); Ьгеак; сазе 1л/М_РА1МТ: ВВС = Ведя пРа1 пР(Шпс1, &рз); ИООбРаО = 8е1есРРа1еРРе(ЬВС, ЬРа!, ЕАЕЗЕ); СеРС! 1 епРВесР (Ыл1пс1, &г0); 61л/ = гО.ндИР / 8; 6Н = гО.ЬоРРот; Тог (1 =0; 1 <8; ++1) { ЗеРВесР(&гесРЕ1 ], 1 *б!л!, 0, (1+1)*б1л1, 6Н); Е111ВесР(ВВС, &гесР[1], ЬгизИ[1]);
Листинг 3.3 {продолжение} 8е1есГРа1еНе(И0С, И01с1Ра1, ТРОЕ); Епс1Ра11тЕ(Шпс1, &рз); Ьгеак; сазе Ш_аЬЕРУМЕ1л1РАЕЕТТЕ: РОС = 6еЬ0С(Шпс1); Зе!есЬРа!еЬЬе(РОС, ИРа!, ГАЕЗЕ): 1Г (Реа! 1 геРа!еНе(РОС)) 1пуа11с1аЬеРесЬ(Шпс1, МЫЕЕ, ТРОЕ); Ре! еазеОС(Г|1л/пс1, РОС); Ьгеак; сазе 1л1М_РАЕЕТТЕСНАМ6Е0: РОС = 6еЬ0С(Шпс1); 3е1 есЬРа!еЬШЬОС, ЬРа!, ГАЕЗЕ): 1Г (Реа!1геРа!еЬЬе(НОС)) 1пуа11с1аЬеРесЬ(Шпс1, МОЕЕ, ТРОЕ); Ре!еазеОС(Шпс1, РОС); Ьгеак; сазе Ш_0Е8ТР0У: 0е1еЬеОЬ)есЬ(ЬРа1); Гог (1 =0; 1 <8; ++1) 0е1еГеОЬ)есГ(ЬгизЬ[1 ]); РозШПГМеззаде(О); Ьгеак; ЬеГаиИ: геГигп ОеГШпЬомРгос(Шпс1, иМзд, мРагагтг 1Рагат); } геГигп 0; } ////////////////////////////////////////////////////////////////////// Обратите внимание на следующие моменты: □ Логическая палитра НРа1 создается в блоке обработки сообщения ММ_СКЕАТЕ, а реализуется как в блоке обработки сообщения ММ_011ЕКУМЕМРА1_ЕТТЕ, так и в блоке обработки сообщения ММ_РА1_ЕТТЕСНАМ6Е0. Функция Кеа11геРа1е1±е воз- вращает количество цветов логической палитры, отображенных на системную палитру. Если это значение больше нуля, то вызывается функция 1пуаИс1а1:е КесЕ □ В блоке обработки сообщения ММ_РА1МТ логическая палитра выбирается в кон- текст устройства, после чего вызываются функции рисования. □ После запуска этой программы в 256-цветном режиме экрана вы должны уви- деть картинку, практически совпадающую по качеству передачи цветов с той, которая была в режиме экрана Тгие Со1ог (см. рис. 3.3). Использование специализированной палитры Бывают ситуации, когда применение полутоновой палитры не обеспечивает удов- летворительной передачи всех требуемых цветов. Это характерно для приложений, которым необходимо отображать множество оттенков одного и того же цвета. Нетрудно показать на примере предыдущего приложения, что оно не справит- ся с выводом даже восьми оттенков одного, например зеленого, цвета. Для этого замените определение массива ра 1е1±еСо 1ог следующим определением:
СОЮККЕЕ ра!еПеСо1ог[8] = { РАЕЕТТЕКСВ(0,31,0), РАЕЕТТЕКЗВ(0,63,0), РАЕЕТТЕКЗВ(О,95,О), РАЕЕТТЕКЗВ(О,127,0), РАЕЕТТЕКЗВ(О,159,0), РАЕЕТТЕКЗВ(О,191,О), РАЕЕТТЕКЗВ(О,223,О), РАЕЕТТЕКЗВ(О,255,О)}; Модифицированная программа выведет картинку, в которой первый, второй, шестой и седьмой прямоугольники неразличимы по цвету. Это означает, что пришло время применить специализированную логическую палитру. Для ее создания используется функция Сгеа1е Ра1ейе: НРАЕЕТТЕ Сгеа0еРа1еПе(С0М8Т ЕОЗРАЕЕТТЕ* 1 р!др!); Параметр 1р1др1 содержит указатель на структуру 1_06РА1_ЕТТЕ, определение кото- рой имеет следующий вид: 6урес1еТ' 81гис1 ЦадЕОЗРАЕЕТТЕ { ШКО ра1\/ег81оп; // номер версии палитры ЮКО ра!МытЕггЕгчез; // число входов в логической палитре РАЕЕТТЕЕМТКУ ра! Ра1 ЕЮгуЕП ; } ЕОЗРАЕЕТТЕ: Номер версии палитры не изменился со времен ее появления в АУшсЬмгз 3.0 и по-прежнему равен 0x300. Поле ра 1Ра1Еп1:гу содержит адрес массива структур РАЕЕТТЕЕМТКУ, определяющих цвет каждого элемента в логической палитре. Структура РАЕЕТТЕЕМТКУ уже рассматривалась в разделе «Системная палитра». Три ее первых поля обычно описывают интенсивность компонентов КСВ-моде- ли, а поле ре Е1ад$ указывает, как данный элемент должен интерпретироваться при реализации палитры. Возможные значения поля реЕ1ад$ перечислены в табл. 3.2. Таблица 3.2. Возможные значения поля реНадз Значение Описание 0 Стандартная процедура, предписывающая искать цвет НСВ в системной палитре. Если цвет отсутствует, то он включается в палитру РС_1\ЮСО1_1_АР8Е Искать соответствие в системной палитре лишь при отсутствии свободных (неиспользуемых) элементов. В противном случае нужно использовать первый свободный элемент РС_ЕХР1_1С1Т Не изменять системную палитру. В первые два байта структуры РА1_ЕТТЕЕ1\1ТкУ поместить индекс элемента в системной палитре. Этот флаг позволяет приложению показать содержимое аппаратной палитры РС_кЕ8ЕНУЕО Указывает, что данный элемент палитры будет использоваться для анимации. Этот флаг запрещает другим окнам согласовывать какой-либо цвет с данным элементом палитры. Если в системной палитре есть свободные элементы, то первый из них используется для размещения данного элемента Отметим, что в определении структуры ЕОСРАЕЕТТЕ массив ра1Ра1ЕпЕгу содержит всего один элемент, так как нельзя заранее сказать, сколько элементов может по- требоваться для создания логической палитры. Поэтому в коде приложения необ- ходимо либо расширить определение типа ЕОСРАЕЕТТЕ, либо выделить в куче блок памяти необходимого размера и адресоваться к нему при помощи указателя на ЕОСРАЕЕТТЕ. В рассматриваемой ниже программе демонстрируется первый подход. После создания объекта палитры он должен быть реализован вызовом функ- ции КеаПгеРа1е1±е так же, как и в случае с полутоновой палитрой. В листинге 3.4 приводится код программы, в которой создается логическая па- литра, содержащая 128 градаций зеленого цвета, и демонстрируется вывод 16 пря- моугольников, равномерно заполненных различными оттенками зеленого цвета.
Листинг 3.4. Проект 1_од1сРа1еНе ////////////////////////////////////////////////////////////////////// // 1_од1 сРа! еНе. срр #1пс1ис1е <\л/1 пс1о\л/8. И> #1пс1ис1е "К1л/пс1.ГГ #с1еГ1пе РАЕ_М0М_ЕИТК1ЕЗ 128 #с1еГ1пе ШМ_ВЕСТ 16 ЁуребеТ зТгисТ { ЕОСРАЕЕТТЕ 1р; РАЕЕТТЕЕЕ1ТВУ ареЕРАЕ_МОМ_ЕМТКIЕ8 - 1]; } ЕодРа!; ЕКЕ50ЕТ САЕЕВАСК Шс1Ргос(НШО, О1ИТ, ПРАВАМ, ЕРАВАМ); //============================================ 1пР ШИАР1 ШпМапп(НIИЗТАМСЕ НТпзТапсе, Н1М5ТАМСЕ 6Ргеу1пзТапсе, ЕРЗТВ 1рСтс1Е1пе, Ш пСгткОом) { МЗС тзд; К1л1пс1 та1 п1л1пс1("Со1 огз \л/1ТГ| 1од1с ра1еТТе", 61пзТапсе, пСтсЕКом, 1л1пс1Ргос, МОЕЕ, 0, 0, 808, 200); \л/Гп 1 е (СеТМеззаде(&тзд, ИЕ1ЕЕ, 0, 0)) { ТгапзШеМеззадеС&тзд); ОтзраТсКМеззадеС&тзд); } геТигп тзд.мРагат; } //========================= ЕВЕЗЕ1ЕТ САЕЕВАСК Шс1Ргос(ШИВ Шпс1, 011\1Т иМзд, ПРАВАМ мРагат, ЕРАВАМ 1Рагат) { НОС ВВС; РА1МТЗТВ0СТ рз; ВЕСТ г0; ВЕСТ гесТЕМСМ_ВЕСТ]; 1пТ сМ, ОН, 1; зТаТ1с ЕодРаО ра!; ЕОСРАЕЕТТЕ* рЕР = (ЕОСРАЕЕТТЕ*) &ра! ; зТаТ1с СОЕОВВЕЕ ра1еТТеСоОогЕМ0М_ВЕСТ1; зТаТ1с НВВОЗН ЬгизИЕМОМ_ВЕСТ0; зТаТ1с НРАЕЕТТЕ 6Ра1; НРАЕЕТТЕ 6О1с1Ра1; ВУТЕ дгееп; ВУТЕ зТерРа!, зТерВгизТ 8\л/1ТсГ| (иМзд) { сазе Ш_СВЕАТЕ: // Создаем логическую палитру рЕР->ра! Уегзпоп = 0x300; // номер версии ШпсЫз рЕР->ра1 МытЕпТнез = РАЕ_1\10М_Е1У1ТВ1ЕЗ; // число входов палитры зТерРа! = 256 / РАЕ_М0М_Е1\1ТВ1 ЕЗ;
дгееп = 255; Тог (1 = 0; 1 < РА1__МиМ_ЕМТРПЕ5; 1++) { рЕР->ра!Ра!ЕпТгуП].реРеб = 0; рЕР->ра!Ра!ЕпТгу[1].ребгееп = дгееп; рЬР->ра! Ра! ЕпТгуП ] ,реВ!ие = 0; рЕР->ра!Ра!ЕпТгу[1].реПадз = 0; дгееп -= зТерРа!; } ИРа! = СгеаТеРа!еТТе (рЕР); // Готовим кисти для рисования зТерВгизЬ = 256 / МиМ_РЕСТ; Гог (а = 0; 1 < МиМ_РЕСТ; ++1) ЬгизЬ[1] = СгеаТеЗо!1бВгизИ(РАЬЕТТЕР6В(0, зТерВгизЬ *1, 0)); Ьгеак; сазе Ш-РА1МТ: ВВС = ВедяпРаппТ(Ыл1пб, &рз); ЬО!с1Ра! = 8е!есТРа!еТТе(Ь0С, ЬРа!, ЕАЕЗЕ); беТС!1 епТРесТ (Шпб, &с0); Ш = гО. гпдктЕ / МЬМ_РЕСТ; 6Н = гО.ЬоТТот; Тог (1 = 0; 1 < ШМ-РЕСТ; ++1) { ЗеТРесТ(&гесТГ а ], 1*(М 0, (1+1)*Ш, 6Н); Е111 РесТ(ЬОС, &гесТ[1], ЬгизЬГИ); } Зе!есТРа!еТТе(ЬОС, ЬО1бРа1, ТРОЕ); ЕпбРап пТ (Ыл/пб, &рз); Ьгеак; сазе 1л/М_абЕРУМ ЕМРАЕЕТТЕ: ЬОС = 6еТ0С(Ыл1пб); Зе!есТРа!еТТе(ЬОС, ЬРа!, ЕАЕЗЕ); 1Т (Реа!агеРа!еТТе(ЬОС)) 1пуа11 баТеРесТ(Ыл1пб, МЕ1ЕЕ, ТРОЕ); Ре! еазеОС(Ыл/пб, РОС); Ьгеак; сазе Ш-РАЬЕТТЕСНАМбЕО: ЬОС = 6еТ0С(Ыл/пб); 3е1есТРа!еТТе(ЬОС, ЬРа!, ЕАЕЗЕ); 1Т (Реа!12еРа!еТТе(Ь0С)) 1пуа! 1баТеРесТ(Ыл1пб, МЕ1ЕЕ, ТРОЕ); Ре!еазеОС(Ыл1пб, ЬОС); Ьгеак; сазе Ш_0Е8ТР0У: Ое!еТеОЬдесТ(ЬРа!); Тог (1 = 0; 1 < ШМ-РЕСТ; ++1) Ое!еТеОЬдесТ(ЬгизЬ[1]); РозТОи!ТМеззаде(0); Ьгеак; беТаи!Т: геТигп ОеТШпбомРгос(Ыл1пб, иМзд, мРагат, !Рагат); } геТигп 0; } //////////////////////////////////////////////////////////////////////
Следует обратить внимание на определение типа 1_одРа1 в глобальной области видимости программы, которое расширяет стандартный тип 1_06РА1_ЕТТЕ. Наш пользо- вательский тип включает структуру типа 1_06РА1_ЕТТЕ, после которой помещен мас- сив структур РА1_ЕТТЕЕМТКУ, содержащий (РА1__МIIМ_ЕМТК1Е$ - 1) элементов. Объект типа кодРа1 объявляется в теле функции \Л/пдРгос: $Шс ЬодРа! ра!; Чтобы работать с этим объектом, интерпретируя его как переменную типа 1_06РА1_ЕТТЕ, объявляется указатель на тип 1_06РА1_ЕТТЕ, после чего ему присваивается адрес переменной раI: ШбРАЬЕТТЕ* рЕР = (Е06РАЕЕТТЕ*) &ра! ; Количество входов в логической палитре не может превышать 236, так как 20 элементов системной палитры занято статическими цветами. В нашей программе это количество задано константой РА1__М11М_ЕМТК1Е$, равной 128, поскольку это даже перекрывает потребности приложения, выводящего 16 градаций зеленого цвета. Создание логической палитры осуществляется в блоке обработки сообщения ММ_СКЕАТЕ. Для этого сначала инициализируются поля объекта раI, адресуемые через указатель рЬР, причем массив ра 1Ра 1Еп1:гу равномерно заполняется распределенными градациями зеленого цвета. После этого вызывается функция Стеа1еРа 1еВ:е. Реализа- ция палитры происходит при обработке сообщений ММ_011ЕКУ1\1ЕМРА1_ЕТТЕ и ММ_РА1_ЕТТЕСНА1\16Е0. И наконец, в блоке обработки сообщения ММ_РА1МТ логичес- кая палитра используется после выбора ее в контекст устройства при помощи функ- ции 5е1ес1Ра1е1:1:е. После запуска программы вы должны получить картинку, показанную на рис. 3.5. Рис. 3.5. Использование специализированной палитры (256-цветный режим) Растры Графические объекты, рассмотренные в главе 2, такие как пикселы, линии и замк- нутые фигуры, могут использоваться для построения инженерных и финансовых диаграмм, чертежей, геометрических узоров и иных графических композиций. Гео- метрические фигуры в этих изображениях описываются точными математически- ми формулами. Соответствующая область программирования компьютерной гра- фики называется векторной графикой (ресСог ^гарЫсз). В другой, не менее важной области компьютерной графики используются оциф- рованные изображения, полученные из окружающего мира. Эта область называ- ется растровой графикой (Ы1тар ^гаркгсз). Обычно растровые изображения по- лучают от таких устройств, как цифровой фотоаппарат, сканер, видеокамера, либо
создают в каком-нибудь графическом редакторе, к которым относится, например, МхсгозоЙ Раш! или АсТоЬе РйоФзйор. Растровый, или битовый, образ (Ыётар) — это оцифрованное представление изоб- ражения. Каждый пиксел изображения представлен в растровом образе одним или несколькими битами, в которых закодирован его цвет. В монохромных битовых об- разах для хранения информации о каждом пикселе достаточно одного бита. Но для кодирования цветных изображений требуется более одного бита на пиксел. Число цветов, которые могут быть представлены в битовом образе, равно 2п, где п — количество битов на пиксел. Например, для старой модели дисплея VС А с поддер- жкой 256 цветов битовые образы содержали по 8 битов на пиксел. Для современных мониторов, поддерживающих до 224 цветов, битовый образ должен содержать 24 бита на пиксел, когда для каждого КСВ-компонента цвета используется 8 битов. В ранних версиях АУтскпуз использовался только один формат битового об- раза, получивший позже наименование аппаратно-зависимого растра, или ЛИВ (беЫсе берепбеп!; Ыйтар}. Особенность формата НОВ состоит в том, что система всегда создает битовый образ в памяти с учетом характеристик конкретного гра- фического оборудования. Например, если компьютер оснащен 256-цветным дис- плеем, то созданный битовый образ будет содержать по 8 битов на пиксел. Фор- мат НОВ не подходил для переноса растров на другие компьютеры, так как характеристики графических устройств изменяются в очень широких пределах. Более того, даже на одном компьютере дисплей может работать в разных режи- мах, в зависимости от текущих настроек экрана. Начиная с АУхпсТом^з 3.0, был введен новый формат битовых образов, названный аппаратно-независимым растром, или Э1В (беЫсе тберепбепё Ыстар). Битовый образ НТВ кроме самого массива пикселов содержит справочную информацию о растре и цветовую таблицу. В цветовой таблице отражается соответствие двоич- ного представления пикселов цветам НОВ, поэтому битовые образы этого формата после сохранения в файле могут быть воспроизведены на любом растровом графи- ческом устройстве. Если устройство, на которое переносится НТВ, имеет более уз- кий диапазон поддерживаемых цветов, то цвета из таблицы преобразуются к бли- жайшим цветам, которые устройство может действительно воспроизвести. В настоящее время аппаратно-зависимые растры (НОВ) используются во внут- ренней работе графической системы АУтскпуз, а также для представления тех битовых образов в программе, которые не предназначены для передачи во внешний мир. Аппаратно-независимые растры (НТВ), напротив, применяются для обмена битовыми образами между программами как в форме файлов, так и через буфер обмена \У1пс1о\\ъ. Мы начнем наше путешествие по растровой тематике с рассмотрения НТВ, потом перейдем к ПИВ и в завершение познакомимся с новым типом растров, поддерживаемым в АУш32 ОПТ, — НТВ-секциями. Аппаратно-независимые растры Аппаратно-независимые растры (НТВ) содержат полную информацию об изоб- ражении, что позволяет правильно отображать изображение на самых различных устройствах. Обычно файлы, в которых сохраняется НТВ, имеют расширение .Ьтр. Поэтому формат О1В-растров имеет альтернативное наименование «файловый формат ВМР».
Файловый формат ВМР Компоненты ВМР-формата, размещаемые в памяти последовательно один за дру- гим, перечислены в табл. 3.3. Таблица 3.3. Формат 01В-растра (Файловый формат ВМР) Компонент Размер Примечание Заголовок растрового файла ВГГМАРРИЕНЕАОЕк ЬтГНеаЬег; 14 байт Заголовок информационного блока ВГГМАРИМРОНЕАОЕк Ьт1Неас1ег; 40 байт Возможно использование структуры В1ТМАРУ4НЕА0Ек (108 байт) или В1ТМАРУ5НЕАОЕк (124 байта) Цветовая таблица Р6В(211АО Ьгп|Со1ог5[]; Переменный Может отсутствовать Массив пикселов ВУТЕ аВ1бтарВКз[][]; Переменный Заголовок растрового файла Структура ВПМАРЕИЕНЕАОЕК, используемая для объявления заголовка растрового файла ЬтГНеас1ег, имеет следующее определение: Уурес1еТ збгысб бадВ1ТМАРЕ1ЕЕНЕА0ЕР { ШРО Шуре: // тип файла ОШРО 6681 хе; // размер файла в байтах ШРО ЬбРезегуесИ: // резервное поле (должно быть равно нулю) ШРО ЬТРезегуебЕ; // резервное поле (должно быть равно нулю) ОШРО ЬбОббЫбз; // байтовое смещение до начала массива пикселов } В1ТМАРК1ЕЕНЕАОЕР; Поле ЬГГуре должно содержать А8СП-символы В и М, которые, разумеется, означают Ъйлпар. Соответствующие шестнадцатеричные значения равны 0x42 и 0x4В. При любых других значениях этого поля файл не будет опознаваться \Упк1о\\ъ как растровое изображение. Учтите, что слово типа МОКО размещается в памяти, начиная с младшего байта, поэтому, если вы формируете заголовок, ис- пользуйте инструкцию вида ЬГГуре = 0х4с142. Поле Ы51не содержит размер всего файла в байтах. Поле ЬГОГГЬйз задает байтовое смещение до начала растрового изображения. Если ВМР-файл прочитан в память, то поле ЬГОГГЬйз позволяет вычислить адрес начала массива аВйтарВйз. Заголовок информационного блока После заголовка растрового файла следует заголовок информационного блока Ьпп Н еа де г, в котором содержится информация о размерах и цветовом формате растра. Соответ- ствующая структура В1ТМАР1И ЕОН ЕАОЕВ определена в файле \лл пдсН.Н: буребеб збгысб бадВТТМАР I МЕОНЕАВЕР{ ВШРВ Ы81хе; // размер структуры В1ТМАРIИЕОНЕАВЕР Е0Г\1<3 ЫШббб; // ширина растра в пикселах Е0М6 ЫНе1дбб; // высота растра в пикселах + ориентация ШРВ ЫР1апе8; // количество плоскостей (должно быть равно 1) ШРВ ЫВ1бСоыпб; // количество битов на пиксел ВШРВ ЫСотргеззтоп; // алгоритм сжатия ВШРВ Ы81хе1таде; // размер массива пикселов в байтах
1_(Ж ЫХРе1зРегМе^ег; // горизонтальное разрешение устройства вывода 1_(Ж ЫУРе1зРегМе^ег; // вертикальное разрешение устройства вывода ОШРО ЫС1 гСзес!; // количество элементов в цветовой таблице ОШРО ЫС1 Нтрогбапб; // количество элементов, реально используемых для вывода // растра } В1ТМАР1МЕОНЕАОЕР; Поля Ь1\Л/1сП:Н и ЫНетдЫ: определяют ширину и высоту битового изображения. Высота ЫНетдЫ: обычно является положительной величиной, но она может быть и отрицательной. Знак этого поля определяет порядок следования строк развертки в массиве пикселов. Положительное значение соответствует обратному порядку строк (снизу вверх), при котором первый пиксел массива является первым пиксе- лом последней строки развертки изображения. Такие В1В-растры называют пере- вернутыми {Ъоиот-ир}. Отрицательное значение соответствует прямому порядку строк (сверху вниз), и такие В1В-растры называют неперевернутыми (1ор-с1омл1). В большинстве ВМР-файлов используется обратный порядок следования строк раз- вертки. Поле ЫР1апе$ задает количество цветовых плоскостей для целевого графическо- го устройства. В разных устройствах может использоваться различная структура строк развертки (с одной или несколькими цветовыми плоскостями). Формат В1В поддерживает изображения только с одной плоскостью, поэтому поле ЫР1апе$ дол- жно иметь единичное значение. Поле ЫВйСоип! содержит количество битов, используемых для кодирования цвета одного пиксела. Иногда эту характеристику называют цветовой глубиной. Возможные значения поля приведены в табл. 3.4. Таблица 3.4. Значения поля ЫВкСоип! Значение Максимальное количество цветов Размер пиксела, байт Описание 0 Зависит от внедренного изображения Число битов на пиксел специфицируется форматом ЗРЕС или РЫС (только для \Л/1пс1о\л/5 98/2000) 1 21 (2) 1/8 Монохромное изображение. Цветовая таблица ЬгтнСо1ог5 содержит два элемента, соответствую- щие двум значениям типа к6В(211АО. Каждый бит из массива аВИтарВКз определяет индекс элемента массива ЬгтнСо1ог5. Если бит равен 0, то он окрашивается в соответствии с содер- жимым Ьгп|Со1ог5[0]/ если 1 — в соответствии с содержимым Ьт1Со1ог5[1] 4 24 (16) 1/2 Изображение содержит до 16 цветов, соответ- ственно, цветовая таблица ЬгтнСо1ог5 содержит до 16 значений типа к6В(211АО. Каждый байт в массиве аВКтарВИз представляет два пиксела. Цвет пиксела определяется его 4-битным индексом в цветовой таблице 8 28 (256) 1 Изображение содержит до 256 цветов, и ЬгтпСо1ог5 содержит до 256 элементов. В этом случае каждый байт в массиве аВИтарВИз представляет отдельный пиксел. Значение байта используется как индекс в цветовой таблице продолжение &
Таблица 3.4 {продолжение} Значение Максимальное Размер количество пиксела, цветов байт Описание 16 216 (65 536) 2 Изображение содержит до 216 цветов (пикселы типа Н1дН Со1ог). Если поле ЫСотргеззюп равно В1_кСВ, то цветовая таблица ЬгтнСо1ог5 обычно отсутствует. Каждое слово (\Л/ОНО) в массиве аВКтарВИБ представляет отдельный пиксел. Относительная интенсивность синего, зеленого и красного цветов представлена пятью битами для каждого цветового компонента. Старший знаковый разряд слова не используется Если поле ЫСотргеззюп равно В1_В1ТР1ЕЮ8, то после заголовка информационного блока и перед цветовой таблицей добавляются три двойных слова — маски для извлечения синей, зеленой и красной составляющих из слова в массиве аВКтарВкз 24 224 (16 777 216) 3 битные Изображение содержит до 224 цветов (24- пикселы Тгие Со1ог). Цветовая таблица ЬгтнСо1ог5 обычно отсутствует. Каждый трехбайтный триплет в массиве аВКтарВПБ характеризует относительную интен- сивность синей, зеленой и красной составляющих для цвета очередного пиксела. Иногда таблица ЬгтнСо1ог5 может использоваться для оптимизации вывода на устройствах, работающих с палитрой. В этом случае цветовая таблица содержит число входов, специфицированное в поле ЫС1г115ес1 32 224 (16 777 216) 4 Изображение содержит до 224 цветов (32-битные пикселы Тгие Со1ог). Если поле ЫСотргеззюп равно В1_кСВ, то цветовая таблица ЬгтнСо1ог5 обычно отсутствует. Каждое двойное слово (О\Л/ОКО) в массиве аВ1йпарВ|15 характеризует отдельный пиксел. Относительная интенсивность синего, зеленого и красного цветов задана тремя байтами в двойном слове. Старший байт не используется. Если поле ЫСотргеззюп равно В1_В1ТР1ЕЮ8, то после заголовка информационного блока и перед цветовой таблицей добавляются три двойных слова — маски для извлечения синей, зеленой и красной составляющих из двойного слова в массиве аВИтарВ^Ь Иногда старший байт в 32-битных пикселах Тгие Со1ог используется для хране- ния альфа-канала, то есть данных о прозрачности пиксела. Поле Ь1’Сотрге$$1’оп определяет алгоритм сжатия, применяемый к массиву пик- селов. Допустимые значения поля приведены в табл. 3.5. Если поле ЫНетдЫ: имеет отрицательное значение, то поле ЫСотрге$$1’оп может принимать только значения В1_К6В или В1_В1ТЕ1ЕЮ5, поскольку неперевернутые растры не допускают сжатие.
Таблица 3.5. Значения поля ЫСотргеззюп Константа Код Описание В1_кСВ 0x0 Несжатое изображение В1_Н1_Е8 0x1 Изображение с кодировкой 8 бит/пиксел, сжатое с использованием алгоритма НЕЕ В1_Н1_Е4 0x2 Изображение с кодировкой 4 бит/пиксел, сжатое с использованием алгоритма НЕЕ В1_В1ТР1ЕЮ8 0x3 Несжатые изображения с кодировкой 16 и 32 бит/пиксел. В изобра- жение включаются три битовые маски, определяющие способ хранения компонентов КСВ Чаще всего поле Ь1’Сотрге$$1’оп содержит значение В1_К6В. Графические редакто- ры от М1СГО8ОЙ, а также среда разработки Ухзпа! ЗЕпсйо генерируют ВМР-файлы только в несжатом формате. При отсутствии сжатия каждая строка развертки изоб- ражения представляет собой упакованный массив пикселов. Размер пиксела в бай- тах указан в табл. 3.4. Строка развертки всегда выравнивается по ближайшей гра- нице двойного слова, а при необходимости строка дополняется нулями. В растре В1В с кодировкой 4 и 8 бит/пиксел для уменьшения размеров растра может применяться необязательное сжатие по алгоритму НГЕ (Кпп-Ееп§1й Епсос1т§). Для 8-битных изображений этот алгоритм ищет последовательность смежных байтов с одинаковым значением, после чего заменяет их двумя байтами, в которых указывается счетчик повторений и код повторяющегося байта. Пре- дусмотрены особые служебные последовательности для неповторяющихся бай- тов, для конца строки и конца изображения. Более подробное описание алгоритма ВЕЕ можно найти в книге [6]. Следующим полем в структуре В1ТМАР1МЕОНЕАОЕК является поле Ь1'5ые1таде, содержащее размер массива пикселов в байтах. Если значением Ь1’Сотрге$$1’оп явля- ется В1_К6В, то поле Ь1'5ые1таде может быть равно нулю. В этом случае СВ1 вычис- лит размер изображения по ширине, высоте и количеству битов на пиксел. Но при использовании сжатия ВЕЕ, а также для файлов формата}РЕС или это поле должно содержать фактический размер данных изображения. Поля ЫХРе РегМеЕег и МУРе РегМеЕег содержат, соответственно, горизонтальное и вертикальное разрешение в пикселах на метр для целевого графического устрой- ства. Приложение может использовать это значение, чтобы выбрать растр из груп- пы доступных растров, который наиболее близок к характеристикам текущего уст- ройства вывода. Поле ЫСЕгОзес! задает количество элементов в цветовой таблице. Для НТВ-рас- тров с количеством цветов, не превышающим 256, нулевое значение поля ЫС1г11$ес1 означает максимально возможное количество цветов, указанное в табл. 3.4. Обыч- ный ВМР-файл, хранящийся на диске, должен содержать полную цветовую таб- лицу с максимальным количеством элементов. Неполная цветовая таблица мо- жет использоваться только в НТВ-растрах, которые хранятся в памяти. Поле ЫС1г1т ро1±ап1: определяет количество элементов, реально необходимых для отображения растра. Нулевое значение поля означает, что используются все цвета из цветовой таблицы. Следует отметить, что структура ВТТМАРТМЕОНЕАОЕК обычно называется «верси- ей 3» описания растра. Этот номер версии принято относить к тем аспектам АУш32
АР1, которые появились в \Упк1о\\ъ 3.1. Соответственно, новые возможности, до- бавленные в \У1пс1о\\ъ 95 и \Упк1о\\ъ 1ЧТ 4.0, именуются «версией 4», а новые воз- можности \У1пс1о\\ъ 98 и \У1пс1о\\ъ 2000 относятся к «версии 5». В \Упк1о\\ъ 95 и \Упк1о\\ъ МТ 4.0 появилась новая структура В1ТМАР\/4Н ЕАОЕК, а в \У1пс1о\\ъ 98 и \У1пс1о\\ъ 2000 была добавлена структура ВПМАРУБНЕАОЕК. Начало этих новых структур совпадает с типом В1ТМАР1М ЕОН ЕАОЕК. В структуре версии 4 появились новые поля для цветовых масок НОВА1, цветовых пространств, конеч- ных точек и гамма-коррекции (для поддержки 1СМ 1.02). В структуре версии 5 добавились новые типы цветовых пространств, рекомендации по воспроизведению и данные цветового профиля, ориентированные на поддержку 1СМ 2.0. Подробные описания этих структур приведены в М8ВМ. Цветовая таблица Цветовая таблица является массивом структур типа КС ВОН АО. Этот тип определен следующим образом: 1зурес1еТ ^адОСЮАО { ВУТЕ гдЬВ1ие; ВУТЕ гдЬбгееп; ВУТЕ гдЬВеб; ВУТЕ гдЬКезегуес!; } кбваидо; Поля структуры задают относительную интенсивность для синей, зеленой и красной составляющих цвета пиксела. Последнее поле в структуре является ре- зервным. Обычно цветовая таблица используется в О1В-растрах, содержащих не более 256 цветов. В этом случае каждый пиксел массива аВйтарВйз содержит индекс в цветовой таблице. Иногда цветовую таблицу включают и в В1В-растры формата Тгие Со1ог (Н1 Со1ог), что, на первый взгляд, кажется избыточным. Но это позволяет отображать указанные растры с требуемым качеством передачи цветов на устрой- ствах, не поддерживающих формат Тгие Со 1от, а работающих с цветовой палитрой. Количество элементов в цветовой таблице задается в поле ЫС1г11$ес1 заголовка информационного блока. Если это поле имеет нулевое значение, то используется максимальное количество элементов для заданной цветовой глубины. Массив пикселов Пикселы изображения хранятся в массиве пикселов аВйтарВйз. Если поле ЫНетдЫ: заголовка информационного блока содержит положительное значение, то строки развертки хранятся в обратном порядке, если же значение отрицательное — то в пря- мом порядке. Пикселы упаковываются внутри строки развертки для экономии места. Стро- ки дополняются битами до границы двойного слова. Количество байтов на строку вычисляется по следующей формуле: ЬуШРегЫпе = ((мШ * ЬПСоигД + 31) / 32) * 4; где мЛН — ширина изображения в пикселах, а ЬйСоип! — количество бит на пиксел. 1 В режиме КСВА цвета сохраняются как совокупность Кеб-, Сгеен-, В1не- и А1р11а-компонентов. 2 1СМ — 1н1е§га1ес1 Со1ог Мана^етен! — набор функций уровня АР1 и встроенных программных мо- дулей, предназначенный для сквозного управления цветопередачей при воспроизведении изобра- жений на разном периферийном оборудовании.
Для несжатых растров ОТВ, у которых поле Ь1’Сотрге$$1’оп имеет значение В1_К6В, обращение к отдельным пикселам массива является простой операцией, которая реализуется достаточно эффективно. Массив пикселов аВйтарВйз можно объя- вить как двумерный, что отражало бы семантику хранения в нем строк развертки изображения. Однако основные функции отображения ОТВ в контексте устрой- ства получают в качестве аргумента адрес одномерного массива. Поэтому удоб- ней объявлять аВйтарВйз как одномерный массив, а прямой доступ к пикселам осуществлять посредством преобразования пары индексов «логического» двумер- ного массива в эквивалентный индекс «физического» одномерного массива. При- мер прямого доступа к пикселам приведен в листинге 3.5. Упакованный аппаратно-независимый растр Если есть файл ОТВ, который следует загрузить в приложение, то можно считать его непосредственно в выделенный блок памяти. Такой блок называют упакованным (раскес[) В ТВ-растром. Он содержит все компоненты файла ОТВ, кроме заголовка ЬтГНеас1ег. Таким образом, упакованный ВТВ-растр начинается с заголовка информа- ционного блока ЬгтНеадег, за которым следуют массив масок, если он нужен, цветовая таблица (если она существует) и массив пикселов. В качестве указателя на упакован- ный ВТВ-растр в СОТ обычно используется указатель на структуру ВТТМАРТМ ЕО. Сле- дует отметить, что термин «упакованный» в данном случае не имеет никакого отно- шения к упаковке пикселов в строке развертки. Он просто означает, что компоненты ОТВ следуют друг за другом в смежных ячейках памяти. Довольно большое количество функций, входящих в состав АУш32 АР1, полу- чает и возвращает упакованные ВТВ-растры. Кроме того, упакованные НТВ-рас- тры используются в работе буфера обмена АУшсТомгз. Отображение О1В в контексте устройства Для вывода НТВ-растра на поверхность графического устройства предназначены функции 5±ге±сН 0ТВ115 и БеТОТВйзТоОеуке. Функция 5±ге1:сН ОТВйз имеет следующий прототип: Ш ЗбгебсШВШС НОС Мс, Ш ХВезб, Ш ХВезб, 1пб п0езШс1Ж Ш пВезбНещбб, 1пб Х8гс, 1пб Х8гс, Ш пЗгсШ сГЫп, 1пб п8гсНе1дбб, СОИЗТ 7010* 1 рВПз, // дескриптор контекста устройства // х-координата приемного прямоугольника // у-координата приемного прямоугольника // ширина приемного прямоугольника // высота приемного прямоугольника // х-координата исходного прямоугольника // у-координата исходного прямоугольника // ширина исходного прямоугольника // высота исходного прямоугольника // массив пикселов СОИЗТ В1ТМАРЖ0* 1рВП81пЕо, Н1МТ Жаде, ВШВВ (Мор // заголовок информационного блока // интерпретация цветовой таблицы // код растровой операции Координаты и размеры исходного прямоугольника1, относящегося к В ТВ-раст- ру, задаются в пикселах. Координаты и размеры приемного прямоугольника, 1 Термины «х-координата» и «у-координата» относятся к левому верхнему углу прямоугольника.
находящегося на поверхности графического устройства, задаются в логических еди - ницах. Манипулируя этими параметрами, можно получать различные преобразования при выводе изображения. Введем следующие обозначения: □ доВтр — ширина растра в пикселах; □ Н В т р — высота растра в пикселах; □ хз — значение аргумента для Х5гс; □ уз — значение аргумента для У5гс; □ доз — значение аргумента для пЗгсМсИН; □ Нз — значение аргумента для п5гсНеп’дЫ:; □ хд — значение аргумента для X0ез!:; □ уд — значение аргумента для УОез!; □ \л/д — значение аргумента для п0езШ1сИН; □ Нд — значение аргумента для п ОезШетдЫ:. Исходный прямоугольник определяет, какая часть растра выводится и осуще- ствляются ли при этом зеркальные отражения по горизонтали и по вертикали. Возможные значения для соответствующих параметров приведены в табл. 3.6. Таблица 3.6. Интерпретация параметров исходного прямоугольника Значения для [х$, уз, \л/з, Н$] Исходный прямоугольник 0, 0, доВтр, ИВтр Все изображение, исходная ориентация доВтр, 0, -доВтр, ИВтр Все изображение, зеркальное отражение по горизонтали 0, -ИВтр, доВтр, ИВтр Все изображение, зеркальное отражение по вертикали доВтр, ИВтр, -доВтр, -ИВтр Все изображение, зеркальное отражение по горизонтали и по вертикали 0, 0, доВтр, 1 Первая строка развертки 0, 0, 1, ИВтр Первый столбец развертки Приемный прямоугольник, задаваемый четверкой [хф уф доф Нс1], определяет место на поверхности графического устройства, в котором размещается выводи- мая часть растра, а также указывает, нужно ли масштабировать изображение. Для приемного прямоугольника также могут быть определены зеркальные отражения по горизонтали и по вертикали, которые осуществляются по правилам, аналогич- ным приведенным в табл. 3.6. Таким образом, окончательная ориентация зависит как от исходного, так и от приемного прямоугольника. Если приемный прямоугольник совпадает по размерам с исходным прямоу- гольником, то вывод растра осуществляется без масштабирования. В противном случае изображение либо увеличивается, либо уменьшается. Увеличение реали- зуется простым повторением пикселов, что обычно ведет к заметному ухудше- нию качества изображения. При уменьшении изображения несколько исходных пикселов преобразуются в один пиксел приемного прямоугольника. Алгоритм преобразования зависит от текущего режима масштабирования растра (Ьй:тар 81ге1сИтп& тобе), являющегося атрибутом контекста устройства. Для изменения режима масштабирования растра предназначена функция ЗеШге^сНВФМоде: Ш 8е^геШВШос1е(Н0С Нбс, Ш 18^геШМос1е);
Параметр 15±ге±сН Моде может принимать одно из значений, указанных в табл. 3.7. Таблица 3.7. Режимы масштабирования растра Константа (прежнее имя) Код Описание 8ТкЕТСН_АЫ08САЫ8 (В1_АСК01\1\Л/Н1ТЕ) 0x1 Значение по умолчанию. Пикселы комбинируются поразрядной логической операцией И 8ТкЕТСН_ОН8САЫ8 (\Л/Н1ТЕ0ЫВ1_АСК) 0x2 Пикселы комбинируются поразрядной логической операцией ИЛИ 8ТкЕТСН_0Е1_ЕТЕ8САЫ8 (СОЮНОЫСОЮк) 0x3 Сохраняется один пиксел, а остальные удаляются 8ТкЕТСН_НА1_РТ0ЫЕ (НА1РТ0ЫЕ) 0x4 Вычисляется средний цвет по нескольким пикселам Продолжим рассмотрение параметров функции 5±ге1:сН 01 Вйз. Параметр [рВйз задает адрес массива пикселов. Обратите внимание на то, что он определен как адрес одномерного массива. Параметру 1рВ1’ЫпГо передается адрес заголовка информационного блока. Здесь он имеет тип указателя на структуру В1ТМАР1МЕ0, которая определена в АУш32 (ЗВ1 следующим образом: ЦуребеТ 8-ЬгисЬ 1задВ1ТМАР1МГО { ВIТМАРIИЕОНЕАОЕЕ Ьт1Неабег; Р6ВСЮА0 Ьт1Со1ог8[1]; } В1ТМАР1МЕ0; Обратите внимание на то, что в этой структуре резервируется место лишь для одного элемента цветовой таблицы. Поэтому будьте осторожны при ее использо- вании в каком-то другом контексте. Параметр 1’11 заде определяет интерпретацию цветовой таблицы и обычно прини- мает значение 01В_В6В_С01_0В5, когда таблица содержит КСВ-составляющие цветов, либо 01В_РА1__С01_0В5, когда в таблице хранятся индексы логической палитры. Последний параметр, ддоКор, содержит код тернарной растровой операции. Эти операции будут рассматриваться позже, а пока мы будем использовать простей- шую растровую операцию 5ВСС0РУ, которая просто копирует пикселы источника в приемник. Есть и еще одна функция для вывода аппаратно-независимых растров — ЗеЮТВйзТоОеутсе. Она обеспечивает вывод В1В-растра полностью или частично, с сохранением исходной ориентации и масштаба. Можно было бы сказать, что возможности рассмотренной выше функции 51ге±сН ОТВкз полностью перекрыва- ют возможности 5еЮ1 ВйзТоОеут’се. Однако интерфейс последней функции позво- ляет копировать исходный растр частями, строка за строкой, что позволяет зна- чительно экономить оперативную память, если это необходимо. Правила использования этой функции приведены в М8ВК Примеры работы с растром О1В В этом разделе приводятся два приложения, демонстрирующие технику работы с аппаратно-независимыми растрами. В первом приложении растр формируется программно, после чего сохраняется в ВМР-файле. Во втором приложении растр загружается из ВМР-файла, а затем воспроизводится в окне программы. Подробности работы с В1В инкапсулированы в класс С++, который мы назвали К01’Ь. Этот класс предназначен для работы только с несжатыми растрами.
В него включен тот минимум средств, которого хватает для решения рассматрива- емых двух задач. Конечно, при желании можно легко расширить класс К01’Ь, доба- вив поля и методы, необходимые для решения других подзадач. Генератор ВМР-растра для АЫ51-символа Программа, приведенная в листинге 3.5, осуществляет генерацию битового образа АИ81-символа, заданного десятичным кодом, после чего сохраняет сгенерирован- ный образ символа в ВМР-файле. Битовый образ АМ81-символа формируется в формате Тгие Со 1ог с кодировкой 24 бит/пиксел. Цветовая таблица в растре отсут- ствует. В программе используется шрифт, содержащийся в контексте устройства по умолчанию, то есть $У$ТЕМ_ЕОМТ. Конечно, в будущем вы можете усложнить код программы, чтобы обеспечить выбор любого шрифта и любого размера сим- волов, а также формировать не отдельный символ, а полный набор из 256 симво- лов1. Код класса К01’Ь, как обычно, размещается в двух файлах. Интерфейс содержит- ся в файле КО1’Ь.И, а его реализация — в файле КОп’Ь.срр. Листинг 3.5. Проект СгеаРеСНагВтр ////////////////////////////////////////////////////////////////////// // КВ16.В #1пс1ибе <\л/1 пс1о\л/8. И> #1пс1ис1е <Т8Ргеат> #1пс1ибе <8Рг1пд> из1пд патезрасе зЦс!; с!азз КВ1Ь { риЫ 1с: КВ1Ь(): ~КВ1Ь(): В001 СгеаРеВ1Ь24( 1 пр м, 1 пр И, сопзР сбаг* РПеИате): уо16 8РогеВ1Ь24(); В001 Еоас1ГготГ11 е(сопзР сбаг* РПеИате): уо16 8еРР1хе1(1пР х, 1пР у, СОЕОРРЕЕ со!ог); 1 пр Вга\л/(НВС Нс1с, 1 пр хВзР, 1 пр уВзР, 1 пр мВзР, 1 пр ВВзР, 1 пр х8гс, 1 пр у8гс, 1 пр м8гс, 1 пр В8гс, ВШРВ гор): 1 пр беРШ с1РН () { геРигп мШИ; } 1 пр СеРНе1дГ|р() { геРигп Ие1дИР; } сопзР сбаг* беРЕггогО { геРигп еггог.с_8Рг(); } рг1уаРе: 1 пр \л/1с!РГ|: 1 пр 1пе1дГ|Р: 1 пр ЬуРезРегЕ1пе: В1ТМАРГ1ЕЕНЕАВЕР ТПеНеаб: // заголовок растрового файла В1ТМАРIИГОНЕАВЕР 1пТоНеаб: // заголовок информационного блока В1ТМАР1МЕОНЕАВЕР* р1пТоНеаб: ВУТЕ* аВ1РтарВ1Рз; // массив пикселов 1 пр Т11еНеаб81 хе; 1 пр 1пТоНеаб81хе: 1 Для создания более продвинутой программы желательно сначала ознакомиться с главами, посвя- щенными работе с меню и диалоговыми окнами.
ЮР 1таде8Юе; зРгЮд еггог; ЮзРгеат ЮрЕПе; оТзРгеат оыРЕПе; }; ////////////////////////////////////////////////////////////////////// // КОЮ.срр #ЮсЮс1е "КОЮЮ" КОЮ: : К0Ю( ) { Г11еНеаб31ге = за геоШВ1ТМАРГ1ОЕНЕАОЕЮ ; 011 еНеас!. ЬТТуре = 0x4642; аВ1РтарВ1Рз = МОЮ; } //============================================ КОЮ: :~К0Ю() { 10 (р!пТоНеаб) 6е1 еРе □ р1пТоНеас1; 10 (аВНтарВШ) ОеЮРе □ аВ1РтарВ1Рз; 10 (оыРЕПе) оыРЕПе.сЮзеС); } //========================================== ВООЕ КОЮ: :0геа0е0Ю24(1пР м, ЮР К, сопзР сНаг* ОПеИате) { \л/1сГЮ = \л/; НеадкгЕ = К; ЬуРезРегЕЮе = (ЫЮЮ * 24 + 31) / 32) * 4; 1таде8Юе = ЬуРезРегЕЮе * НеадРтЕ; ЮОоНеасЕЮе = зЮеоОСВПМАРЮЕОНЕАОЕР): 011еНеас1Ю08Юе = 011еНеас18Юе + ЮОоНеасЕЮе + ЬуРезРегЕЮе * Не1дНР; 011 еНеаб Ю0000В1Оз = 011еНеаб31ге + ЮОоНеабЗЮе; ЮОоНеабЮЮЮе = ЮОоНеабЗЮе; Ю0оНеас1Ю1Шс10Н = ЮНОЮ 1 ттГоНеаб Ю1 Не1 дИР = Ие1дИР; ЮОоНеас1Ю1Р1апез = 1; ЮОоНеаОЮ1В1РСоипР = 24; ЮОоНеас1Ю1Сотргезз1оп = В1_Р6В: Ю0оНеабЮ18Юе1таде = 1таде8Юе; аВ1РтарВ1Рз = пем ВУТЕ[1таде8Юе]; тетзеР(аВ1РтарВ1Рз, 0, 1таде8Юе); оиРЕ11е.ореп(Т1 ЮИате, Юз::оиР | Юз:ЮЮагу | ЮзхРгыпс); Ю (!оиРЕ11е) геРыгп ЕАЕЗЕ: еЮе геРыгп ТРОЕ; } //========================================== ВООЕ КОЮ::ЕоабЕготЕ! 1е(сопЮ сКаг* РПеИате) { ЮрЕ11е.ореп(Р11еМате, Юз::Ю | ЮзхЫпагу): Ю (!ЮрЕ11е) { еггог = "Неверное имя файла или каталога."; геРыгп ЕАЕЗЕ; } ЮрЕПе. геасК(с1ааг*)&Р11 еНеаб, Р11еНеас18Юе): Ю (Р11 еНеаб ЮТТуре != 0x4042) { еггог = "Это не ВМР-файл"; продолжение
Листинг 3.5 {продолжение} геТигп ГА15Е; } 1 пТоНеас!81 хе = Т11 еНеас!. ЬТОТТВЮз - Т11 еНеас1$1 хе; ЮТ Т11е81ге = Т11 еНеас!. ЬТ81 ге: 1таде81хе = Т11е81хе - (Т11 еНеас1$1 хе + 1 пТоНеас!81 ге); р!пГоНеас1 = (В1ТМАР11Х1Е0НЕА0ЕЕ*) (нем ВУТЕ Е1 пТоНеас181 геЦ); 1 прЕ11е. геасК (Юаг*)р1пТоНеас1, 1 пТоНеас181 хе); \л/1 ОТВ = р1пТоНеас1->Ы 1л/1 сИзН; НеадЕтЕ = р!пТоНеас1->Ы Не1 дКТ; аВЮтарВЮз = нем ВУТЕ[1таде81хе]; 1прЕ11е.геасК (Юаг*)аВ1ТтарВпТз, 1таде81хе); геТигп Тгие; } //============================== 1 пТ КОЮ: :Вга\л/(НВС Нс1с, 1 пТ хОзТ, 1 пТ уОзТ, 1 пТ ОзТ, 1 пТ КОзТ, 1 пТ х8гс, 1 пТ у8гс, 1 пТ м8гс, 1 пТ 68гс, ОШВО гор) { геТигп ЗТгеТсКО!В1 Тз(Мс, хВзТ, уВзТ, ОзТ, ИВзТ, х8гс, у8гс, м8гс, 68гс, аВ1ТтарВ1Тз, (С0М8Т В1ТМАРIМЕО*)рIпТоНеас!, ВIВ_Р8В_С0Е0Р8, гор); } //============================================ уоЮ КОЮ: :8еТР1хе1 (1 пТ х, 1 пТ у, СОЕОРРЕЕ соЮг) { 1пТ гом = у; 1 пТ СО1 = 3 * х; аВ1ТтарВ1Тз[гом*ЬуТезРегЕЮе + со1] = беТВУа! ие(соЮг); аВ1ТтарВ1Тз[гом*ЬуТезРегЕЮе + со!+1] = беТбУа! ие(соЮг); аВ1ТтарВ1Тз[гом*ЬуТезРегЕЮе + со!+2] = беТРУа! ие(соЮг); } //=============================== уоЮ КОЮ: :8ТогеОЮ24() { // Запись заголовка ВМР-файла оиТЕ11е.мг1Те((сИаг*Ж11еНеас1, Т11еНеас181хе); оиТЕ11е.мг1Те((сИаг*)&ЮГоНеас1, ЮТоНеасЕЮе); // Запись массива пикселов оиТЕ11е.мг1Те( (сИаг*)аВ1ТтарВ1Тз, ЮадеЗЮе); } ////////////////////////////////////////////////////////////////////// // СгеаТеСКагВтр.срр #1пс1ис1е <м1 пс1омз Ю> #1пс1ис1е "К1л1пс1Ю" #1пс1ис1е "КОЮЮ" #с1еТЮе АМ81_С00Е 65 #с1еТЮе Е1ЕЕ_МАМЕ "зутЬо! Ютр" КОЮ Ьтр; ЕРЕ80ЕТ САЕЕВАСК 1л1пс1Ргос(НШО, О1МТ, Р/РАРАМ, ЕРАВАМ); //============================================ ЮТ ШМАР1 1л1ЮМаЮ(Н1М8ТАМСЕ ЮпзТапсе, Н1М8ТАМСЕ КРгеуЮзТапсе, ЕРЗТР 1рСтс1ЕЮе, ЮТ пСтсЕЮом) { М2б тзд;
К1л/пс1 та1п1л1пс1("СгеаЬеСВагВтр", ВТпзЬапсе, пСггкОом, МпбРгос); мВ11е (бе^МеззадеС&тзд, М0ЕЕ, 0,0)) { ТгапзШеМеззадеС&тзд); ОтзраЬсВМеззадеС&тзд); } геЬыгп тзд.мРагат; } //========================================== ЕРЕЗОЕТ САЕЕВАСК 1л1пс1Ргос(ШМ0 Шпс1, 1ИМТ иМзд, АРАРАМ мРагат, ВРАГАМ 1Рагат) { НОС ВВС; РА1МТ8ТР0СТ р8; ТЕХТМЕТР1С От; 812Е 82; 8баЫ с Ш \л/1 сГЕВ, НеадН-В; СОЬОРРЕЕ СО1ОГ; Ш х, у; ВООЕ 18Е11 еСгеаЬеб; 8баЫс сВаг 11пе[2]; 8мПсВ (иМзд) { сазе Ш_СРЕАТЕ: ВВС = ОеОВС(ВШО); 0еЬТехЬМеЬг1сз(В0С, &Ш; ИпеЕО] = АМ51-С0ВЕ; 11пе[1] = 0; 0е0Тех0Ех0еп0Ро1п032(ВВС, 11пе, 1, &32); \л/1сИаВ = 82. сх; Ве1дВЬ = ЬтЛтНе1дВЬ; 1зЕ11еСпеаЬес1 = Ьтр.СгеабеО1Ь24(мШВ, Ве1 дВО, Г1ЕЕ_МАМЕ); 10 (!1зЕ11еСпеаЬес1) Ме88адеВох(В1л/пс1, "Файл " Г1ЕЕ_МАМЕ " не создан.", "Еггог", МВ_ОК); Ре1 еазеОС(В1л1пс1, ВОС); Ьгеак; сазе 1л/М_РА1МТ: ВОС = Ведп пРа1 пЬ(В1л1пс1, &рз); ЗеЬВкСо!ог(ВОС, РОВ(0,0,0)); ЗеЬТехЬСо!о г(В 0С, РОВ(255,255,255)); ТехЬОиШОС, 0, 0, 11пе, 1); // Сканирование изображения символа (от последней строки к первой) // Запись в массив пикселов 01В Сог (у = 0; у < Ве1дВЬ; ++у) { Сог (х = 0; х < \л/ШВ; ++х) { со1ог = 0еЬР1хе1(ВОС, х, Ве1дВЬ-1-у); Ьтр.8еЬР1хе1(х, у, со!ог); } } Ьтр.80оге01Ь24(); Епс1Ра1 пФ (В1л1пс1, &р8); продолжение
Листинг 3.5 {продолжение} Ьгеак; сазе Ш_0Е8ТШ: РозЬОьп ЬМеззаде(О); Ьгеак; с1еГаи!Ь: геЬигп ОеТШ пс1омРгос(Шпс1, иМзд, мРагат, 1Рагат); } геЬигп 0; } ////////////////////////////////////////////////////////////////////// Конечно, требуется сделать несколько пояснений по интерфейсу и реализа- ции класса К01Ь. Мы надеемся, что большая часть кода понятна благодаря тому, что вы внимательно прочли теоретический материал по В1В. Такие поля класса, как , Не1дН1:, Ьу1езРегИпе, ШеНеаф 1 пГоНеад и аВйтарВйз, не требуют особых комментариев. Но для чего нужен указатель на В1ТМАР1М ГОН ЕАОЕК, который хранится в поле р!пГоНеас1? Чуть позже, рассматривая метод ЕоадЕготЕ1Те, мы увидим, что это поле используется для хранения адреса блока памяти, выделя- емого из кучи при помощи оператора пем. Выделенный блок памяти предназначен для заголовка информационного блока и всего того, что может следовать за ним, напри- мер цветовых масок и/или цветовой таблицы. Дело в том, что формат загружаемого файла может быть любым, и поэтому заранее неизвестно, сколько памяти потребует- ся для указанных компонентов. Кроме традиционных для класса методов, таких как конструктор и деструк- тор, класс К01’Ь содержит методы, перечисленные в следующем списке: □ Метод Сгеа1:еО1’Ь24 создает в памяти 24-разрядный растр с заданными шириной и высотой. В процессе создания осуществляется инициализация полей заголовка растрового файла и заголовка информационного блока. После этого выделяется память для хранения массива пикселов. В завершение открывается дисковый файл с заданным именем, предназначенный для сохранения в нем создаваемого растра. □ Метод 5е1:Р1’хе1 осуществляет прямой доступ к пикселам в 24-разрядном растре. Цветовая информация для синей, зеленой и красной составляющих цвета пик- села записывается в соответствующие элементы массива аВйтарВйз. Адреса эле- ментов вычисляются через номер строки развертки у и номер пиксела в строке х. □ Метод 81оте 01Ь24 сохраняет созданный растр, записывая его на диск в формате ВМР. □ Метод 1_оас1 Егот Е11е загружает ВМР-файл с заданным именем, считывая его с диска в оперативную память. В процессе загрузки сначала читается заголовок растрового файла, затем вычисляются размер заголовка информационного бло- ка в совокупности с цветовой таблицей, который указывается в поле 1 пГоНеадЗп’ге. Помимо этого в поле 1таде51не указывается вычисленный размер изображения в байтах. Полученные величины используются для указания размеров динами- чески выделяемой памяти под заголовок информационного блока р!пГоНеас1 и под массив пикселов аВйтарВйз. После выделения указанной памяти соответ- ствующие компоненты растра читаются из файла. □ Метод Огам выводит растр на поверхность графического устройства, используя функцию 81:ге1:сНОТВйз.
Теперь можно перейти к рассмотрению модуля Сгеа1:еСНагВтр.срр. Обратите вни- мание на то, что в глобальной области видимости имеются следующие объявления: □ Объект Ьтр класса КО1’Ь. □ Макрос АМ$1_СООЕ, определяющий десятичный код А№1-символа. Значение 65 соответствует прописной букве «А» английского алфавита. □ Макрос Е11_Е_МАМЕ, задающий имя ВМР-файла, сохраняемого в текущем ката- логе программы. Разбирая код оконной процедуры \Л/пс1 Ргос, обратите внимание на следующие ее особенности: □ В блоке обработки сообщения ММ_СКЕАТЕ текстовые метрики текущего шрифта читаются в переменную 1:т при помощи функции 6еНех1:Ме1:лс$. Из всех метрик потребуется только высота символа, которая запоминается в переменной Не1дНС После этого формируется С-строка Н п е, предназначенная для последующего ото- бражения в клиентской области окна программы. В первый байт строки записы- вается код АМ$1_СООЕ, во второй — нуль. □ Теперь можно определить ширину символа, заданного кодом АМ$1_СООЕ. Для этого вызывается функция 6еНех1:Ех1:еп1:Ро1’ П132 с передачей ей в качестве второго ар- гумента строки Ипе. Искомая ширина запоминается в переменной \л/1 сИН. В завер- шение при помощи метода Ьтр.Сгеа1:еО1’Ь24 создается В1В-растр в памяти при- ложения. □ В блоке обработки сообщения ММ_РА1МТ установливаются белый цвет текста и черный цвет фона графических элементов. Затем вызывается функция ТехЮи!:, которая отвечает за прорисовку нужного символа. □ После этого изображение символа построчно сканируется от последней стро- ки к первой при помощи функции 6е±Р1хеI. Получаемый цвет пиксела заносится в растр вызовом метода Ьтр.5е1Р1хе1. Заполненный растр сохраняется на диске вызовом метода Ьтр.51:оге01’Ь24. Теперь проверьте работу приложения, откомпилировав и запустив проект1. Про- грамма должна создать файл зутЬоЬЬтр, содержащий изображение, показанное на рис. 3.6. Изображение на рисунке увеличено в 8 раз при помощи редактора М8 Р А ШТ. Сетка на рисунке также нанесена инструментами этого редактора. Рис. 3.6. Изображение из файла зутЬо1.Ьтр В рассмотренной программе используются не все методы класса К01’Ь. Так, мето- ды 1_оас1 Егот ЕНе и Ога\л/ будут применены только во втором примере. 1 Не забудьте добавить к проекту файлы КУУпс!.К и КУУпс1.срр, код которых приведен в листинге 1.2.
Просмотрщик ВМР-файла Программа, приведенная в листинге 3.6, является примитивным просмотрщиком ВМР-файлов. Спектр ее возможностей действительно не слишком велик. Она может воспроизводить только несжатые файлы. Это раз. У ее окна нет полос про- крутки, и поэтому приложение может показывать только те файлы, которые по- мещаются на экране дисплея. Это два. И наконец, имя файла задается при помо- щи макроса в тексте программы, что говорит о сугубо учебном характере этого проекта. В программе используется тот же класс КОПэ, с которым вы познакомились в предыдущем приложении. Естественно, код этого класса не будет приводиться повторно. Для сборки приложения вам просто нужно взять файлы К01’Ь.Н и КОФ.срр из предыдущего проекта и подключить их к данному проекту. Листинг 3.6. Проект ВтрН1е\/1е\л/ег ////////////////////////////////////////////////////////////////////// // ВтрЕ11е\/1е\л/ег.срр #1пс1ис1е <\л/1 пс1о\л/8. Н> #1пс1ис1е "К1л/пс1.ГГ #1пс1ис1е "КОЮ. К" #с1еТ1 пе ЕНЕ-МАМЕ "УоипдНаскег.Ьтр" КОИ Ьтр; ЕкЕЗОЕТ САЕЕВАСК 1л1пс1Ргос(НШ0, 01МТ, ПРАВАМ, ЕРАКАМ); //========================= 1пР ШМАР1 ШпМа1п(НIИЗТАИСЕ НпзТапсе, Н1МЗТАИСЕ НРгеу1пзТапсе, ЕРЗТк 1рСтс1Е1пе, 1пТ пСтсЗНсм) { М86 тзд; К1л1пс1 та1 п1л/пс1 ("ВтрЕ11 е\/1 е\л/ег", НпзТапсе, пСтсЕНом, Е1пс1Ргос); \л/М 1 е (0еТМеззаде(&тзд, МОЕЕ, 0, 0)) { Тгапз1аТеМеззаде(&тзд); 01зраТсНМеззаде(&тзд); } геТигп тзд.мРагат; } //========================^ ЕЕЕЗОЕТ САЕЕВАСК 1л1пс1Ргос(НШ0 ШпсЕ 01МТ иМзд, ПРАВАМ мРагат, ЕРАЕАМ 1Рагат) { НОС НОС; РА1МТЗТЕ0СТ рз; ВЕСТ гесТ; Ш ОХ, с1У; ИТ \л/8, Из, \л/сЕ М; зТаТ1с ВООЕ 1 зЕ11 еЕоабес!; змПсН (иМзд) { сазе Ш-СВЕАТЕ: НОС = ОеТОС(Шпб); 1 зЕ11 еЕоас1еб = Ьтр.Еоас1ЕготЕ11е(Е1ЕЕ_МАМЕ);
(! 18Е11 е1_оас!ес1) { МеззадеВох(Шпс1, "Файл " ЕНЕ МАМЕ " не загружен.", "Еггог", МВ-ОК); МеззадеВох(Шпс1, Ьтр.6еФЕггог(), "Еггог", МВ_ОК); Ьгеак; } // Подогнать размеры окна программы под размер растра Ьтр СМС11 егОесН Шпс1, &гесФ); с1Х = Ьтр.6еШс1Ш) - гесФ.г1 дПФ; с1У = Ьтр.6еФНе1дПФ() - гесФ.ЬоНот; ОеШ пс1омКесШ1л1пс1, &гесФ); 1пТ1аФеКесФ(&гесФ, с1Х/2, с1У/2); МоуеШпс1ом(Шпс1, гесФ.1еТФ, гесФЛор, гесФ.г1дПФ-гесФ.1еТФ, гесФ.ЬоФФот-гесФ.Фор, ТИПЕ); Ке1еазеВС(Шпс1, ЬОС); Ьгеак; сазе Ш_РА1МТ: ЬОС = ВедппРаппФ(Шпс1, &рз); \л/с1 = мз = Ьтр.6еФШс1ФП(); Е1с1 = Н8 = Ьтр.6еФНе1дПФ(); Ьтр.0гам(Ь0С, 0, 0, \л/с1, Пс1, О, О, мз, Из, ЗЕССОРУ); Епс1Ра1пФ(Шпс1, &рз); Ьгеак; сазе 1л1М_0Е8ТР0Х: РозФ0ы1ФМеззаде(0); Ьгеак; с1еФаы1Ф: геФигп ВеФШпсЫРгос(Шпс1, иМзд, мРагат, 1Рагат); } геФигп 0; } ////////////////////////////////////////////////////////////////////// Возможно, первое, на что вы обратили внимание, — это макрос, задающий имя ВМР-файла, предназначенного для загрузки и отображения в окне программы. В данном случае файл называется УоипдНаскег.Ьтр. Но где же его взять? Можно заглянуть на сайт издательства «Питер» \л/\л/\л/.р1'Фег.сот и найти там листинги приме- ров к данной книге. В соответствующем каталоге нетрудно отыскать и этот графи- ческий файл. Кроме того, можно выбрать любой другой ВМР-файл, поместить его в каталог проекта и записать его имя в указанном макросе. В остальном код приложения достаточно прозрачен. Работающая программа выводит на экран изображение, показанное на рис. 3.7. Следует обратить внимание на аргументы метода Ьтр. Огам, который реализован при помощи функции ЗФгеФсНОТВйз. Значения аргументов определяют, что изобра- жение выводится в своем исходном размере (без масштабирования) и в своей ис- ходной ориентации. Давайте проведем следующий эксперимент для иллюстрации возможностей функции 5±ге±сН 01В115. Нужно внести небольшие изменения в оконную процедуру. Добавьте в нее определения следующих переменных: 1пФ хсЮ, ус10, хсП, усП, хс12, ус12, хбЗ, убЗ;
Рис. 3.7. Вывод растра из файла УоипдНаскег.Ьтр при помощи программы ВтрН1е\/1е\л/ег Рис. 3.8. Результат работы модифицированной программы ВтрН1е\/1е\л/ег
А блок обработки сообщения ММ_РА1МТ замените новым фрагментом кода: сазе Ш_РА1МТ: РОС = Вед1 пРап ггЬ(Шпс1, &рз); №8 = Ьтр.6еШс1Ш); Из = Ьтр.6еЬНе1дШ); \л/с1 = мз/2 - 4; М = Ьз/2 - 4; хс10 = 2; убО = 2; хс11 = мз/2 + 2; ус11 = 2; хс12 = 2; уб2 = Ьз/2 + 2; хбЗ = мз/2 + 2; убЗ = Ьз/2 + 2; 8еЬ8ЬгеЬсЬВ1ЬМос1е(Ь0С, 8ТВЕТСН_НАЬЕТ0МЕ); Ьтр. О гам (РОС, хсЮ, усЮ, ж1, Ьс1, мз, 0, -мз, Из, 8РСС0РУ); Ьтр.Ога\л/(НОС, хс11, ус11, \л/с1, М, 0, 0, мз, Из, 8РСС0РУ); Ьтр.Ога\л/(РОС, хс12, ус12, \л/с1, М, мз, Из, -мз, -Из, 8РСС0РУ); Ьтр.Ога\л/(НОС, хбЗ, ус13, \л/с1, Ьс1, 0, Из, мз, -Из, 8РСС0РУ); Епс1Ра1пЬ(Шпс1, &рз); геЬигп 0; Теперь программа выведет картинку, показанную на рис. 3.8. Аппаратно-зависимые растры Аппаратно-зависимый растр (ВВВ) представляет собой объект СВ1, который фун- кционирует под управлением СВ1 и драйверов устройств. Он обладает тем же ста- тусом, что и объекты логического пера или логической кисти. При создании ВВВ- растра \У1пс1о\\ъ определяет его внутренний формат и выделяет память из области памяти СВ1. После этого все операции с растром выполняются через дескриптор объекта СВ1, имеющий тип НВ1ТМАР. ВВВ-растры также часто называют «растро- выми объектами СВ1». Напомним, что особенностью формата НОВ является то, что система всегда создает битовый образ в памяти с учетом характеристик конк- ретного графического оборудования. Создание ООВ-растра в программе АУш32 СВ1 предлагает следующие функции для создания растровых объектов СВ1: НВ1ТМАР СгеаЬеВ1 Ьтар( 1 пЬ п1л1ШЬ, Ш пНе1дЬЬ, ЫМТ сР1апез, ЩИТ сВПзРегРе!, С0И8Т 7010* 1руВПз); НВ1ТМАР СгеаЬеВ1 Ьтар1пс11 гесб(С0М8Т В1ТМАР* 1рЬт); НВ1ТМАР СгеаЬеСотраШ1еВ1Ьтар(Н0С Ьс1с, Ш пШсЮЬ, Ш пНе1дШ; Функция Сгеа1еВйтар создает растр НОВ с заданными шириной п, высотой пНетдЫ:, количеством плоскостей сР1апе$, количеством битов на пиксел сВИзРегРеЦ после чего инициализирует массив пикселов данными из массива 1руВНз. Параметры сР1апе$ и сВйзРегРеI определяют лишь минимальные требования к растру. В свою оче- редь, СВ1 поручает драйверу устройства выбор ближайшего доступного формата с кодировкой по крайней мере сР1апе$ х сВйзРегРе1 бит/пиксел. Функция Сгеа^еВИтарТпсН гее! делает практически то же самое, но параметры ра- стра передаются ей через структуру типа В1ТМ АР: ЬуресМ зЬгисЬ ЬадВТТМАР ЕОМЗ ЬтТуре; // ЕОМЗ ЬтШс1Ш // ЕОМЗ ЬтНе1д1тЕ: { тип растра (поле должно быть равно 0) ширина растра в пикселах // высота растра в пикселах
1_(Ж Ьт1л/1 сИзГ|ВуЬе8; // число байтов на строку развертки //(значение должно быть четным) ШЕО ЬтР1апе8; // число цветовых плоскостей ШРО ЬтВ1бзР1 хе!; // число битов на пиксел 1Р\/0Ю ЬтВПз; // указатель на массив битов с данными инициализации } В1ТМАР; К сожалению, обе функции имеют существенные ограничения, влияющие на их практическое применение. Они позволяют создавать либо монохромный растр, инициализированный данными из массива, либо цветной растр, но без инициализации. При создании растра без инициализации параметр [руВйз или поле [рЬт.ЬтВйз должны иметь значение МОП. Попытка создать цветной растр, инициализированный данными из конкретного массива, как правило, заканчи- вается неудачей, так как созданный битовый образ оказывается несовместимым с конкретным графическим устройством. Функция Сгеа^еСотраВ’ЫеВйтар предназначена для создания ВВВ-растра с заданными шириной и высотой, который заведомо совместим с контекстом уст- ройства Нс1с. Но битовый образ, создаваемый этой функцией, также является не- инициализированным. Создание ООВ-растра из упакованного О1В Итак, рассмотренные выше функции мало пригодны для создания инициализиро- ванных цветных ВВВ-растров. В то же время аппаратно-независимый растр обла- дает хорошими средствами для описания стандартных цветовых форматов. Поэто- му СВ1 содержит функцию Сгеа1еОТВйтар, которая создает инициализированный В В В-растр на базе В1В, то есть, по существу, преобразует упакованный В1В в В В В. Функция имеет следующий прототип: НВ1ТМАР СгеабеО1ВПтар(НОС Мс, СОИЗТ В1ТМАР1ИЕОНЕАОЕЕ* 1 рЬпгп б, ОШЕО ЙМпП, СОИЗТ 7010* 1рЫпП, СОИЗТ В1ТМАР1ИЕ0* 1 рЬплп , 0П\1Т ГыОзаде): В своей реализации она сначала создает ВВВ-растр, совместимый с контек- стом устройства Ндс, а затем преобразует В1В в ВВВ. Параметр 1рЬгтН содержит адрес заголовка информационного блока В1В. Параметр ГсМпй определяет необхо- димость инициализации ВВВ-растра. Если он равен СВМ_1М1Т, то следующие три параметра ссылаются на упакованный В1В-растр, используемый для инициализа- ции. Параметр 1рЫпй указывает на массив пикселов, а параметр 1рЬгт — на заголо- вок информационного блока В1В, преобразованный к типу В1ТМАР1МЕ0. Последний параметр, ГиОзаде, сообщает, содержит цветовая таблица индексы палитры (01В_РА1__С01_0К5) или цвета НОВ (01В_К6В_С01_0К5). Честно говоря, не совсем понятно, для чего нужна эта функция, поскольку если В1В-растр загружен, как, например, в коде листинга 3.6, то никаких проблем с его выводом не будет. Для этого вызывается функция 5±ге±сН 01ВЙ8 (в классе К01’Ь она инкапсулирована в метод Огам). Функция СтеаЕе01 Вйтар позволяет преобразовать этот же В1В-растр, предварительно загруженный в память, в объект ВВВ-растра, который затем может быть отображен при помощи функции ВЙВЙ (подробности вывода ВВВ-растров рассматриваются ниже). Но остается вопрос: а что дает эта более сложная схема? Все же основные способы создания ВВВ-растров сводятся к рассматриваемым ниже двум функциям.
Загрузка ООВ-растра из ресурсов приложения В АУхпбомгз-программировании В1В-растры (ВМР-файлы) часто присоединяются к модулю в виде ресурса1, а затем загружаются в программу функцией 1_оас1 Вйтар, которая преобразует растр к типу НВ1ТМАР: НВ1ТМАР Ьоас1ВПтар( Н1М5ТАМСЕ МпзЕапсе, // дескриптор экземпляра приложения 1РСТЗТР 1рВПтарМате // имя растрового ресурса ); Как показано в главе 5, имя ресурса может быть либо строкой с завершающим нуль-символом, например 82ВтрМате1, либо целочисленным идентификатором, например ЮВ_1. В первом случае параметру [рВйтарМате передается адрес строки 82ВтрМате1, а во втором — выражение, преобразующее целочисленный идентифи- катор к типу 1РСТ5ТК при помощи макроса МАКЕ1МТВЕ5О11 ВСЕ, то есть параметр полу- чает значение МАКЕ1МТКЕ5ОиКСЕ(ЮВ_1). Растровые ресурсы хранятся в модулях АУш32 в формате упакованного В1В- растра. Функция 1_оас1 Вйтар находит растровый ресурс, загружает его в память, получает дескриптор упакованного В1В-растра и затем на его базе создает ВВВ- растр, совместимый с текущим экранным режимом. Пример использования функции 1_оас1 Вйтар можно найти в листинге 5.4 и в лис- тинге 12.1. Приложение может также вызвать функцию Еоас1ВП:тар для загрузки предопре- деленных в системе растров с изображениями различных кнопок, которые исполь- зует АУш32 АР1. Для этого необходимо передать параметру ЫпзЕапсе значение МОП, а второму параметру — одно из значений ОВМ_ВТМСОВМЕВ$, ОВМ_ВТ512Е и других аналогичных констант. Загрузка ООВ-растра из ВМР-файла Если по какой-то причине вам удобно загружать битовый образ не из ресурса, а не- посредственно из ВМР-файла, то можно воспользоваться многопрофильной функ- цией ЕоасИтаде, предназначенной для загрузки пиктограмм, курсоров и растров. Функция имеет следующий прототип: НАМО1Е Еоас11таде( Н1М5ТАМСЕ МпзЦ, ЕРСТЗТЕ 1р82Мате, 01 ИТ иТуре, 1пТ сх0ез1гес1, 1пТ су0ез1геб, ЮТ ГиЕоас! // дескриптор экземпляра приложения // имя или идентификатор изображения // тип изображения // ширина изображения // высота изображения // флаги загрузки Имя загружаемого растра передается параметру [рзгМате, при этом параметр Ги1_оас1 должен иметь значение 1_В_1_0А0ЕВ0МЕ11_Е, а параметр иТуре — значение 1МА6Е_В1ТМАР. Установка нулевых значений для параметров схОезп’гес! и суОезйес! приводит к использованию текущих размеров загружаемого растра. В случае успешного завершения функция возвращает дескриптор полученно- го ВВВ-растра. Если загрузить изображение не удалось, то возвращается значе- ние МОП. Пример использования функции ЕоасИтаде приведен в листинге 3.7. 1 Более подробно ресурсы АУтболуз-приложения рассматриваются в главе 5.
Получение информации о графическом объекте После создания или загрузки изображения в формате ВВВ приложению необхо- димо узнать его размеры, чтобы использовать их для отображения растра. Для решения этой проблемы можно вызвать функцию ОеЮЬ^ес!:, имеющую следующий прототип: Ш беШэцесП Н60ЮВ6 НдсПоЬз, // дескриптор графического объекта Ш сЬВиГГег, // размер буфера для информации об объекте ВРУОЮ 1руОЬцесГ // адрес буфера для информации об объекте Функция является универсальной, поэтому в параметре НдсИоЬ] может быть ука- зан дескриптор одного из таких объектов, как перо, кисть, шрифт, палитра или растр типа НВ1ТМАР. Информацию об объекте функция помещает в буфер с адресом 1руОЬ]’ес1:. В табл. 3.7 показано, каким должен быть тип буфера в зависи- мости от типа объекта. Таблица 3.7. Тип буфера, на который указывает параметр 1руОЬ]ес1: Тип объекта Тип буфера НВ1ТМАР НВ1ТМАР, возвращенный функцией Сгеа1еО1В8есйоп В1ТМАР О1В8ЕСТ1ОМ, если поле сЬВиГГег установлено в значение 512еоГ(О1В8ЕСТЮ1\1), или В1ТМАР, если поле сЬВиГГег установлено в значение 512еоГ(В1ТМАР) НРАЕЕТТЕ НРЕЫ НРЕ1М, возвращенный функцией Ех1Сгеа1:еРеп НВк118Н НЕОМТ УУОНО — количество цветов в логической палитре ЕОСРЕ1М ЕХТЮСРЕЫ 1_О6Вк118Н ЬОСРОМТ Если при вызове функции параметр 1руОЬ] есЕ равен ИII И, то функция возвраща- ет количество байтов, которое необходимо зарезервировать в буфере 1руОЬ]’ес1: для размещения информации об объекте. Если параметр 1руОЬ]ес1: не равен МОП, то при успешном завершении функция возвращает количество сохраненных в буфере байтов. При возникновении ошиб- ки функция возвращает нулевое значение. Когда функция 6е1:0Ь]ес1: применяется к объекту типа НВ1ТМАР, то возвращае- мая информация содержит данные только о ширине и высоте растра, а также о цветовом формате изображения. Поле ЬтВйз структуры В1ТМАР при этом равно нулю. Отображение ООВ-растра Несмотря на то что ВВВ-растры играют важную роль в АУшсклуз-программирова- нии, в \Ут32 АР1 нет функции для непосредственного отображения НОВ. В СВ1 задача отображения НОВ решается обобщенно, как задача пересылки пря- моугольного массива пикселов с одного графического устройства (устройства- источника) на другое графическое устройство (устройство-приемник). При отображении растра устройством-приемником обычно является экран дисплея, и его представителем выступает контекст дисплея.
А что в этом случае является устройством-источником? В роли этого устрой- ства выступает виртуальное графическое устройство, имитируемое в памяти в виде растра. Для его представления в СН1 предусмотрен контекст устройства в памя- ти (или совместимый контекст). В главе 2 была дана краткая характеристика это- го вида контекста устройства. Напомним, что совместимый контекст создается следующей функцией: НОС СгеабеСотраШ1еВС (НОС Мс); где Нс1с — дескриптор существующего контекста устройства, с которым должен быть совместим создаваемый контекст. Функция Сгеа^еСотраВЫеОС может использовать- ся только с устройствами, поддерживающими растровые операции. Чаще всего Ндс является дескриптором контекста дисплея, который относится к клиентской облас- ти окна приложения. Если этот параметр имеет значение МОП, то функция создает контекст в памяти, совместимый со всем экраном дисплея. Если функция заверша- ется успешно, то она возвращает дескриптор совместимого контекста устройства. Когда совместимый контекст устройства создан, с ним по умолчанию связан монохромный растр из одного пиксела. Поэтому, прежде чем использовать этот контекст, необходимо выбрать в него ННВ-растр при помощи функции 5е1ес1:ОЬ]’есЕ. Типичная схема отображения ННВ-растра с дескриптором Н Вйтар на графичес- кое устройство с дескриптором контекста устройства НОС выглядит следующим образом: // Определить объект совместимого контекста НОС НМетОС; // Создать совместимый контекст НМетОС = СгеабеСотраШ1е0С(Н0С); // Выбрать ООВ-растр в совместимый контекст 8е1есбОЬдесШМетВС, НВПтар); // Скопировать изображение из НМетОС в НОС ВИВИ (НОС, 0, 0, ЬтрШсЮН, ЬтрНещНО, НМетОС, О, О, 8ВСС0РХ); // Удалить совместимый контекст 0е1ебе0С(НМет0С); Очевидно, что устройство-источник и устройство-приемник в рассмотренной схеме можно поменять местами. В этом случае будет осуществляться копирова- ние изображения с устройства НОС на устройство НМетОС, то есть в ННВ-растр. Функции блиттинга Для поддержки блиттинга1 СН1 использует функции ВИВИ и 5!:ге±сН В1±. Рассмот- рим подробнее первую из них. Название функции ВИВИ происходит от слов «Ы1 Ыоск СгапДег», означающих «перенос битового блока». На самом деле функция ВДВ 11: позволяет делать намного больше, чем просто перенос блока пикселов. Эта функция может выполнить одну из 256 логических растровых операций над тремя наборами пикселов. Прототип функции имеет следующий вид: В001 ВИВИ (НОС МсВезб, Ш пХВезб, Ш пУВезб, Ш п1л/1 ббб, Ш пНелдкгЬ, НОС НбсЗгс, Ш пХЗгс, Ш пХЗгс, ВШВВ (Мор); Функция ВДВ 11: передает прямоугольный блок пикселов с поверхности устрой- ства-источника НдсЗгс на поверхность устройства-приемника НдсОезЕ Исходный прямоугольник определяется параметрами пХ5гс, пУ5гс, п^’сЙН, пНетдЫ: в логической 1В компьютерной графике блиттингом называют копирование изображений в память видеоустройств.
системе координат контекста устройства-источника. Приемный прямоугольник определяется параметрами пХОез!, пУОез!:, п\Л/1д1:Н, п Нет’дЫ: в логической системе ко- ординат контекста устройства-приемника. Оба контекста устройства должны под- держивать растровые операции КС_В1ТВ1_Т. Из списка параметров функции видно, что ширина и высота исходного и прием- ного прямоугольников задаются одними и теми же параметрами — п\Л/1д1:Н и пНетдЫ:. Поэтому если в устройстве-приемнике и в устройстве-источнике уста- новлен режим отображения по умолчанию ММ_ТЕХТ, в котором логические единицы совпадают с физическими единицами, то перенос блока пикселов происходит без масштабирования. В других режимах отображения исходный и приемный прямоугольники могут иметь разные размеры в системах координат устройства. В таком случае исход- ное изображение масштабируется по размерам приемного прямоугольника. Контексты устройства-источника и устройства-приемника не обязательно должны быть разными. Функцию ВИВИ можно применять и для переноса блока пикселов в пре- делах той же поверхности, на которой находится исходный прямоугольник. Последний параметр с!\л/Кор задает тернарную растровую операцию, то есть спо- соб объединения исходного пиксела, приемного пиксела и кисти для формирова- ния нового значения приемного пиксела. Эти операции будут рассмотрены позже, а пока что будет использоваться простейшая растровая операция ЗКССОРУ, которая просто заменяет пикселы приемника значениями пикселов источника. Вторая функция блиттинга З^ге^сН В К имеет следующий прототип: В001 8ЦгеЦсГ|В1 Ц(НОС МсОезЬ, Ш пХОезЬ, Ш пХОезЬ, Ш п1л/1 сЕЬОезЬ, Ш пНе1 дЬЬОезЬ, НОС ЫсЗгс, 1пЬ пХ8гс, Ш пХЗгс, 1пЬ пШ сЕИЗгс, Ш пНе1д1тЕ8гс, ОШРО (Мор); Она делает практически то же самое, что и функция ВИВИ, но в списке парамет- ров размеры исходного и приемного прямоугольников определяются независимо друг от друга. Это позволяет задавать масштабирование при переносе блока пиксе- лов, когда исходный и приемный прямоугольники имеют разные размеры в логических координатах. Еще одно различие — функция может осуществлять зеркальные отражения изображения так же, как это делает функция З'Ьге'ЬсН 01ВИз, если параметры исходного (или приемного) прямоугольника задавать по прави- лам, приведенным в табл. 3.6. Пример работы с СОВ Программа, приведенная в листинге 3.7, осуществляет загрузку ВВВ-растра из ВМР- файла, после чего отображает рисунок в своем окне. По существу, здесь решается та же задача, что и в программе ВтрЕ11е\/1’е\л/ег (см. листинг 3.6), но другими средст- вами. Листинг 3.7. Проект ВтрН1е\/1е\л/ег2 ////////////////////////////////////////////////////////////////////// // ВтрП 1 е\/1 е\л/ег2. срр #1пс1ис1е <\л/1 пс1о\л/8. Ь> #1пс1ис1е "К1л1пс1.И" #с1еТ1 пе ЕНЕ-МАМЕ "ХоипдНаскег .Ьтр" ЬРЕЗЫТ САЕЕВАСК ШЬРгос(НШО, ЫМТ, ПРАВАМ, ЬРАВАМ);
//==================================================================== 1пТ ШИАР1 ШпМапп(НIИЗТАМСЕ Шнапсе, Н1МЗТАМСЕ ИРгеу1пзТапсе, ЕРЗТР 1рСтс1Е1пе, 1пТ пСтбЗИом) { М86 тзд; К1л1пс1 та1п1л!пс1("ВтрП1е\/1е\л/ег2", ЫпзТапсе, пСтбЗИом, МпбРгос); \л/Н11е (СеТМеззадеС&тзд, МОЕЕ, 0,0)) { ТгапзШеМеззадеС&тзд); ОтзраТсИМеззадеС&тзд); } геТигп тзд.мРагат; } //========================================== ЕРЕЗЕЕТ САЕЕВАСК ШбРгос(НШ0 Шпб, Е1МТ иМзд, ПРАВАМ мРагат, ЕРАВАМ 1Рагат) { НОС НОС; РА1М1ТЗТВ0СТ рз; ВЕСТ гесТ; зТаЫс 1пТ Ьтр1л/1 6ТИ, ЬтрНе1дНТ; зТаЫс НВ1ТМАР ИВПтар; // дескриптор битового образа Н1МЗТАМ1СЕ ЫпзТ; НОС ИМетОС; // дескриптор совместимого контекста В1ТМАР Ьт; 1’пТ 6Х, 6Х; зм1ТсИ (иМзд) { сазе Ш_СВЕАТЕ: НОС = СеТОС(ИШб); ЫпзТ = (НIМЗТАМ1СЕ) ЗеТС! аззЕопд(Н1л1пб, ЗСЕ_НМ00НЕЕ); ИВПтар = (НВ1ТМАР)ЕоабТтаде(ИIпзТ, Р1ЕЕ_МАМЕ, 1МА6Е_В1ТМАР, 0, 0, ЕВ-ЕОАОРВОМР1ЕЕ); И (ИВПтар == ИЕ1ЕЕ) { МеззадеВох(И1л/пб, "Файл " Е1ЕЕ_ИАМЕ " не загружен", "Еггог", МВ_ОК); Ьгеак; } СеТОЬдесТ(НВ1 Ттар, зПеоТ(Ьт), &Ьт); ЬтрШбТН = Ьт.ЬтШбТН; ЬтрНе1дИТ = Ьт.ЬтНе1дНТ; // Подогнать размеры окна программы под размер растра НВПтар 6еТС11 епТВесТ( Н1л1пб, &гесТ); 6Х = ЬтрШбТН - гесТ.г1дНТ; 6Х = ЬтрНе1дНТ - гесТ.ЬоНот; СеШ пбомВесТ (Н1л1пб, &гесТ); 1пС1аТеВесТ(&гесТ, 6Х/2, 6Х/2); МоуеШпбом(Н1л1пб, гесТ.1еТТ, гесТ.Тор, гесТ.г1дНТ-гесТ.1еТТ, гесТ.ЬоТТот-гесТ.Тор, ТВОЕ); Ве1еазеОС(НМпб, НОС); Ьгеак; сазе 1л/М_РА1МТ: НОС = ВедппРаппТ(И1л1пб, &рз); // Создать совместимый контекст ИМетОС = СгеаТеСотраЫЫеОС(ИОС); продолжение
Листинг 3.7. {продолжение} II Выбрать ИВПшар в совместимый контекст 8е1есбОЬаесШМетВС, ИВПтар); // Скопировать изображение из ЬМетОС в ВВС ВИВИ (ВОС, О, 0, ЬтрШсШ ЬтрНе1дЬЬ, ЬМетОС, О, О, 8ВСС0РУ); Ве1ебеОС(ЬМетОС); ЕпбРаппб(Шпс1, &рз); Ьгеак; сазе Ш_0Е8ТВ0У: РозЬОыПМеззадеСО); Ьгеак; с1еГаы1Ь: гебыгп ОеГШ пбомРгос(Шпс1, иМзд, мРагат, 1Рагат); } гебигп 0; } ////////////////////////////////////////////////////////////////////// Следует обратить внимание на несколько интересных моментов. Так, ВВВ- растр НВ1Ттар загружается в блоке обработки сообщения ММ_СКЕАТЕ при помощи функции ЕоасИтаде. Дескриптор экземпляра приложения Н1п$Е, который нужно передать в функцию ЕоасИтаде, определяется предварительным вызовом функции Се^СЕаззЕопд(Н\Л/пф 6СЕ_НМ00Е1ЕЕ). После загрузки растра его ширина и высота нахо- дятся при помощи функции беЮЬ^ес!:. Отображение растра в клиентской области окна реализовано в блоке обработ- ки сообщения ММ_РА1МТ по традиционной схеме. Сначала создается совместимый контекст (контекст устройства в памяти) Н Мет ОС, после чего в него выбирается битовый образ НВ1’Етар. Затем блок пикселов копируется при помощи функции В1ЕВ1Е. В завершение совместимый контекст удаляется. Результат выполнения этой программы выглядит так же, как и результат вы- полнения программы ВтрЕйеУтемег (см. рис. 3.7). О1В-секции Каждый из рассмотренных выше типов растров обладает своими недостатками и достоинствами. Сравнительная характеристика ВВВ и В1В приведена в табл. 3.8. Таблица 3.8. Сравнение растров ООВ и О1В Характеристика ООВ 01В Независимость от аппаратуры Рисование на растре средствами 601 Прямой доступ к массиву пикселов со стороны приложения Максимальный размер растра Нет Да Поддерживается Не поддерживается Отсутствует Имеется 48 Мбайт (используется Ограничен объемом виртуаль- системная память) ного адресного пространства (используется память уровня приложения) В АУш32 АР1 появился новый тип растра, объединяющий достоинства ВВВ и В1В.
БТВ-секцией (НТВ зесбюп) называется НТВ-растр, который позволяет произво- дить непосредственное чтение и запись как со стороны приложения, так и со сторо- ны СНТ. Массив пикселов НТВ-секции хранится либо в виртуальной памяти при- ложения, либо в файле, отображаемом на память, который в среде разработчиков операционной системы АУтсТомга называется секцией. Этим, видимо, объясняется происхождение термина «НТВ-секция». НТВ-секция, как и аппаратно-зависимый растр, является объектом СНТ. Пос- ле ее создания СНТ возвращает дескриптор НТВ-секции, относящийся к типу НВ1ТМАР. Завершив работу с НТВ-секцией, приложение должно вызвать функцию Ое1е1еОЬ]ес1:, чтобы освободить связанные с нею ресурсы. Создание О1В-секции Для создания НТВ-секции используется функция Сгеа1е01 В5есВоп: НВ1ТМАР Сгеабе01В8есб1оп( НОС Мс, // дескриптор контекста устройства С0И8Т В1ТМАР1МЕ0* рЬгп1, // адрес заголовка информационного блока ЮТ Жаде, // тип данных в цветовой таблице 7010** рруВПз, // адрес массива пикселов НАМОЮ И8есТ1оп, // дескриптор объекта файла, отображаемого на память ОШВО сЫЖзеб // смещение массива пикселов внутри отображаемого файла ); Основные характеристики создаваемого растра задаются в структуре типа ВТТМАРТМЕО, адрес которой передается второму параметру функции. В этой струк- туре необходимо указать ширину, высоту, цветовую глубину и тип сжатия. НТВ- секции поддерживают только тип В1_К6В, то есть только несжатые растры. Также задаются битовые маски, если они нужны, и цветовая таблица, если она исполь- зуется. Параметр 1’11$аде определяет тип данных, содержащихся в массиве ЬгтСо1ог$, ко- торый является членом структуры В1ТМАР1И Е0. Если параметр 1’11$аде имеет значе- ние 01В_РАЬ_С0ЮК$, то цветовая таблица ЬгтпСо1ог8 содержит индексы логической палитры. Значение 01В_К6В_С01_0К5 указывает, что цветовая таблица содержит зна- чения в формате Вб В. Параметр рруВйз содержит адрес переменной-указателя, в который СНТ зано- сит адрес массива пикселов НТВ-секции. Параметр НЗесВоп может быть равен МОП. В этом случае СНТ выделяет память под массив пикселов из виртуальной памяти приложения, а параметр с1 хл/ОНъеЕ игно- рируется. Если параметр НЗесВоп не равен МОП, то он должен быть дескриптором объекта файла, отображаемого на память, который был создан вызовом функции Сгеа±еЕ11еМарр1 пд с флагами РА6Е_ВЕА0МВ1ТЕ или РА6Е_МВ1ТЕС0РУ. В этом случае раз- мещение массива пикселов осуществляется в указанном файле со смещением, оп- ределяемым параметром с1 хл/ОГГзеЕ. Прямой доступ к пикселам со стороны приложения Прямой доступ к пикселам со стороны приложения реализуется точно так же, как и при работе с НТВ-растром (см. класс КН1Ь в листинге 3.5). Применительно к НТВ-секциям аналогичный доступ реализован в классе К01’Ь5ес1п’оп, определение которого будет дано в главе 12, когда он будет приме- няться для решения проблемы рисования в реальном времени (см. листинг 12.4).
Отображение О1В-секции Для вывода В1В-секции на поверхность графического устройства можно исполь- зовать два подхода: □ вывод как В1В-растра при помощи функции 51ге1хНОТВйз или ЗеЮТВИзТоОеутсе; □ вывод как растрового объекта СВ1, заданного дескриптором Н Вйтар, с исполь- зованием совместимого контекста устройства и функции ВИВИ или 5±ге±сН В1±. Пример использования В1В-секций приведен в приложении АппсВесеАег (гла- ва 12, листинг 12.4). Тернарные растровые операции Последний параметр, П\л/Кор, в функциях ВйВИ, РаНЗИ и 51ге1:сНВ11 задает растровую операцию, которая указывает для СВ1, как должны комбинироваться биты ис- ходного изображения с битами изображения-приемника и с битами шаблона те- кущей кисти. Результат операции определяет новое состояние изображения-при- емника. Поскольку число операндов равно трем, то операция относится к классу тернарных операций. В АУш32 АР1 существует 256 КОР-кодов (газ1ег-орегайоп собез). Растровые операции кодируются 32-разрядными двойными словами ОШ)ВО. В старшем слове хранится один из 256 однобайтных кодов растровой операции, а младшее слово содержит кодировку формулы, по которой растровая операция реализуется драй- вером графического устройства. Разработчики МхсгозоЙ присвоили символические имена только пятнад- цати тернарным растровым операциям из 256. Видимо, это как раз те операции, которые использует операционная система. Указанные операции приведены в табл. 3.9, при этом использована следующая система обозначений: Р — шаблон кисти (раиегп), 5 — источник {зоигсе'), 0 — приемник (безйпайоп). Таблица 3.9. Тернарные растровые операции РаНегп: Зоигсе: Оезйпайоп: 11110000 11001100 10101010 Формула КОР-код Имя Результат: 00000000 0 = 0 0x00000042 В1.АСКЫЕ88 00010001 Э = | О) 0x001100А6 МОТ8НСЕКА8Е 00110011 О = ~8 0x00330008 МОТ8НССОРУ 01000100 О = 8&~О 0x00440328 8НСЕКА8Е 01010101 О = -О 0x00550009 О8Т11\1УЕкТ 01011010 О = Р Л О 0Х005А0049 РАТИМУЕНТ 01100110 О = 8 Л О 0x00660046 8кС11\1УЕкТ 10001000 О = 8&О 0х008800С6 8НСАМО 10111011 О = -8 1 О 0х00ВВ0226 МЕНСЕРА1ЫТ 1 1 0 0 0 0 0 0 О = Р&8 ОхООСОООСА МЕНСЕСОРУ 11001100 0 = 8 0х00СС0020 8кССОРУ 11101110 О = 8 1 О 0х00ЕЕ0086 8кСРА11\1Т 11110000 О = Р 0х00Е00021 РАТСОРУ 11111011 О = Р | -8 | О 0х00ЕВ0А09 РАТРАИМТ 11111111 О = 1 0Х00ЕЕ0062 \Л/Н1ТЕЫЕ88
Полную таблицу тернарных растровых операций можно найти в М8^^ (Р1аНогт 50К ► \Л/1’пс1одо$ 6 01 ► Тегпагу Разбег ОрегаБопз). Следует обратить внимание на восьмиразрядные двоичные коды, являющие- ся результатом каждой операции. Двузначное шестнадцатеричное число, соответ- ствующее этим битам, есть старшее слово КОР-кода. При внимательном взгляде на табл. 3.9 можно заметить, что не все растровые операции зависят от всех трех операндов. Поэтому можно рассматривать отдель- ные подгруппы этих операций. Инициализирующие растровые операции Операции В 1_АС К И Е55 и \Л/ Н1Т Е М Е55 не зависят ни от шаблона кисти, ни от источника, ни от приемника. Они обычно используются для инициализации или возврата гра- фической поверхности в исходное состояние. Черный (В1аск) и белый (АУкйе) цвета отличаются от других тем, что все разряды для них установлены либо в О, либо в 1. Благодаря этому для любого произвольного цвета С справедливы следую- щие утверждения: В1аск АМО С = В1аск В1аск ОР С = С В1аск ХОР С = С ШПе АМО 0 = 0 00 0 = ШПе ШПе Х00 0 = МОТ 0 Указанные свойства могут использоваться для достижения различных эффек- тов при выводе растров. Операции, зависящие только от источника К таким операциям относятся 5КСС0РУ и М0Т5КСС0РУ. Операция 5КСС0РУ уже использовалась ранее в примерах. Она просто копирует пикселы источника в приемник. Обычно эта операция применяется для отображе- ния растра в исходном цвете. Операция М0Т5КСС0РУ копирует в приемник цвет, противоположный цвету ис- точника. Операция, зависящая только от приемника Операция ОЗТИМУЕКТ меняет цвет пиксела приемника на противоположный. Операции, не зависящие от источника В табл. 3.9 к таким операциям относятся РАПМУЕКТ и РАТСОРУ. Всего существует 16 ра- стровых операций, не зависящих от источника, но не все они имеют символические имена. В СВ1 предусмотрена специальная функция для выполнения растровых опе- раций, не зависящих от источника: В001 РаШЧНОС Нбс, Ш пХЬеЧ, Ш пХ1_еГС Ш пШс1Ж Ш пНетдкгЬ, 0ШР0 (Мор); Функция Ра1В 11 комбинирует текущую кисть с прямоугольным регионом прием- ника. Вы можете использовать любые коды ВОР из полной таблицы операций1, в ко- торых не задействован источник, а не только те, которые приведены в табл. 3.9. 1 См. выше ссылку на М8Б1У.
Операции, не зависящие от шаблона кисти К таким операциям относятся 1М0Т8КСЕКА8Е, 8КСЕКА8Е, 8КС1МУЕКТ, 5КСАМ0, МЕКСЕРА11МТ и 8КСРА11МТ. Следует отметить, что операции 8КС1М\/ЕКТ и 5КСАМ0 используются систе- мой \У1пс1о\\8 для отображения пиктограмм и курсоров мыши. Рассмотрим, например, механизм отображения пиктограмм, которым пользу- ется \Упк1о\\ъ. Ресурсы предопределенных пиктограмм хранятся в динамически загружаемой библиотеке 5Не1132.с1111. Доступ к библиотеке можно получить, за- грузив ее в приложение при помощи функции 1_оас11лЬгагу, которая возвращает дес- криптор экземпляра модуля (Н8Не1132), содержащего соответствующие растровые изображения. Затем, используя индекс пиктограммы, можно загрузить любую пик- тограмму из модуля Н5Не1132. Для этого достаточно вызвать функцию 1_оас11таде. Например, если передать функции коасИтаде индекс 12, то будет загружена пиктог- рамма, обозначающая привод СН-КОМ. Функция коасИтаде в случае успешного завершения возвращает дескриптор пиктограммы Н1соп. Затем при помощи вызова 6еисоп1пТо(И1соп, &1соп1пТо); можно получить информацию о загруженной пиктограмме, сохранив ее в пере- менной 1’соп1пГо типа 1С0М1МЕ0. Для нас в этой структуре интересны два поля: НВ1ТМАР ИЬтМазк; НВ1ТМАР НЬтСо1ог; Первое из этих полей содержит дескриптор растра, представляющего собой маску пиктограммы, второе поле — дескриптор растра, представляющего собой изображение пиктограммы. На рис. 3.9 показаны маска и изображение для пик- тограммы «привод СВ-КОМ». Маска Изображение [ЬЬтМазк] [ЬЬтСо1ог] Рис. 3.9. Пара растров для вывода пиктограммы «привод СО-НОМ» Оба растра имеют размеры 48 х 48. Но для чего нужны эти два растра? Дело в том, что при выводе пиктограммы в виде прямоугольной области некоторые пик- селы этой области должны изменяться, а некоторые должны остаться прежними. Изменяемая область называется непрозрачной, и в маске ей соответствует черный цвет. Остальные пикселы образуют прозрачную область — в маске ей соответству- ет белый цвет. Приложение, код которого приведен в листинге 3.8, демонстрирует, как можно отобразить пиктограмму с учетом ее прозрачной области. Листинг 3.8. Проект ОгамТсопМкИКор ////////////////////////////////////////////////////////////////////// // Огам1сопШРИКор.срр #1пс1ис1е <\л/1 пс1о\л/8. И> 1 Вопросы создания и применения пользовательских пиктограмм рассматриваются в главе 5.
#1пс1ис1е "К1л/пс1.1п" #с1еС1пе 1С0М_Ю 12 2КЕ81ЛТ САЕЕВАСК ИпбРгос(НШО, 1ИМТ, НРАВАМ, ОРАВАМ); //============================================ 1пЬ ШИАР1 ШпМапп(НIИЗТАМСЕ Шнапсе, Н1М5ТАМСЕ ИРгеуШЬапсе, ЕРЗТЕ 1рСтсИ1пе, Ш пСтс15Но\л/) { М80 тзд; КИпс1 та1 пНпс1 ("Огам 1соп мПН ВОР", ЫпзЬапсе, пСтсЕИом, Ипс1Ргос); мП11е (СММеззадеС&тзд, НПО, 0, 0)) { Тгап81аЬеМе88аде(&т8д); 018раШМе88аде(&т8д); } гебигп тзд.мРагат; } //========================================== ЕЕЕЗОЕТ САЕЕВАСК ИпОРгос(НШ0 ИИпсО 01ИТ иМзд, НРАВАМ мРагат, ОРАВАМ 1Рагат) { НОС НОС; РА1М1ТЗТВ0СТ р8; 8-Еа-Ыс Н1МЗТАМ1СЕ И8Ие1132; 1С0М1ИЕ0 1соп1пСо; В1ТМАР Ьтр; Н1С0И Ысоп; НОС НМетОС; 1пЬ х = 20, у = 20; 8мЮсН (иМзд) { сазе ИМ_СВЕАТЕ: Н8Не1132 = Ьоас1Е1Ьгагу("8Ье1132.с111"); Зе-ОС 1 а88Ьопд(Шпс1, ЗСЕ_НВВВАСКЗВОНИ0, (ЕОМОСгеа-ЕеЗо! 1 бВгиеН (В6В(180,140,200))); Ьгеак; саее Ш_РА1МТ: НОС = Вед1 пРа1 пЬ(Н1л1пс1, &ра); НМетОС = СгеаЬеСотраЫЫеОС(НОС); Н1соп = (Н1С0М)Еоас11таде(Н8Не1132, МАКЕIИТВЕЗОНВСЕ(1С0М_Ю), 1МА6Е_1С0М, 48, 48, ЕВ_ОЕЕАНЕТСОЕОВ); Ю (!Н1соп) { Ме88адеВох(Н1л1пс1, "Ошибка загрузки пиктограммы", "Еггог", МВ_ОК); Ьгеак; } 0еЬ1соп1пСо(Ысоп, &1соп1пСо); ОеЬОЬ0есЬ(1соп1пСо. ИЬтМазк, зыеоПЬтр), &Ьтр); // Маска 8е1есЬОЬ0есб(ИМетОС, 1соп1пСо.ИЬтМа8к); В1ЬВ1Ь(И0С, х, у, Ьтр.Ьт1л1ШИ, Ьтр.ЬтНе1дНЬ, НМетОС, О, О, ЗВССОРУ); // Изображение продолжение
Листинг 3.8 {продолжение} 8е1есЬ0ЬцесШМет0С, 1соп1пГо.ИЬтСо1ог); В1ЬВ1Ь(И0С, х+60, у, Ьтр.ЬтШсНзН, Ьтр.ЬтНе1дИЬ, ИМетВС, О, О, 8ВСС0РУ); // Вывод пиктограммы наложением маски и рисунка 8е1есЬ0ЬцесЬ(кМетВС, 1соп1пГо.ИЬтМазк); ВИВИ (ВВС, х+120, у, Ьтр.ЬтШс1Ьк, Ьтр.ЬтНе1д1тЕ, ЬМетВС, О, О, 8ВСАМВ); ЗеТесЬОЬцесШМетОС, 1соп1пГо.ИЬтСо1ог); ВИВИ (ВОС, х+120, у, Ьтр.ЬтШс1Ьк, Ьтр.ЬтНе1д1тЕ, ЬМетВС, О, О, 8ВС1М7ЕВТ); // Вывод пиктограммы функцией Вгам1соп Вгам1соп(ЬОС, х+188, у, Ысоп); Ве1еЬеОЬцесШМетВС); Ое8Ьгоу1соп(Ысоп); ЕпбРаппЬ(Шпс1, &рз); Ьгеак; сазе Ш_0Е8ТВ0У: РозЩПЬМеззадеСО); Ьгеак; с1еГаи1Ь: геЬигп ВеГШ псЫРгос(Шпс1, иМзд, мРагат, 1Рагат); } геЬигп 0; } ////////////////////////////////////////////////////////////////////// Для отображения пиктограммы сначала при помощи функции ВДВД с растровой операцией 5 ПСАМ 0 выводится маска. При этом непрозрачная область устанавлива- ется в черный цвет, а прозрачная остается без изменения. Затем в этой же позиции выводится цветной растр изображения, для чего используется вызов ВДВД с рас- тровой операцией 5 ВС1И УЕ ВТ. Результат работы программы показан на рис. 3.10. В клиентскую область прило- жения последовательно выводятся: маска (а), цветной растр (б), пиктограмма (в), образованная наложением цветного растра на маску, и пиктограмма (г), нарисован- ная функцией Ога\л/1соп, которая масштабирует значок по его стандартным разме- рам 32 х 32 пиксела. а бег Рис. 3.10. Вывод программы Ога\л/1соп\Л/|1йкор Метафайлы Метафайл — это протокол обращений к функциям (ЗВ1, сохраненный в двоич- ном формате. Метафайлы имеют такое же значение для векторной графики, как и битовые образы для растровой графики. Метафайл состоит из набора записей,
соответствующих вызовам графических функций, таких как создание и выбор в контекст устройства пера или кисти, рисование линий, фигур, вывод текста, и иных операций. При воспроизведении метафайлов достигается такой же результат, как и при непосредственном использовании функций (ЗОЕ Разница между их воспроизве- дением и непосредственным вызовом функций состоит в том, что метафайлы мо- гут храниться в памяти или в файлах на диске, загружаться и воспроизводиться приложением столько раз, сколько это нужно. Метафайлы используются для передачи изображений между программами через буфер обмена, а также для сохранения изображений в виде файлов на дис- ке. Для их хранения требуется значительно меньше места, чем для хранения рас- тровых изображений. В то же время для отображения метафайлов требуется обыч- но больше времени, чем для вывода растровых изображений. Поскольку метафайл описывает изображение в терминах команд графическо- го вывода, то изображение может быть масштабировано при воспроизведении без потери разрешения. Для битовых образов масштабирование всегда связано с по- терей качества изображения. Первоначальный 16-битный формат метафайлов УУМ Е1 появился в АУтйомга 2.0. Однако этот формат был аппаратно-зависимым и не содержал информацию о раз- мерах изображения, поэтому его использование было связано со многими про- блемами. В АУш32 появился новый 32-битный формат ЕМЕ (Епйапсес! Ме1аШе Еогша!). Расширенные метафайлы содержат дополнительную информацию о раз- мерах изображения и цветовой палитре, что обеспечивает их аппаратную незави- симость. Кроме того, они поддерживают все 32-битные функции рисования. АУш32 АР1 позволяет использовать файлы форматов УУМЕ и ЕМЕ. Поскольку в новых приложениях рекомендуется использовать формат ЕМЕ, то в дальнейшем будут рассматриваться только расширенные метафайлы. Создание метафайла Расширенный метафайл является таким же объектом СВ1, как, например, объект кисти или объект В1В-секции. Объект расширенного метафайла однозначно оп- ределяется своим дескриптором типа НЕМНМЕТАЕПЕ. Создание метафайла и запись в него команд СВ1 связаны с использованием метафайлового контекста устройства, который рассматривался во второй главе. Метафайловый контекст устройства создается функцией СгеаЕеЕпНМеЕаЕПе: НОС СгеаШпНМеШ11е(Н0С МсКеЕ ЕРСТ8ТК 1рЕ11епате, СОИЗТ КЕСТ* 1 рКесТ, Е РСТЗТК 1 рОезсн рТ1 оп); Первый параметр, Нс1сКеГ, указывает на эталонный контекст устройства, данные которого будут использованы при записи ЕМЕ. Если этот параметр имеет значение МОП, то СВ1 принимает в качестве эталона текущий экран. Во втором параметре, фЕйепате, может передаваться имя файла на диске или значение МОП. Если передается МОП, то метафайл создается в памяти. Если переда- ется имя файла, то после удаления объекта метафайла функцией Ое1е1еЕп НМеЕаЕПе файловый вариант сохраняется на диске. 1 \УМР — УУтсЬлуз МеЕаШе РогтаЕ.
Третий параметр, 1р Кес!, определяет позицию и размеры сохраняемого изобра- жения в единицах 0,01 мм. Если параметр имеет значение МОП, то СВ1 вычисляет ограничивающий прямоугольник по всем командам вывода. Последний параметр, 1рОе$спрйоп, содержит необязательное текстовое описа- ние, сохраняемое в метафайле. Обычно описание содержит имя приложения и имя документа, разделенные нулевым символом. Это описание должно завер- шаться двумя нулевыми символами. Функция СгеаЕеЕп НМеЕаЕПе возвращает дескриптор метафайлового контекста устройства. Этот дескриптор передается затем всем графическим функциям СВ1 для записи «протокола рисования» в метафайл. Когда рисование завершено, необходимо вызвать функцию С1о$еЕп НМеЕа ЕПе, ко- торая закрывает метафайловый контекст и возвращает дескриптор расширенного метафайла. Этот дескриптор может быть использован для воспроизведения мета- файла. Как говорилось выше, если имя файла было задано при вызове функции СгеаЕе Еп И МеЕа Е11е, то сохранение файла на диске происходит после вызова функции ОеЕеЕе Еп И МеЕа Е11е, которая удаляет объект метафайла из памяти приложения. Технология создания метафайла иллюстрируется приложением, код которого приведен в листинге 3.9. В программе СгеаЕеМеЕа ЕПе имитируется типичная ситуация для графических редакторов, работающих с векторной графикой, когда рисование в клиентской области окна дублируется рисованием в метафайле, который затем сохраняется на диске. Листинг 3.9. Проект Сгеа1еМе1аРПе ////////////////////////////////////////////////////////////////////// // СгеаТеМеТаЕПе.срр #1пс1ис1е <\л/1 пс1о\л/8. И> #1пс1ис1е "К1л1пс1.И" #с1еТ1 пе Е1ЕЕ_МАМЕ "Р1сТ1.етГ" уо1с1 0гам8отеШпд(Н0С); Е^ЕЗСЕТ САИВАСК ШбРгос(НШО, 1ЛМТ, АРАРАМ, ЕРАВАМ); //============================================ 1пР ШИАР1 Ш пМа 1 п (НIМ8ТАМ1СЕ Шнапсе, Н1М8ТАМСЕ ИРгеуЩзТапсе, ЕР8ТВ 1рСтсЕ1пе, 1пТ пСтсЕИом) { М86 тзд; К1л/пс1 та1 п1л/пс1 ("Сгеа'ЕеМе'ЕаЕа 1 е", И1пзТапсе, пСтсЕИом, МпбРгос); \л/М 1 е (6еТМеззаде(&тзд, ЖЕ, 0, 0)) { Тгапз1аТеМеззаде(&тзд); 01зраТсИМеззаде(&тзд); } геТигп тзд.мРагат; } //========================= ЕЕЕЗЕЕТ САЕЕВАСК ШбРгос(ШИВ ШпсЕ В11\1Т иМзд, ПРАВАМ мРагат, ЕРАВАМ 1Рагат) { НОС ВВС; РА1ИТЗТВЕ1СТ рз; ВЕСТ гесТ;
зТаТ1с НОС ЕбсЕМЕ; НЕМНМЕТАЕ1ЕЕ ЬетТ; змПсЕ (иМзд) { сазе Ш_СКЕАТЕ: ЕбсЕМЕ = СгеаТеЕпЬМеТаЕ11е(МНЕЕ, Е1ЕЕ_МАМЕ, ИНЕЕ, "СгеаТеМеТаП1е\0Р1сТ1\0"); Ьгеак; сазе 1л/М_РА1МТ: НОС = Вед1 пРа1 пТ(Шгб, &рз); ОгамЗотеТМ пд(ЕОС); ОгамЗотеТМ пд(ЕбсЕМЕ); ЕпбРа! пТ(Е1л1пб, &рз); Ьгеак; сазе Ш_0Е5ТР0У: ЬетТ = С!озеЕпЕМеТаЕ!1е(ЕбсЕМЕ); Ое!еТеЕпЕМеТаЕ!1е(ЕетТ); РозТОьп ТМеззаде(0); Ьгеак; беТаи!Т: геТигп ОеТШпсЫРгос(Шгб, иМзд, мРагат, 1Рагат); } геТигп 0; } ////////////////////////////////////////////////////////////////////// уо1б ОгамЗотеТМ пд(НОС Ыс) { КЕСТ г; НРЕМ ЬРеп, ЬОЮРеп; НВВЕ13Н НВгизЬ, Е01бВгизЕ; ЗеТКесШг, 20, 20, 220, 220); ПРеп = СгеаТеРеп(Р3_30Е10, 10, К6В(255, 160, 140)); Н01с1Реп = (НРЕМ)5е1есТОЬ0есТ(Ебс, НРеп); НВгизЬ = СгеаТеЗо!1ЬВгизЬ(КСВ(140, 160, 255)); ЬОЮВгизЬ = (НВВбЗН)3е1 есТОЬ0есТ(Ыс, НВгизЮ; КесТапд!е(Ыс, г.1еТТ, г.Тор, г.н’дИТ, г.ЬоТТот); ПРеп = СгеаТеРеп(Р5_50Ы0, 3, КСВ(О, 255, 0)); 5е1 есТОЬОесТ(Ьс1с, НРеп); НВгизЬ = СгеаТеЗо!1ЬВгизЬ(КСВ(100, 100, 100)); 5е1 есТОЬОесТ(Ьс1с, НВгизЬ); IпТ1аТеКесТ(&г, -40, -40); Е111рзе(Ьс1с, г. 1 еТТ, г.Тор, г.п’дНТ, г.ЬоТТот); ЗеТТехТСо1ог(Нс1с, КСВ(255, 255, 255)); ЗеТВкМобе(Ебс, ТКАМЗРАКЕМТ); Ога\л/ТехТ(Ебс, "ТЕЗ !", -1, &г, ОТ_СЕМТЕК | 0Т_31ИЗЕЕЕIНЕ | 0Т_7СЕМТЕК); 0е1еТеОЬдесТ(3е1есТОЬдесТ(Ебс, ЕО1бРеп)); 0е1еТеОЬОесТ(Зе1есТОЬдесТ(Ебс, ЕС!бВгизЕ)); } //////////////////////////////////////////////////////////////////////
Метафайловый контекст устройства создается в блоке обработки сообщения ММ_СКЕАТЕ. Его дескриптор Н дс ЕМ Е объявлен со спецификатором з^аИс, так как ис- пользование объекта метафайла происходит позже, при обработке других сообще- ний. В приложении определена функция ОгадоЗотеВйпд, которая рисует некоторое изображение в контексте устройства Ндс, передаваемом ей в качестве параметра. Изображение может быть произвольным, но в данном случае отображается квад- рат размером 200 х 200 пикселов, внутри квадрата рисуется круг, а уже внутри круга выводится текст. При обработке сообщения ММ_РА1МТ функция ОгамЗотеВйпд вызывается дваж- ды: сначала с контекстом дисплея, затем с метафайловым контекстом. При обработке сообщения ММ_ОЕ$ТКОУ вызываются следующие функции: ПетГ = С1 озеЕпНМе1заЕ11 е(Г|с1сЕМЕ); ОеШеЕпИМе^аЕПеШетТ); Именно благодаря им созданный метафайл сохраняется на диске. Откомпилируйте и запустите приложение. В окне программы должно появить- ся изображение, показанное на рис. 3.11. Если ваш дисплей работает в режиме Тгие Со1ог, то рамка, ограничивающая квадрат, будет нежно-кремового цвета, внут- ренняя область квадрата — светлого серо-голубого цвета, окружность, ограничи- вающая круг, — светло-зеленого цвета, а сам круг — темно-серого цвета. Рис. 3.11. Вывод программы Сгеа1еМе1:аН1е Завершите работу с программой, щелкнув мышью на кнопке закрытия окна. Теперь посмотрите на содержимое папки с вашим проектом. В ней должен по- явиться файл РкИ.етГ. Это и есть созданный программой метафайл. Он может быть просмотрен с помощью любого графического редактора, поддерживающего формат ЕМ Е, например, при помощи приложения АСВЗее. Воспроизведение метафайла Расширенный метафайл воспроизводится функцией Р1ауЕпНМе1аЕНе, которая име- ет следующий прототип: ВООЕ Р1ауЕпИМеСаЕа1е(НОС Мс, НЕМНМЕТАЕ1ЕЕ ИепТ, С0И8Т ВЕСТ* ПрЕесС); Первый параметр, Ндс, задает дескриптор приемного контекста устройства. Вто- рой параметр содержит дескриптор расширенного метафайла. Третий параметр определяет ссылку на ограничивающий прямоугольник изображения в логичес- ких координатах контекста Ндс.
Если метафайл находится на диске, то сначала нужно получить его дескриптор. Это можно сделать с помощью функции СеЕЕп НМеЕаП 1еНеадег: НЕМНМЕТАГ1ЕЕ 6еШНМеШ11е(ЕРСТ8ТВ фзгМеШПе); Функция возвращает искомый дескриптор по заданному имени файла 1р$2МеЕа Н 1е. Файл должен находиться в текущем каталоге, в противном случае строка [рзгМеЕа Й1е должна содержать полный путь к файлу. Остается последний вопрос: как получить размеры ограничивающего прямоу- гольника для передачи третьему параметру функции Р1ау Еп И МеЕа Е11е? Здесь нас вы- ручит функция СеЕЕп И МеЕа Е11еНеас1ег, читающая заголовок метафайла в структуру типа ЕМНМЕТАНЕАОЕК, так как поле гс1Воипс1$ этой структуры как раз и содержит не- обходимую информацию. В листинге 3.10 приведен код приложения, которое воспроизводит созданный ранее метафайл РкИ.етГ. Перед запуском приложения нужно скопировать этот файл в каталог проекта Р1ауМе1:аЕ1’1е. Листинг 3.10. Проект Р1ауМе1аН1е ////////////////////////////////////////////////////////////////////// // Р1 ауМе^аП 1 е. срр #1пс1ис1е <\л/1 пс1омз. Н> #1пс1ис1е "К1л1пс1.Н" #с1еМпе Е1ЕЕ_МАМЕ "Р1Ш .етТ" ЕКЕ8ЕЕТ САЕЕВАСК ШбРгос(НШО, 1ЛМТ, ПРАВАМ, ЕРАВАМ); //========================================== 1пР ШМАР1 ШпМаап(НIМЗТАМСЕ Шнапсе, Н1МЗТАМСЕ НРгеу1пзШсе, ЕРЗТВ 1рСтс1Е1пе, Ш пСтс13Ио\л/) { М86 тзд; К1л/пс1 та1 пМпс!("Р1 ауМеТаЕ11 е", НТпз^апсе, пСтсЕНом, МпбРгос); \л/М1е (6е^Меззаде(&тзд, МЫЕЕ, 0,0)) { ТгапзШеМеззаде(&тзд); 01зра^сНМеззаде(&тзд); } ге^игп тзд.мРагат; } /============================= ЕВЕЗЕЕТ САЕЕВАСК ШОРгос(НШО Шпс1, Е1МТ иМзд, ПРАВАМ мРагат, ЕРАВАМ 1Рагат) { НОС НОС; РА1МТЗТВ0СТ рз; ВЕСТ гесМ НЕМНМЕТАЕ1ЕЕ НепТ; ЕМНМЕТАНЕАОЕВ етН; 1пР х1, у1, х2, у2; змПсН (иМзд) { сазе Ш_РА1МТ: НОС = Вед1 пРа1 пР(Н1л1пс1, &рз); НегтТ = ЗеЦЕпИМеТаЕН 1 е(Е1 ЕЕ_МАМЕ); продолжение &
Листинг 3.10 {продолжение} 1Г (! ИетГ) МеззадеВох(Шпек "Файл " Г11_Е_МАМЕ " не найден.", "Еггог", МВ_ОК); е!зе { 6еЬЕпЬМеЬаЕ11еНеабег(НетТ, 81геоТ(ЕМНМЕТАНЕАВЕЮ, &етЮ; х1 = етИ.гс1Воипс1з.1еГк у1 = етИ.гс1Воипс1зЛор: х2 = етИ.гс!Воипс1з.г1 дЬЬ; у2 = етИ. гс!Воипс1з .ЬоФФот; ЗеФРесШгеск х1, у1, х2, у2); Р1 ауЕпИМеФаЕ11 е(НВС, НетГ, &гесЬ); Ве1еЬеЕпЬМеЬаЕ11е(ИетТ); } ЕпбРалпЬ(Г|1л/пб, &рз); Ьгеак; сазе Ш_ВЕ8ТР0У: РозФОиПМеззаде(О); Ьгеак; ЬеГаыИ: геЬигп ВеблМпсЫРгос(Шпс1, иМзд, мРагат, 1Рагат); } геЬигп 0; } ////////////////////////////////////////////////////////////////////// Рекомендуем вам поэкспериментировать с масштабированием воспроизводи- мого метафайла, заменяя инструкцию 8еЬРесЬ(&гесЬ, х1, у1, х2, у2); одним из следующих вариантов: 8еЬРесЬ(&гесЬ, х1, у1, 2 * х2, у2); 8еЬРесЬ(&гесЬ, х1, у1, х2, 2 * у2); 8еЬРесЬ(&гесЬ, х1, у1, 2 * х2, 2 * у2); ЗеЬВесШгесЬ, х1, у1, 0.5* х2, 0.5 * у2); и наблюдая, как будет изменяться выводимое изображение.
Средства ввода Приложение, работающее под управлением системы АУтскпуз, получает данные от пользователя через устройства ввода, главными из которых являются клавиату- ра и мышь. Когда сообщения от клавиатуры или мыши воспринимаются стандарт- ными элементами управления, такими как кнопки, поля редактирования и меню, то обработка этих событий реализована в соответствующих оконных классах. Од- нако иногда необходимо непосредственно обрабатывать сообщения от клавиатуры или мыши. Например, это нужно в приложениях, воспринимающих пользователь- ский ввод или в главном окне, или в одном из дочерних окон. В АУш32 АР1 предус- мотрен широкий набор функций для управления клавиатурой и мышью. Клавиатура \У1пс1о\\ъ обеспечивает поддержку клавиатуры, независимую от аппаратуры и от на- ционального языка, используемого в системе. Независимость от аппаратуры дос- тигается благодаря использованию программного драйвера клавиатуры. Незави- симость от национального языка реализуется выбором нужной кодовой страницы1. На рис. 4.1 приведена схема обработки сообщения от клавиатуры, используе- мая в операционной системе. Рис. 4.1. Модель клавиатурного ввода 1 Вы можете сменить кодовую страницу, запустив системное приложение «Клавиатура» в папке «Па- нель управления» и выбрав закладку «Языки и раскладки».
Каждой клавише на клавиатуре соответствуют два уникальных целых числа, называемых скан-кодом нажатия и скан-кодом отпускания клавиши. Скан-код (зсап сос1е) является аппаратно-зависимым, то есть на клавиатурах разных производите- лей одна и та же клавиша может иметь разные скан-коды1. Когда пользователь на- жимает или отпускает клавишу, драйвер клавиатуры получает соответствующие скан-коды. Полученный скан-код преобразуется драйвером в виртуальный код кла- виши (о1г1иа1-кеу сос1е). Этот код является уже аппаратно-независимым, поэтому в системе АУтйохуз он однозначно идентифицирует нажатую или отпущенную кла- вишу. Завершающим действием драйвера является создание сообщения, содержа- щего скан-код, виртуальный код клавиши и другую информацию о нажатой/отпу- щенной клавише. Это сообщение помещается в системную очередь сообщений. \Упк1о\\-з извлекает сообщение из системной очереди и передает его в очередь сообщений того приложения, которое имеет активное окно. Далее сообщение извлекается в известном вам цикле обработки сообщений, поступая в конечном счете на обработку в соответствующую оконную процедуру. Фокус ввода Все приложения, работающие одновременно под управлением АУшсЕпуз, должны иметь возможность получать данные от клавиатуры. Если какое-то приложение имеет более одного окна, то клавиатура должна разделяться и между окнами в рамках одного приложения. Это разделение АУшсЕпуз реализует, используя по- нятие фокуса ввода. Фокус ввода (кеуЪоагсй/осиз) — это временное свойство окна, которое означает, что все сообщения от клавиатуры направляются именно этому окну. В каждый момент времени из всех окон, имеющихся на экране, только одно окно может иметь фокус ввода. Понятие фокуса ввода тесно связано с понятием активного окна. Активное окно — это окно верхнего уровня, с которым в данный момент работает пользова- тель. Система выделяет текст заголовка активного окна, поэтому отыскать его на экране довольно просто. Если активное окно минимизировано, то АУшсЕпуз выде- ляет текст заголовка на панели задач. Фокусом ввода всегда владеет либо активное окно, либо одно из его дочерних окон. Часто дочерними окнами являются элементы управления — кнопки, пере- ключатели, флажки, текстовые поля и списки, которые обычно размещаются в окне диалога. Элементы управления по-разному показывают, что они находятся в фокусе. Так, вокруг текста кнопки выводится точечная линия, а текстовое поле показывает, что оно получило фокус ввода, при помощи мигающего курсора. Если активное окно минимизировано, то окна с фокусом ввода нет. АУтйохуз продолжает пересылать программе сообщения клавиатуры, но эти сообщения выглядят иначе, чем сообщения, направляемые активным неминимизированным окнам. Обрабатывая сообщения ММ_$ЕТЕОС115 и ММ_К11_1_ЕОС115, оконная процедура мо- жет определить текущий статус связанного с ней окна. Первое сообщение по- казывает, что окно получило фокус ввода, второе — что окно потеряло фокус ввода. 1 По этой причине используется также термин «скан-код ОЕМ» (Оп§ша1 Есрпртеп!: Матпй’асТпгег Зсап Сойе).
Для работы с фокусом ввода предусмотрены следующие функции: □ Функция Н\Л/М0 5екЕоси$(Н\Л/М0 Н\Л/пс1) устанавливает фокус ввода на окно НУУпс!, возвращая дескриптор окна, которое располагало фокусом до вызова функции. □ Функция НУУМ 0 6е1:Еоси$() возвращает дескриптор окна, имеющего фокус ввода в текущий момент. Клавиши и символы Клавиатура всегда генерирует некоторые числовые коды, но ее можно рассматри- вать либо как совокупность отдельных физических клавиш, либо как средство генерации кодов символов. В первом случае любой генерируемый код должен идентифицировать клави- шу и показывать, нажата она или отпущена. Во втором варианте код, вырабатыва- емый при нажатии клавиши, идентифицирует уникальный символ из набора сим- волов. По умолчанию используется набор символов А^8I1. У многих клавиш современного компьютера нет кодов символов. Ни функцио- нальные клавиши, ни клавиши управления курсором их не генерируют. Поэтому в программах, использующих ввод с клавиатуры нетривиальным способом, обыч- но приходится иметь дело с клавиатурой и как с совокупностью клавиш, и как с генератором символов одновременно. Клавиши можно разделить на следующие четыре группы: □ Клавиши-переключатели — Сарз 1_оск, Мит коек, 5сго11 коек и в некоторых случа- ях клавиша Iпзегк. При нажатии такой клавиши включается ее состояние, а при повторном нажатии — выключается. Как правило, на клавиатуре есть свето- вые индикаторы состояния клавиш-переключателей. □ Клавиши управления регистром — 5ЫГк, Скг1 и АН. В нажатом состоянии такая клавиша меняет интерпретацию других клавиш. Иногда такие клавиши назы- вают клавишами-модификаторами. □ Клавиши, пе генерирующие символов, — функциональные клавиши, клавиши управления курсором, Раизе, 0е1еке. □ Символьные клавиши — буквы, цифры и другие символы, пробел, ТаЬ, Васкзрасе, Е$с и Епкег. Впрочем, клавиши ТаЬ, Васкзрасе, Е$с и Епкег также можно рассмат- ривать как клавиши без символов. Сообщения клавиатуры, которые приложение получает от \У1пс1о\\у можно разделить на «аппаратные» (кеуз^гокез) и «символьные» {скагас^егз). Это раз- деление соответствует указанной выше двойственной интерпретации клавиа- туры. Аппаратные сообщения Аппаратные сообщения от клавиатуры приведены в табл. 4.1. Обычно различают системные и несистемные сообщения. Для системных сообщений используется префикс 575 в составе идентификатора кода сообщения. 1 См. главу 2, раздел «Рисование текста».
Таблица 4.1. Аппаратные сообщения от клавиатуры Категория Клавиша нажата Клавиша отпущена Несистемные сообщения \Л/М_КЕУОСМ/Ы \Л/М_КЕУ11Р Системные сообщения \Л/М_8У8КЕУОО\Л/Ы \Л/М_8У8КЕУ11Р Системные сообщения ММ_$У$КЕУООММ и ММ_$У$КЕУ11Р, вырабатываемые обычно при нажатии клавиш в сочетании с клавишей АЙ, более важны для АУтскпуз, чем для приложения. Эти сообщения вызывают команды программного или систем- ного меню. Они также используются для вызова системных функций, таких как смена активного окна (АЙ + ТаЬ), или как быстрые клавиши системного меню (АЙ в сочетании с функциональной клавишей). Поскольку система АУтскпуз самосто- ятельно отрабатывает всю логику АЙ-клавиш, то вам, фактически, не нужно обра- батывать эти сообщения. Ваша оконная процедура в конце концов получит дру- гие сообщения, являющиеся следствием этих аппаратных сообщений клавиатуры (например, выбор команды меню). Обычно сообщения о нажатии и отпускании клавиши появляются парами. Однако если пользователь оставит клавишу нажатой так долго, что включится автоповтор, то АУтйомга пошлет оконной процедуре серию сообщений ММ_КЕУООММ и одно сообщение ММ_КЕУ11Р, когда, в конце концов, клавиша будет отпущена. Для всех аппаратных сообщений клавиатуры 32-разрядная переменная 1Рагат, передаваемая в оконную процедуру, содержит 6 полей (табл. 4.2). Таблица 4.2. Интерпретация полей параметра 1Рагат Разряды Назначение Описание 15 ... 0 Счетчик повторений Число повторений данного сообщения. Равно либо единице, либо числу нажатий в режиме автоповтора, когда пользо- ватель удерживает клавишу нажатой. Для сообщения \Л/М_КЕУ11Р всегда равен единице 23 ... 16 Скан-код Восьмибитный скан-код ОЕМ 24 Флаг расширенной клавиатуры Устанавливается в 1 для дополнительных клавиш расши- ренной клавиатуры 1ВМ (функциональные клавиши в верх- ней части клавиатуры, клавиши АН и СШ на правой стороне клавиатуры и некоторые другие клавиши) 29 Код контекста Устанавливается в 1, если нажата клавиша АН (для сооб- щений \Л/М_КЕУОО\Л/Ы и \Л/М_КЕУ11Р всегда равен 0 ) 30 31 Флаг предыдущего состояния клавиши Флаг нового состояния клавиши Единица, если клавиша была нажата, и нуль, если клавиша была отпущена Нуль, если клавиша нажата, и единица, если клавиша отпущена Хотя некоторая информация в 1Рагат при обработке аппаратных сообщений может оказаться полезной, гораздо более важен параметр доРагат, содержащий виртуальный код клавиши. Виртуальный код клавиши (ойЯиа1 кеу сойе) идентифицирует нажатую или от- пущенную клавишу и является аппаратно-независимым. Идентификаторы вир- туальных клавиш определены в файле \лли изег. Н. В табл. 4.3 приведены наиболее часто используемые коды.
Таблица 4.3. Виртуальные коды клавиш Десятичное значение Шестнадцатеричное значение И дентифи катор Клавиатура 1ВМ 3 03 УК_СА1\1СЕ1_ СШ + Вгеак 8 08 УК_ВАСК Васкзрасе 9 09 УК_ТАВ ТаЬ 13 ОО УК_КЕТ1ЖЫ Еп1ег 16 10 УК_5Н1РТ 17 11 УК_СО1\1ТкО1_ ст 18 12 УК_МЕ1\111 АН 19 13 УК_РА118Е Раизе 20 14 УК_САР1ТА1_ Сарз 1_оск 27 1В УК_Е5САРЕ Езс 32 20 УК_5РАСЕ Пробел 33 21 УК-РкКЖ Раде 11 р 34 22 УК_ЫЕКТ Раде Оо\л/п 35 23 УК_ЕШ Епб 36 24 УК_НОМЕ Ноте 37 25 УК_1_ЕЕТ Стрелка влево 38 26 УК_11Р Стрелка вверх 39 27 УК_Н1СНТ Стрелка вправо 40 28 УК_ОСМ/Ы Стрелка вниз 44 2С УК_5ЫАР5НОТ Рпп! Зсгееп 45 20 УК_1Ы5ЕНТ 1пзег1 46 2Е УК_ОЕ1_ЕТЕ Ое1е1е 48...57 30...39 0...9 (на основной клавиатуре) 65...90 41...5А А...2 112...123 70...7В УК_Е1..Л/К_Е12 Е1...Е12 144 90 УК_1\111М1_ОСК Ыит 1_оск 145 91 УК_8СкО1_1_ ЗсгоН 1_оск Обычно оконная процедура обрабатывает только небольшую часть из всех ап- паратных сообщений клавиатуры, игнорируя остальные. Например, она может обрабатывать только те сообщения ММ_КЕУООММ, которые содержат виртуальные коды для клавиш управления курсором, клавиши 5Н1Й: и функциональных кла- виш. В то же время аппаратные сообщения для символьных клавиш могут игно- рироваться. Дело в том, что ввод информации от символьных клавиш удобнее обрабатывать, используя символьное сообщение ММ_СНАК. Символьные сообщения Когда нажимается символьная клавиша, генерируемый код зависит от многих факторов. На его формирование влияет положение клавиш-переключателей и клавиш управления регистром, а также текущая кодовая страница, установлен-
ная в системе. Все эти факторы учитывает функция Тгап$1а1:еМе$$аде, вызываемая в цикле обработки сообщений, который находится в теле функции п Ма1 п и име- ет вид \л/М1е (бе^МеззадеС&тзд, М1Л_1_, 0, 0)) { ТгапзШеМеззадеС&тзд); Отзра^сИМеззадеС&тзд); } Напомним, что функция беВЧеззаде извлекает очередное сообщение из очере- ди и помещает его в структурную переменную т$д, а функция О1’$ра1:сНМе$$аде вы- зывает соответствующую оконную процедуру. Между этими двумя функциями располагается вызов функции Тгап$1а1:еМе$$аде, которая преобразует аппаратные сообщения клавиатуры в символьные сообще- ния. Если аппаратным сообщением является ММ_КЕУООММ, а нажатая клавиша в сочетании с положением клавиш-модификаторов соответствует некоторому сим- волу, то Тгап$1а1:еМе$$аде помещает символьное сообщение в очередь сообщений. В результате системной обработки сообщения ММ_КЕУООММ может быть ини- циировано символьное сообщение ММ_СНАК или ММ_ОЕАОСНАК. Стоит привести краткие пояснения о назначении сообщения ММ_ОЕАОСНАК. На некоторых неамериканских клавиатурах имеются так называемые немые кла- виши (с1еа(1 кеу$), предназначенные для ввода диакритических знаков, таких как «умляут», например. Диакритические знаки вводятся в паре с обычными симво- лами, в результате чего формируется составной символ. Например, для ввода на немецкой клавиатуре символа «ЕЕ» (Н-умляут) пользователь сначала нажимает немую клавишу «умляут», а затем — клавишу II. К счастью, вам не нужно обраба- тывать в вашей программе сообщение ММ_ОЕАОСНАК, поскольку АУтйомга берет это на себя, вырабатывая в результате сообщение ММ_СНАК, которое содержит Л1Ч51-КОД буквы с диакритическим знаком. Таким образом, обработка символьных сообщений обычно сводится к обра- ботке сообщения ММ_СНАК. Параметр 1Рагат сообщения ММ_СНАК, передаваемый в оконную процедуру, имеет то же значение, что и параметр 1Рагат породившего его аппаратного сообщения. Параметр доРагат содержит код символа АМ81. Символьные сообщения передаются в оконную процедуру в промежутке меж- ду аппаратными сообщениями клавиатуры. Например, если переключатель Сарз 1_оск выключен1, а пользователь нажимает и отпускает клавишу А, то оконная про- цедура получит следующие три сообщения: Сообщение Клавиша или код \Л/М_КЕУОО\Л/1\1 Виртуальная клавиша А \Л/М_СНАк АЫЗЬкод символа 'а' \Л/М_КЕУЬР Виртуальная клавиша А Если пользователь, удерживая клавишу 5Н1Т1, нажимает клавишу А, отпускает клавишу А и затем отпускает клавишу 5Н1Т1, то оконная процедура получит пять сообщений: 1 Состояние клавиши-переключателя Сар$ 1_оск контролируется при помощи светового индикатора <Сар$ 1_оск>.
Сообщение Клавиша или код \Л/М_КЕУОО\Л/Ы \Л/М_КЕУОО\Л/Ы \Л/М_СНАР. \Л/М_КЕУЬР \Л/М_КЕУЬР Виртуальная клавиша \/К_8Н1РТ Виртуальная клавиша А АЫ81-КОД символа А Виртуальная клавиша А Виртуальная клавиша \/К_8Н1РТ Работа с кареткой Каретка — это небольшой символ в виде вертикальной или горизонтальной черты. Иногда каретка отображается в виде прямоугольника, который показы- вает пользователю место на экране, где будет отображен очередной символ, вво- димый с клавиатуры. Если вы создаете программу, реализующую функции уп- рощенного текстового редактора, то необходимо позаботиться и об управлении кареткой. Для этого в АУш32 АР1 предусмотрены функции, приведенные в следующем списке: □ Функция СгеаЕеСагеЕ создает связанную с окном каретку. □ Функция ЗеЕСагеЕРоз устанавливает положение каретки в окне. □ Функция 8Но\л/Саге1: показывает каретку. □ Функция Н1с1еСаге1: прячет каретку. □ Функция ОезЕгоуСагеЕ удаляет каретку. Однако вы не можете просто создать каретку при обработке сообщения ММ_СКЕАТЕ и удалить ее при обработке сообщения ММ_ОЕ$ТКОУ. Каретка является общесистемным ресурсом и должна разделяться между всеми работающими при- ложениями. Следует помнить, что появление каретки в окне имеет смысл только в том случае, когда окно имеет фокус ввода. Оконная процедура может контроли- ровать моменты, когда окно получает или теряет фокус ввода, обрабатывая сооб- щения ММ_$ЕТЕОС115 и \Л/М_К11_1_ЕОСи5. Это диктует основные правила использования каретки. Оконная процедура вызывает функцию СгеаЕеСаге!: при обработке сообщения ММ_$ЕТЕОС115 и функцию ОезЕгоуСагеЕ при обработке сообщения ММ_К11_1_ЕОС115. Рассмотрим подробней использование функции СгеаЕеСаге!:, имеющей следую- щий прототип: В001 СгеаЦеСагеЦ(Ни/ИО Шпс1, НВ1ТМАР ИВПтар, Ш ШШИ, Ш пНе1дШ; Первый параметр функции содержит дескриптор окна, которое становится владельцем создаваемой каретки. Второй параметр определяет форму каретки. В нем можно указать дескриптор растра, единичное значение или значение МОП. Если параметр НВйтар равен МОП, то каретка имеет форму прямоугольного бло- ка шириной пУУтсИН и высотой пНе1дНЕ закрашенного черной однородной кистью. Если параметр НВйтар равен единице, то форма каретки будет такой же, что и для значения МОП, но заполняться блок будет черно-белой кистью с шахмат- ным рисунком, что выглядит внешне как серый цвет. При единичной ширине ка- ретки она будет выглядеть как пунктирная линия.
Если параметру НВИтар передается дескриптор растра, то форма каретки опре- деляется этим растром. В этом случае параметры п\Л/1’с11Н и п НетдЫ: игнорируются. Параметры п\Л/1д1:Н и пНе1дН1 задают ширину и высоту каретки в логических единицах. Их использование уже было описано выше. Если параметр пМ1’с11Н име- ет нулевое значение, то используется предопределенная в системе ширина рамки для окна, которая обычно равна одному пикселу. Если пНетдН! имеет нулевое зна- чение, то используется предопределенная в системе высота рамки для окна, кото- рая тоже обычно равна одному пикселу1. В случае успешного завершения функция СгеаЕеСагеЕ возвращает ненулевое значение. Нулевое значение служит признаком ошибки. После создания каретки функцией СгеаЕеСагеЕ она все еще остается скрытой. Чтобы каретка стала видимой, необходимо вызвать функцию ЗНохл/СагеЕ. Перед уничтожением каретки с помощью функции ОезЕгоуСагеЕ рекомендуется убрать ее с экрана функцией Н1 деСаге!. Примитивный текстовый редактор Программа Турег, код которой приведен в листинге 4.1, демонстрирует принципы и технику обработки аппаратных и символьных сообщений клавиатуры. Програм- му можно рассматривать как примитивный текстовый редактор. Пользователь может набирать в окне текст, переходить на следующую строку, нажимая клави- шу ЕпЕег, перемещать каретку по уже набранному тексту при помощи клавиш уп- равления курсором, вводить новый текст в том месте, на которое указывает ка- ретка, или удалять предшествующий символ. Однако у этого редактора нет ни полос прокрутки, ни функций поиска и замены строк, ни возможности сохранять текст в файле. Для хранения текста, введенного с клавиатуры, в программе используется объект класса 81лпд из стандартной библиотеки С++ вместо традиционной С-строки. Благодаря автоматическому управлению выделением и освобождени- ем памяти в этом классе мы можем писать более компактный программный код, не отвлекаясь на детали реализации. Чтобы использовать в программе объекты класса 81лпд, необходимо в начале файла добавить следующие директивы: #1пс1ис1е <81г1пд> из1пд патезрасе 81с1; Переменная (объект класса 81л пд) для хранения введенных символов объяв- ляется следующим оператором: з1а11с 81г1пд 1ех1: Спецификатор 81а11’с необходим для сохранения текущего значения перемен- ной 1ех1 до следующего вызова оконной процедуры2. Хотя предполагается, что читатель знаком с классом 81лпд, мы все же напом- ним назначение тех операций и методов класса, которые используются в коде. 1 Предопределенные в системе ширину и высоту рамки для окна можно получить вызовами функ- ций Се18у51етМе1пс5 (8М_СХВ0 КО ЕК) и Се18у51етМе1пс5 (8М_СУВ0К0ЕК) соответственно. 2 Локальные переменные, не имеющие спецификатора 81а1п'с, инициализируются заново при каждом вызове функции УУпс!Ргос.
Добавление символа $утЬ в конец строки осуществляется с помощью опера- ции += : ТехТ += зутЬ; Метод 1пзег1: позволяет вставлять символ зутЬ в произвольную позицию 1 пдех: Сех1з. 1 П8ег1з( 1 пс1ех, зутЬ); Для удаления п символов, начиная с позиции 1’пс1ех, вызывается метод егазе: ТехТ.егазе(1пс1ех, п); В любой момент можно узнать текущий размер строки при помощи метода 81/е, например, в выражениях, подобных следующему: п1_1пез = ТехТ.з1хе() / пСВагРегЫпе; И наконец, содержимое строки ±ех± можно преобразовать к типу обычной С-строки при помощи метода с_$1:г. А это пригодится при вызове функции ТехТОи!:, поскольку она принимает в качестве четвертого параметра адрес С-строки. Обратите внимание на обработку сообщений ММ_КЕУООММ, ММ_СНАК, ММ_$ЕТЕОС115, уум.кшЕосиз, а также на использование редактором Турег системного фиксиро- ванного шрифта для вывода текста. Создание текстового редактора для пропор- ционального шрифта было бы более трудной задачей. Листинг 4.1. Проект Турег1 ////////////////////////////////////////////////////////////////////// // Турег.срр #1пс1ис1е <\л/1 пс1о\л/8. В> #1пс1ис1е <зТг1пд> из1пд патезрасе зТс1; #1пс1ис1е «К1л1пс1. И» ККЕ81ЛТ САИВАСК ШбРгос(НШО, 1ЛМТ, ПРАВАМ, ЕРАВАМ); //============================================ 1пТ ШИАР1 ШпМа1п(Н1М8ТАМСЕ ЩпзТапсе, Н1М5ТАМСЕ ИРгеу1пзТапсе, ЕРЗТЕ 1рСтсИ1пе, 1 пТ пСтс15Но\л/) { М86 тзд; К1л1пс1 та1п1л1псК«Турег», И1пзТапсе, пСтсЕИом, 1л1пс1Ргос); \л/Гп'1е (СеТМеззадеС&тзд, МВЕЕ, 0, 0)) { Тгапз1аТеМеззаде(&тзд); 01зраТсИМеззаде(&тзд); } геТигп тзд.мРапат; } //======================== ЕЕЕЗЕЕТ САЕЕВАСК ШбРпос(НШВ Шпс1, 1ИМТ иМзд, ПРАВАМ мРапат, ЕРАВАМ 1Рапат) { НОС ВВС; РА1МТЗТЕВСТ рз; л г продолжение & 1 Не забудьте добавить к проекту файлы К1л1пс1. В и К1л1пс1. срр, код которых приведен в листинге 1.2 (гла- ва 1).
Листинг 4.1 {продолжение} ТЕХТМЕТкЮ зЬаЫ с зЬг1пд ЬехЬ; // буфер для хранения введенного текста збг1пд зутЬ; зЬаЫс Ш схСЬаг, суСЬаг, схСИепЬ, суСИепЬ; зЬаЫ с 1пЬ пСИагРегЫпе, пСИепШпез; зЬаЫс Ш хСагеб = 0, уСагеб = 0; 1пЬ сыгМех; 1пЬ пЫпез; // число «полных» строк текста 1пЬ пТаЮаг; // число символов в последней строке 1 пЬ х, у, 1; 8\л/ПсИ (иМзд) { сазе Ш-СРЕАТЕ: НОС = бебОС(Шпб); 8е1есШэдесШ0С, 6еШоск0ЬдесШХ8ТЕМ_Е1ХЕ0_Е0МТ)); 6еЬТехЬМебг1сз(Ь0С, &Ш; схСЬаг = ШбтАуеСЬагШсЕЬ; суСЬаг = Ьт.ЬтНе1дИЬ; Ре1 еазеОС(Шпб, НОС); Ьгеак; сазе Ш-812Е: // получить размеры окна в пикселах схСИепЬ = ЬОШРОО Рагат); суСИепЬ = НШ0Р0(1 Рагат); // вычислить размеры окна в символах (по ’х') и строках (по 'у') гОагРегЫпе = тах(1, схСИепЬ / схСЬаг); пСИепШпез = тах(1, суСИепЬ / суСЬаг); 1Г (Н1л/пс1 == беЬЕосызО) 8еЬСагеЬРоз(хСагеЬ * схСЬаг, уСагеб * суСЬаг); Ьгеак; сазе Ш_8ЕТЕСО8: // создать и показать каретку СгеаЬеСагеШЫпсЬ МЫЕЕ, 0, суСЬаг); 8еЬСагеЬРоз(хСагеЬ * схСЬаг, уСагеб * суСЬаг); ЗЬомСагеб(Шпс1); Ьгеак; сазе 1л/М_К1 ЕЕЕ0СЫ8: // спрятать и уничтожить каретку Н1 ЬеСагеб (Шпс1); ОезбгоуСагеб(); Ьгеак; сазе 1л1М_КЕУОО1л1М: пЫпез = ЬехЬ.зыеО / гОагРегЫпе; пТаПСИаг = ЬехЬ.зыеО % гОагРегЫпе; змПсЬ ЫРагат) { сазе \/К_НОМЕ: хСагеб = 0; Ьгеак;
сазе \/К_ЕИО: 1Т (уСагеТ == пЫпез) хСагеТ = пТаЮаг; е!зе хСагеТ = пСИагРегЫпе - 1; Ьгеак; сазе \/К_РВ10В: уСагеТ = 0; Ьгеак; сазе УК—ИЕХТ: уСагеТ = пЫпез; хСагеЬ = пТаЮаг; Ьгеак; сазе УК—ЕЕЕТ: хСагеЬ = тах(хСагеЬ - 1, 0); Ьгеак; сазе УК—ЕЮНТ: хСагеЬ = гтпп(хСагеТ + 1, пСИагРегЫпе - 1); 1Т ((уСагеТ == пЫпез) && (хСагеЬ > пТаЮаг)) хСагеЬ = пТаЮаг; Ьгеак; сазе УК_Е1Р: уСагеТ = тах(уСагеТ -1,0); Ьгеак; сазе УК—00Ш: уСагеТ = тШуСагеТ + 1, пЫпез); 1Т ((уСагеТ == пЫпез) && (хСагеЬ > пТаЫСкаг)) хСагеЬ = пТаЮаг; Ьгеак; сазе УК—ОЕЕЕТЕ: сиг1пс1ех = уСагеТ * пСкагРегЫпе + хСагеЬ; 1Т (сиг1пс1ех < ЬехЬ.зыеО) { ТехТ.егазе(сиг1пс1ех, 1); 1пуа11с1аТеВесТ(Шпс1, Ш, ТРОЕ); } Ьгеак; } 8еТСагеТРоз(хСагеТ * схСкаг, уСагеТ * суСкаг); Ьгеак; сазе Ш_СНАЕ: зЫЬсИ (\л/Рагат) { сазе '\Ь': // символ 'Ьаскзрасе' 1Т (хСагеЬ > 0) { хСагеЬ--; 8епс1Меззаде(Шпс1, Ш_Ш0СЖ УК_ОЕЕЕТЕ, 1); } Ьгеак; сазе '\Ь': // символ 'ЬаЬ' с1о { 8епс1Меззаде(Шпс1, ЫМ_СНАР, ' 1Е); } мЫ1е (хСагеЬ % 8 != 0); Ьгеак; сазе '\г': сазе '\п': // возврат каретки или перевод строки Тог (1 = 0; 1 < пСИагРегЫпе - хСагеТ; ++1) ТехТ += ' '; хСагеТ = 0; 1Т (++уСагеТ == пСЫепТЫпез) { МеззадеВох(Шпс1, "Нет места в окне", "Еггог", МВ_ОК); уСагеТ--; } продолжение
Листинг 4.1 {продолжение} Ьгеак; ЬеГаи1Р: // любой другой символ сиг!пс1ех = уСагеР * пСЬагРегЫпе + хСагеР; 1Г (сигМех == РехР.зыеО) РехР += (сбаг)мРагат; е!зе { зутЬ = (сбаг)мРагат; РехР. 1пзегР( сиг Мех, зутЬ); } Ма11с1аРеВесР(Шпс1, Ш1, ТРОЕ); 1Г (++хСагеР == пСЬагРегЫпе) { хСагеР = 0; 1Г (++уСагеР == пСИепШпез) { МеззадеВох(Шпс1, "Нет места в окне", "Еггог", МВ_ОК); уСагеР--; } } Ьгеак; } ЗеРСагеРРоз(хСагеР * схСНаг, уСагеР * суСЬаг); Ьгеак; сазе Ш-РА1МТ: НОС = ВедапРаапР(Шпб, &рз); 8е1есР0ЬдесР(ЬВС, СеР8Роск0ЬдесР(8У8ТЕМ_Е1ХЕВ_Е0МТ)); 1Г (РехР.зыеО) { пЫпез = РехР.зыеО / гОагРегЫпе; пТаЮаг = РехР.зыеО % гОагРегЫпе; Гог (у = 0; у < пЫпез; ++у) ТехРОиРМС, 0, у*суСЬаг, РехР.с_зРг() + у*гОагРегЫпе, гОагРегЫпе); ТехРОиРМС, 0, у*суСЬаг, РехР.с_зРг() + у*гОагРегЫпе, пТаЮаг); } Епс1Ра1 пР(Шпс1, &рз); Ьгеак; сазе Ш_ВЕ8ТВ0У: РозШПГМеззаде(О); Ьгеак; ЬеГаиИ: геРигп ВеГ1л11 пбомРгос(Шпс1, иМзд, мРагат, 1Рагат); } геРигп 0; } ////////////////////////////////////////////////////////////////////// Откомпилируйте проект и посмотрите, как будет работать текстовый редак- тор Турег. Поэкспериментируйте с изменением параметров каретки. Для этого най- дите в блоке обработки сообщения ММ_$ЕТЕОС115 следующую инструкцию:
СгеаШагеШШпс!, М1Л_1_, 0, суСИаг); Поменяйте ее сначала на инструкцию Сгеа^еСагеНШпс!, 1, 0, суСИаг); а затем на инструкцию СгеаЦеСагеЦ(Шпс1, 1, 10, суСНаг); наблюдая каждый раз после компиляции, как будет меняться форма каретки. Мышь Мышь — это устройство ввода информации с одной или более кнопками. АУш32 АР1 поддерживает однокнопочную, двухкнопочную или трехкнопочную мышь, а также мышь с колесиком. Наличие мыши можно определить при помощи функ- ции бе'ЬЗуз'ЬетМе'Ьпсз: Шизе = СМ8узШпМе^г1сз (8М_М0и8ЕРЕЕ8ЕМТ); Если мышь установлена, то значение ГМоизе будет равно ТК11Е. Количество кно- пок у мыши определяется следующим вызовом: сВиНопз = (М8уз^етМе^г1сз (8М_СМ0и8ЕВЫТТ0М8); Если мышь не установлена, то функция вернет нулевое значение. Для двухкнопочной мыши, которая наиболее популярна среди пользователей, сложилась некоторая традиция интерпретации ее кнопок. Левая кнопка считает- ся главной и выполняет самые распространенные действия, к которым относятся выбор предметов, перетаскивание значков и инициирование действий. Правая кнопка чаще всего используется для вызова контекстного меню. Оно появляется там, где находится курсор мыши, и набор его функций соответствует возможнос- тям, доступным в этой области. Трехкнопочная мышь не отличалась особой популярностью у пользователей АУшсЬауз, пока МхсгозоЙ не выпустила модель Тп^еШМопзе. Хотя эту мышь труд- но считать интеллектуальной в прямом смысле этого слова, ее новый конструк- тивный элемент в виде небольшого колесика, расположенного между левой и пра- вой кнопками, позволяет использовать множество дополнительных функций. При нажатии на колесико оно функционирует как средняя кнопка. Но кроме этого колесико можно прокручивать, и программы, поддерживающие эту функцию, будут реагировать прокруткой или масштабированием документа. Это намного удобнее, чем работать мышью на полосе прокрутки, так как колесико выполняет свои функции даже тогда, когда курсор мыши находится за пределами окна с до- кументом. Наличие мыши с колесиком можно определить при помощи вызова все той же функции Ое'ЬЗуз'ЬетМе'Ьпсз: ТМоизеШее! = 0е^8узШМе^п'сз(8М_М0и8ЕШЕЕЕРРЕ8ЕМТ); Функция возвращает значение ТК11Е, если у мыши есть колесо. Хотя мышь является важнейшим средством ввода для пользователя, разработ- чики программ для АУхшАоауз часто дублируют, если это возможно, «мышиный» интерфейс соответствующим клавиатурным интерфейсом. При разработке новых программ весьма желательно придерживаться этой концепции, ориентированной на обеспечение максимума удобств для конечного пользователя.
Терминология, связанная с мышью Когда пользователь перемещает мышь, \У1пс1о\\ъ передвигает по экрану неболь- шую растровую картинку, которая выступает в качестве курсора мыши (тоизе сигзог). У курсора мыши есть горячая точка (йоё зрой) — пиксел, соответствующий положению курсора на экране. Когда говорят о позиции курсора мыши, то имеют в виду именно позицию горячей точки. Например, горячая точка стандартного курсора в виде стрелки (ЮС_АКК0М) находится на кончике стрелки. Другой кур- сор, в виде перекрестья (ЮС_СК055), имеет горячую точку в центре крестообраз- ного шаблона. Следует помнить, что курсор, используемый по умолчанию, для конкретного окна задается при определении структуры класса окна. Например, следующим оператором: мс.ИСигзог = 1_оас1Сигзог (М1Л_1_, 1ЭС_АЖМ): Действия пользователя с мышью принято описывать следующими терминами: □ Щелчок — нажатие и отпускание кнопки мыши. □ Двойной щелчок — быстрое двойное нажатие и отпускание кнопки мыши. □ Перетаскивание — перемещение мыши при нажатой кнопке. Кнопки трехкнопочной мыши называются левой, средней и правой кнопками. В связанных с мышью идентификаторах, которые определены в заголовочных файлах \У1пс1о\\у используются аббревиатуры ЕВ11ТТОМ, МВ11ТТОМ и КВОТТОМ. На двухкнопочной мыши имеются только левая и правая кнопки. Единственная кноп- ка однокнопочной мыши является левой. Сообщения мыши Изучая в предыдущем разделе ввод с клавиатуры, мы видели, что АУтсЕпуз посы- лает сообщения клавиатуры только тому окну, которое имеет фокус ввода. С мы- шью \Упк1о\\ъ ведет себя несколько иначе. Оконная процедура получает сообще- ния мыши, и когда мышь проходит через окно, и при щелчке внутри окна, даже если окно не активно или не имеет фокуса ввода. Если мышь перемещается по клиентской области окна, то оконная процедура получает сообщение ММ_МО115ЕМО\/Е. Если кнопка мыши нажимается или отпус- кается внутри клиентской области, то оконная процедура получает следующие сообщения: Кнопка Нажатие Отпускание Нажатие (второй щелчок) Левая ММ-кВОТТОЫОСМ/Ы №МЦ_ВиТТО1\111Р У7М_1_В1ПТОЫОВ1_С1_К Средняя \л/м_мвиттоыоо\л/ы \л/м_мвиттоыир \Л/М_МВиТТОЫОВ1С1К Правая \л/м_квиттоыоо\л/ы №М^В11ТТО1\111Р №М^В11ТТОЫОВ1_С1_К Для всех этих сообщений значение параметра 1Рагат содержит положение мыши. При этом в младшем слове находится значение координаты х, а в старшем слове — значение координаты у. Отсчет координат ведется от левого верхнего угла клиентской области окна. Эти значения можно извлечь из 1Рагат при помощи мак- росов ЮУУОКО и Н1МСЖ0.
Значение параметра доРагат показывает состояние кнопок мыши и клавиш 5Н1Г1: и Йг1. Вы можете проверить параметр доРагат при помощи соответствующих бито- вых масок: Константа Описание МК_1_В11ТТОЫ Левая клавиша нажата МК-МВ11ТТОЫ Средняя клавиша нажата МК_кВ11ТТОЫ Правая клавиша нажата МК_8Н11=Т Клавиша ЗЫН: нажата МК_СО1\1ТкО1_ Клавиша СШ нажата Если пользователь щелкнет левой кнопкой мыши в клиентской области неак- тивного окна, то \У1пс1о\\ъ сделает это окно активным, а затем передаст оконной процедуре сообщение ДОМ_1_В11ТТОМ СОДОМ. Обработка двойного щелчка Два последовательных щелчка воспринимаются системой как двойной щелчок, если они происходят в течение достаточно короткого промежутка времени. Пользо- ватель может самостоятельно задать этот интервал при помощи системного при- ложения Мышь (Мои$е), которое можно найти в группе Панель управления. Следует особо подчеркнуть, что окно будет получать сообщения о двойном щелчке (ОВ1С1К) только в том случае, если стиль соответствующего класса окна содержит флаг С5_СВ1_С1_К5. Поэтому перед регистрацией класса окна нужно при- своить полю 8Еу1е структуры ДОМ0С1А55 значение, включающее этот флаг. Впро- чем, если используется объект класса К\Л/пс1, то следует модифицировать стиль класса окна вызовом следующих инструкций: зУу!е = 6еЮа881опд(Шпс1, 6С1__5ТУ1Е); 8еЮа881опд(Шпс1, СС1__5ТУ1_Е, 8Цу1 е | СЗ_0ВЕСЕКЗ); Если класс окна определен без флага С5_СВ1_С1_К5 и пользователь делает двойной щелчок левой кнопкой мыши, то оконная процедура последовательно получает со- общения ДОМ_1_В1ПТОМ СОДОМ, ДОМ_1_В1ЛТОМОР, ДОМ_1_В1ПТОМ СОДОМ и ДОМ_1_В ОТТОМ IIР. Если класс окна определен с флагом С5_СВ1_С1_К5, то после двойного щелчка окон- ная процедура получит сообщения ДОМ_1_ВОТТОМСОДОМ, ДОМ_1_ВОТТОМОР, ДОМ_ 1ВОТТОМ- □ В1_С1_К и ДОМ_1_ВОТТОМОР. Легко заметить, что в этом случае второе сообщение ДОМ_1_ВОТТОМСОДОМ заменяется сообщением ДОМ_1_В1ЛТОМСВ1_С1_К. Пример обработки сообщения ДОМ_1_ВОТТОМ СВ 1_С1_К приведен в листинге 4.4. Обработка сообщений от колеса мыши При нажатии на колесо мыши АУшбом^ генерирует такие же сообщения, какие вырабатываются при нажатии средней кнопки трехкнопочной мыши. Прокрутка же колесика вызывает сообщение ДОМ_М005ЕДОНЕЕ1_. Если вам нужно обрабатывать это сообщение в коде программы, то в начале файла добавьте следующую директиву: #1пс1ис1е <2гтюи8е.И> В заголовочном файле гтоизе.Н определены все необходимые константы.
Младшая часть параметра \л/Рагат сообщения ММ_МО115ЕМНЕЕ1_ интерпретиру- ется точно так же, как для других сообщений мыши, то есть показывает состояние кнопок мыши и клавиш 5Н1Т1: и С1г1. Старшая часть параметра доРагат содержит значение, отображающее дистан- цию, пройденную колесом. Оно рассчитывается как количество шагов колеса при прокрутке, умноженное на коэффициент МНЕЕ1__ОЕ1_ТА. В заголовочном файле 2ГПои$е.Н этот коэффициент определен со значением 120. Обработка сообщения ММ_МО115ЕМНЕЕ1_ довольно проста, и ее пример можно найти в листинге 4.5. Теперь рассмотрим примеры программного кода. Рисуем мышью В любом графическом редакторе, как правило, предусмотрена возможность рисо- вать линии произвольной формы с помощью мыши. Пользователь устанавливает курсор на начальную точку линии, нажимает левую кнопку мыши и, не отпуская кнопку, перемещает мышь, рисуя прямые, кривые и другие замысловатые линии. Рисование линии заканчивается, когда левая кнопка отпускается. Попробуем реализовать эту операцию в программе 5слЬЫе (листинг 4.2). Листинг 4.2. Проект ЗспЬЫе ////////////////////////////////////////////////////////////////////// // ЗтЬЫе.срр #1пс1ис1е <\л/1 пс1о\л/8. В> #1пс1ис1е "КМпсКГГ СКЕ81ЛТ САЕЕВАСК ШбРгос(НШВ, С1МТ, ПРАВАМ, КРАКАМ); ////////////////////////////////////////////////////////////////////// 1пР ШИАР1 ШпМапп(НIИЗТАМСЕ ВТпзШсе, Н1МЗТАМСЕ ВРгеуЩз^апсе, ЕРЗТЕ 1рСтсИ1пе, 1гД пСтсЕВом) { МЗО тзд; Шпс1 та1 п1л/пс1 ("ЗсгПэЫе", ИТпзРапсе, пСтсЕВом, 1л1пс1Ргос); мВ11е (Се^Ме88аде(&т8д, ИВЕЕ, 0, 0)) { ТгапзШеМеззадеС&тзд); В18ра^сВМе88аде(&т8д); } ге^игп тзд.мРагат; } /============================= ЕЕЕЗЕЕТ САЕЕВАСК ШбРгос(ШИВ Шпс1, В11\1Т иМзд, АРАРАМ мРагат, ЕРАВАМ 1Рагат) { 8^1с НОС ВВС; 8Ш1с 1пР х, у; // позиция курсора мыши 8^1 с ВООЕ ЬТгаск1пд = ЕАЕЗЕ; 8мПсВ (иМзд) { сазе Ш_СВЕАТЕ: ВВС = ОеЦВС(ВВпб);
Ьгеак; сазе Ш_ЕВЬТТОШОШ: ЬТгаск1пд = ТВОЕ; // начальная позиция х = ЮШЖ! Рагат); у = НШ0Р0(1 Рагат); МоуеТоЕх(ИОС, х, у, ШЕЕ); Ьгеак; сазе 1л/М_ЕВЕ1ТТОМЕ1Р: ЬТгаскппд = ЕАЕЗЕ; Ьгеак; сазе Ш_М0Ь8ЕМ07Е: 1Т (ЬТгаскппд) { // новая позиция х = Ь0ШР0(1 Рагат); у = НШ0РВ(1 Рагат); ЫпеТоШВС, х, у); } Ьгеак; сазе Ш_ВЕ8ТР0У: Ре1еазеВС(Шпс1, ВВС); РозЬОьп ЬМеззаде(0); Ьгеак; ЬеТаиИ: геЬигп ОеТШпсЫРгос(Шпс1, иМзд, мРагат, 1Рагат); } геЬигп 0; } ////////////////////////////////////////////////////////////////////// Процесс рисования очередной линии начинается с нажатия левой кнопки мыши. Программа, обрабатывая сообщение ММ_1_ БОТТОМСОМ И, устанавливает флаг слежения за мышью ЬТтаскппд в значение ТРОЕ и задает начальную позицию пера при помощи функции МоуеТоЕх. После этого при обработке сообщения ММ_МО115ЕМО\/Е программа просто проводит линию в текущую позицию курсора мыши. Когда пользователь отпускает кнопку мыши, флаг ЬТгаскп’пд сбрасывает- ся в ЕА1_8Е и рисование прекращается. Обратите особое внимание на то, с каким контекстом устройства работают ри- сующие функции МоуеТоЕх и ИвеТо. Дескриптор контекста дисплея НОС объявлен со спецификатором з'ЬаНс. Это раз. Программа получает контекст устройства при помощи функции СеЬОС в блоке обработки сообщения ММ_СКЕАТЕ, а освобождает его с помощью Ке1еа$еОС в блоке обработки сообщения ММ_ОЕ$ТКОУ. Это два. Все это позволяет обновлять клиентскую область непосредственно в ответ на пользо- вательский ввод информации с помощью мыши, не прибегая к механизмам, гене- рирующим сообщение ММ_РА1МТ. Иными словами, обеспечивается минимальная задержка между вводом пользователя и реакцией приложения. Статический класс памяти для переменной НОС здесь необходим потому, что требуется сохранять значение атрибута «текущая позиция пера», используемого и модифицируемого функцией ИвеТо, между двумя последовательными вызовами функции \Л/вс1 Ргос.
На рис. 4.2 показан результат испытаний «графического редактора» 5слЬЫе. Рис. 4.2. Рисование мышью в редакторе ЗспЬЫе Но что это?.. Как только пользователь пытается изменять размеры окна, ухва- тившись мышью за его рамку, изображение тотчас бесследно исчезает! Ах, да... Мы же забыли про обработку сообщения ММ_РА1МТ, которое генерируется при из- менении размеров окна! Давайте устраним эту оплошность. Но что же будет рисовать приложение в блоке обработки сообщения ММ_РА1МТ? Ведь информация о траекториях движе- ния мыши нигде не сохранена! Значит, надо где-то сохранять массивы координат позиций мыши для каждой рисуемой линии. Сложность проблемы состоит в том, что количество линий заранее неизвестно, так же как и количество точек в каж- дой линии может быть произвольным. Таким образом, использование статичес- ких массивов будет крайне неудобным. На выручку в таких ситуациях приходит замечательный класс уес1юг из стан- дартной библиотеки шаблонов 8ТЬ. Объект класса уес1юг — это, фактически, ди- намический массив с переменным размером, обслуживаемый набором удобных для программиста методов. Аналогично классу $Ь1пд класс уес1юг обеспечивает автоматическое управление выделением и освобождением памяти. Чтобы использовать в программе объекты класса уеНог, необходимо в начало файла с исходным кодом добавить следующие директивы: #1пс1ис1е <уесДог> из1пд патезрасе зЦб; Класс уе Но г является шаблонным классом, поэтому при объявлении его объекта необходимо указать в угловых скобках имя конкретного типа, как показано ниже: уесДог<1пР> VI; // VI - вектор элементов типа Ш Мы, конечно, не будем рассматривать здесь все методы класса уеНог, а остано- вимся кратко только на тех, которые нужны для нашей программы. После объявления вектора VI, приведенного выше, этот вектор пуст, то есть не содержит ни одного элемента. Добавление нового элемента в конец вектора осу- ществляется при помощи метода ри$Н_Ьаск: VI. ризМэаск(уа! ие); Эта инструкция добавляет новый элемент типа 1П1: в конец вектора VI и при- сваивает ему значение переменной уа1ие.
Количество элементов в векторе в любой момент времени можно определить при помощи метода 81/е. Доступ к произвольному элементу вектора осуществляется либо через индекс, как в обычном массиве, либо при помощи так называемого итератора, что похо- же на доступ через указатель в обычном массиве. В следующем фрагменте кода показан второй способ доступа к элементам вектора на примере обхода всех эле- ментов вектора VI: уесШ<Ш>:: НегаШ И; // объявление итератора И для класса уесбог<1 пб> Тог (И = У1.бед1п(); И != У1.епс1(); ++П) { // - значение элемента с адресом И } Здесь Ьедп’п() — метод, возвращающий адрес первого элемента вектора VI, а епс1() — метод, возвращающий адрес воображаемого элемента, который мог бы следовать за последним элементом вектора VI. Если нужно удалить все элементы из вектора и сделать его пустым, то приме- няется вызов метода с1еаг. Теперь все необходимые методы класса Vес1:о^ рассмотрены, и можно вернуть- ся к доработке программы. Сначала нужно определиться с тем, какие переменные должны быть добавле- ны для сохранения информации о траекториях движения мыши. Каждая позиция курсора мыши описывается координатами х и у. В АУш32 АР1 есть структура РО1МТ, которая позволяет объединять эти два значения. Поэтому, казалось бы, можно объявить следующий объект: збабтс уесбог<Р01МТ> сигуе; для накопления точек, описывающих текущую кривую линию. Но, к сожалению, в определении структуры РОНХ1Т нет конструктора, который позволял бы исполь- зовать анонимные экземпляры класса1 РО1МТ(х, у), и поэтому компилятор не про- пустит выражения следующего вида: сигуе.ризН—Ьаск(Р011\1Т(х, у)); Значит, нам нужно определить свою структуру Ро1’п1:, в которой устранен ука- занный недостаток: збгисб Ро1пб { Ро1пб(1пб _х = 0, 1пб _у = 0) : х(_х), у(_у) {} Ш х; Ш у; Е Теперь определение объекта для накопления точек, представляющих текущую кривую линию, будет иметь следующий вид: збабтс уесбог<Ро1пб> сигуе; Накопление заканчивается, как только пользователь отпускает кнопку мыши, и оконная процедура получает сообщение ММ_1_В11ТТО1\111Р. Сформированный век- тор сип/е необходимо сохранить в другом векторе сип/ез, который будет хранили- щем для накопления объектов сип/е: збаб1с уесбог<уесбог<Ро1 пб> > сигуез; 1 Напомним, что структура является частным случаем класса, в котором все члены по умолчанию считаются открытыми (рпЬНс). Анонимный экземпляр класса — это временный объект, создаваемый явным вызовом конструктора.
В каком-то смысле объект сип/е$ является аналогом двумерного массива. Его элементами являются объекты типа уес1:ог<Ро1’п1:>. Обратите внимание на пробел между двумя последними угловыми скобками. Если его не поставить, то компи- лятор выдаст сообщение об ошибке. Текст приложения 5сл’ЬЫеАскапсес1, являющегося улучшенной версий Зсл’ЬЫе, приведен в листинге 4.3. Листинг 4.3. Проект 5слЬЫеАс1уапсес1 ////////////////////////////////////////////////////////////////////// // 8сг1ЬЫеАсЕапсес1.срр #1пс1ыс1е <\л/1 пс1о\л/8. Н> #1пс1ис1е <уесРог> из1пд патезрасе збб; #1пс1ыс1е "К1л1пс1.Н" збгисб РоШ { Ро1пР(1пР _х = О, Ш _у = 0) : х(_х), у(_у) {} 1пР х; Ш у; }; ЕКЕЗОЕТ САЕЕВАСК 1л1пс1Ргос(НШ0, С1МТ, ПРАВАМ, КРАКАМ); //========================================== 1пР ШИАР1 ШпМапп(НIИЗТАМСЕ Щпзбапсе, Н1М5ТАМСЕ НРгеу1пзбапсе, ЕРЗТК 1рСтс1Е1пе, 1пР пСгткОом) { М86 тзд; К1л1пс1 та1пШЩ"8сг1ЬЬ1еАсКапсесГ, Н1пзбапсе, пСггкОом, МпбРгос); \л/Н11е (6ебМеззаде(&тзд, МЫЕЕ, 0, 0)) { Тгапз1абеМеззаде(&тзд); 01зрабсНМеззаде(&тзд); } геРигп тзд.мРагат; } //========================================== ЕКЕЗЕЕТ САЕЕВАСК ШбРгос(ШИВ Шпс1, С11\1Т иМзд, ПРАВАМ мРагат, ЕРАВАМ 1 Рагат) { зРаР1с НОС ВВС; // контекст устройства для рисования мышью НОС Мс; // контекст устройства для восстановления // рисунка при обработке Ш_РА1МТ РА1МТЗТВНСТ рз; збаб1с 1пР х, у; // позиция курсора мыши збаб1с ВООЕ ЬТгаскЩд = ЕАЕЗЕ; збаб1 с уесЕог<РоШ> сыгуе; збаб1с уесбог<уесбог<Ро1пб> > сыгуез; уесбог<Ро1пб>::1бегабог 1 б; Ш 1, ); з\л/Псб (иМзд) { сазе Ш_СВЕАТЕ: НОС = ОеООС(Шпб);
Ьгеак; сазе Ш_ЕВЬТТОМВОШ: ЬТгаск1пд = ТРОЕ; // начальная позиция х = Е0ШРВ(1 Рагат); у = НШ0РВ(1 Рагат); МоуеТоЕх(ЬВС, х, у, ШЕЕ); сигуе.ризЬ_Ьаск(Ро1пТ(х, у)); Ьгеак; сазе 1л/М_ЕВЕ1ТТОМЫР: 1Т (ЬТгаскппд) { ЬТгаск1пд = ЕАЕЗЕ: сыгуез.ризЬ_Ьаск(сигуе); сыгуе.с1еаг(); } Ьгеак; сазе Ш_М0Ь8ЕМ07Е: 1Т (ЬТгаскппд) { // новая позиция х = Е0ШРВ(1 Рагат); у = НШ0РВ(1 Рагат); ЫпеТо(ЬВС, х, у); сигуе.ризЬ_Ьаск(Ро1пТ(х, у)); } Ьгеак; сазе 1л/М_РА1МТ: Ьс1с = Веди пРап пТ(Шпс1, &рз); Тог (1 = 0; 1 < сыгуез.зыеО; ++1) { 1Т = сыгуез[1].Ьедп п(); МоуеТоЕх(Ьс1с, 1Т->х, 1Т->у, МЕ1ЕЕ); Тог (1Т + 1; 1Т != сигуез[1].епсК); ++1Т) ЫпеТо(Ьс1с, 1Т->х, 1Т->у); } Епс1Ра1пТ(Шпс1, &рз); Ьгеак; сазе Ш_ВЕ8ТВ0У: Ве1еазеВС(Ь1л1пс1, ЬОС); РозЩПТМеззадеСО); Ьгеак; с1еТаи!Т: геТигп ВеТШпсЫРгос(Шпс1, иМзд, мРагат, 1Рагат); } геТигп 0; } ////////////////////////////////////////////////////////////////////// Кроме тех дополнений, о которых упоминалось ранее, в этой программе по- явился также блок обработки сообщения ММ_РА1МТ. Заметьте, что рисование здесь производится с дескриптором контекста дисплея Н дс, который отличается от дес- криптора НОС, используемого при обработке сообщений от мыши.
Обратите внимание на то, что доступ к элементам вектора сип/е$ осуществля- ется по индексу, а доступ к элементам его элементов (то есть к объектам типа уес1юг<Ро1 пЬ») — с использованием итератора 4. Здесь у вас может возникнуть воп- рос: «Как же так, ранее говорилось, что значением элемента с адресом 4 является выражение *1Ч?» Да, все правильно, но учтите, что наш элемент является структу- рой типа Ро1П±, а в коде нужен доступ к ее полям х и у. Поэтому вызов функции ИпеТо мог бы выглядеть так: 1_1 пеТо(Г|с1с, (*П).х, (*П).у); Этот вызов тоже работал бы правильно. Но язык С++ содержит более удоб- ную синтаксическую конструкцию для такого случая, позволяя вместо (*1Ч).х за- писать 14->х. Поэтому вызов ИпеТо имеет следующий вид: 1_1пеТо(Нс1с, П->х, П->у); Теперь можно протестировать усовершенствованную версию программы и убедиться в том, что рисунок сохраняется после изменений размеров окна. Эластичные прямоугольники Графические редакторы, как правило, поддерживают операцию выделения неко- торой прямоугольной области. Пользователь нажимает левую кнопку мыши, фик- сируя одну из вершин прямоугольника, и перемещает мышь к противоположной вершине. В процессе перемещения мыши построение фигуры считается еще не законченным, и она выводится пунктирным контуром, который все время изме- няется. При отпускании кнопки мыши прямоугольник фиксируется, обознача- ясь, например, другим цветом или другим стилем линии контура. Приложение Е1а$йсКесЕ приведенное в листинге 4.4, демонстрирует реализа- цию подобной операции. Кроме того, в нем показана обработка двойного щелчка мыши, по которому программа рисует квадрат фиксированного размера. Рисование линий и фигур происходит на некотором фоне или на некотором изображении, которое расположено в клиентской области окна. Для простоты дальнейшего изложения этот фон или существующее изображение будет назы- ваться холстом. В основе рисования эластичных прямоугольников обычно лежит следую- щая алгоритмическая идея. Получив сообщение ММ_МО115ЕМО\/Е, программа должна стереть предшествующее изображение прямоугольника, затем полу- чить координаты новой позиции мыши и нарисовать новое изображение пря- моугольника. Проще всего стирание текущего изображения фигуры можно организовать, если в контексте устройства установлен режим рисования К2_ХОКРЕМ или К2_МОТХОКРЕМ. В этом случае повторный вывод фигуры приводит к восстановлению того состоя- ния холста, которое было перед первым выводом этой фигуры. Чем же различают- ся эти два режима или эти две растровые операции: К2_ХОКРЕМ и К2_МОТХОКРЕМ? Если фигура рисуется черным пером на белом холсте, то используйте бинар- ную растровую операцию К2_МОТХОКРЕМ. Если белым пером на черном холсте, то — К2_ХОКРЕМ. Почему? Для ответа на этот вопрос рассмотрим реализацию этих опе- раций в монохромной системе с кодировкой 1 бит/пиксел («О» — черный цвет, «1» — белый цвет), показанную в табл. 4.4 и 4.5.
Таблица 4.4. Операция К2_ХОКРЕЫ Холст Перо Результат операции К2_ХОКРЕЫ Примечание 0 0 0 0 1 1 Цвет пера 1 0 1 Цвет холста 1 1 0 Таблица 4.5. Операция К2_1\ЮТХОКРЕЫ Холст Перо Результат операции К2_ЫОТХОКРЕЫ Примечание 0 0 1 0 1 0 Цвет холста 1 0 0 Цвет пера 1 1 1 Когда фигура рисуется первый раз, то наибольший интерес представляют си- туации, в которых цвет пера и холста различаются, что соответствует второй и третьей строкам в таблицах. Естественно, мы хотели бы в результате этого рисо- вания получить цвет пера, а не холста. В графе «Примечание» показано, что такой результат достигается при рисовании белым пером на черном холсте для растро- вой операции К2_ХОКРЕМ и при рисовании черным пером на белом холсте для рас- тровой операции К2_МОТХОКРЕМ. Теперь вернемся к представлению цвета в формате КСВ. Так как для белого и черного цветов значения одного бита, приведенные в табл. 4.4 и 4.5, просто ти- ражируются по всем разрядам, то выявленные рекомендации остаются в силе. Все сказанное выше справедливо для рисования фигур сплошным черным или белым пером (стиль Р$_$01_Ю). Если же эластичный прямоугольник рисуется пре- рывистым пером (например, имеющим стиль Р5_00Т), то ваша задача как про- граммиста упрощается — вы может использовать любую растровую операцию: и К2_ХОКРЕМ, и К2_МОТХОКРЕМ. Также любая из этих операций может быть приме- нена в случае рисования сплошным цветным пером. Но хватит теории, пора предъявить код (листинг 4.4). Листинг 4.4. Проект Е1азйсКес1: ////////////////////////////////////////////////////////////////////// // Е1азШВесД .срр #1пс1ис1е <\л/1 псЕмз. И> #1пс1ис1е "КМпсЕИ" ЕКЕ81ЛТ САЕЕВАСК ШбРгос(НШО, ЮТ, АРАРАМ, ЕРАВАМ); //========================================== 1пР ШИАР1 ШпМаШЮЗТАМСЕ НТпзЦапсе, Н1М5ТАМСЕ КРгеуТпзТапсе, ЕР8ТВ 1рСтс1Е1пе, Ш пСтс15В|О\л/) { М86 тзд; К1л1пс1 та1 п1л1пс1 ("Е1 азТ1 с ВесТапд1е", МпзТапсе, пСггкОом, МпбРгос); \л/М1е (6еТМе88аде(&т8д, МСЕЕ, 0, 0)) { ТгапзШеМеззадеС&тзд);
Листинг 4.4 {продолжение} 01 8раЕсИМе88аде(&т8д); } геЕыгп тсд.мРагат; } //========================== О^ЕЗООТ САЕЕВАСК 1л1пс1Ргос(НШ0 Шпс1, 1ИМТ иМсд, АРАРАМ мРагат, ЕРАЕАМ 1Рагат) { ЮТ 8Ту1е; 8ТаЫ с НОС НОС; 8ТаЫс 1пТ х1, у1, х2, у2; 8ТаЫс ВООЕ ЬТгаск1пд = ЕАЕЗЕ; 8ТаЫс НВЕОЗН ИОЮВгисИ; 8ТаЫс НРЕИ ИОоТРеп, И01с1Реп; 8м1ТсИ (иМсд) { сасе Ш_СРЕАТЕ: 8Ту1е = 6еТС1а88Еопд(Шпс1, 0СЕ_ЗТУЕЕ); ЗеТС!а88Еопд(И1л1пс1, 0СЕ_ЗТУЕЕ, 8Ту1е | СЗ_0ВЕСЕКЗ); ИОоТРеп = СгеаТеРеп(Р8_00Т, 1, Р6В(0,0,0)); НОС = 6еТ0С(И1л1пс1); НОЮВгиаН = (НВЕОЗН)3е1 есТОЬ!есТ(НОС, ЗеНаЗ^оскОЬдес-Е(Н0ЕЕ0М_ВЕ03Н)); Ьгеак; сасе Ш_ЕВ0ТТ0М00Ш: ЬТгаскапд = ТРОЕ; ЗеТР0Р2(И0С, Р2_М0ТХ0РРЕЮ; х1 = х2 = Ь0ШВ0(1 Рагат); у1 = у2 = НШ0Е0(1 Рагат); И01с1Реп = (НРЕЮ8е1есТ0Ь!есТ(И0С, ИОоТРеп); РесТапд!е(НОС, х1, у1, х2, у2); Ьгеак; сасе Ш_ЕВ0ТТ0ШР: 1Г (ЬТгаскапд) { ЬТгаскапд = ЕАЕЗЕ; ЗеЕР0Р2(И0С, Р2_С0РУРЕЮ; // нарисовать окончательный прямоугольник х2 = Ь0ШВ0(1 Рагат); у2 = Н1ШР0(1 Рагат); Зе!есШэаесЕ(НОС, П01с1Реп); РесЕапд!е(НОС, х1, у1, х2, у2); } Ьгеак; сале Ш_М008ЕМ07Е: 1Г (ЬТгаскппд) { // стереть предшествующий прямоугольник РесЕапд!е(НОС, х1, у1, х2, у2); // нарисовать новый прямоугольник х2 = Ь0ШВ0(1 Рагат); у2 = Н1ШР0(1 Рагат); РесЕапд!е(НОС, х1, у1, х2, у2); }
Ьгеак; сазе Ш-ЬВЬТТОМОВЬСИ: ЬТгаск1пд = ГАЬЗЕ: х1 = ЮШВВ(1 Рагат); у1 = НШОРВ(1 Рагат); РесЬапд!е(ВВС, х1, у1, х1 + 100, у1 + 100); Ьгеак; сазе Ш_0Е8ТВ0У: 8е1есЬОЬОесЬ(ЬОС, Ь01ЬВгизЬ); Ре1 еазеОС(Шпс1, ЬОС); РозЬОьп ЬМеззаде(0); Ьгеак; ЬеТаиИ: геЬигп ОеТШпсЫРгос(Ыл1пс1, иМзд, мРагат, 1Рагат); } геЬигп 0; } ////////////////////////////////////////////////////////////////////// Особое внимание следует обратить на код обработки сообщений ММ_СКЕАТЕ, УУМ_ЕВ11ТТ0М00УУМ и домд_в1лтомир. В блоке обработки сообщения ММ_СКЕАТЕ модифицируется стиль класса окна при помощи добавления флага С5_0В1_С1_К5. Без этой модификации не будет обраба- тываться двойной щелчок мыши. Затем создается прерывистое перо Н Оо1Реп со сти- лем Р5_00Т и инициализируется контекст дисплея НОС, с которым будет идти вся дальнейшая работа. В контекст устройства выбирается пустая кисть (ноиоуу.вкизн), чтобы рисуемые прямоугольники были прозрачными. При обработке сообщения ММ_1_В11ТТОМООММ программа устанавливает растро- вую операцию К2_МОТХОКРЕМ и выбирает в контекст устройства прерывистое перо НОсйРеп. Дальнейшее рисование в блоке обработки сообщения ММ_МО115ЕМО\/Е бу- дет идти этим пером с установленным режимом рисования. При обработке сообщения ММ_1_В11ТТО1\111Р программа возвращает растровую операцию по умолчанию К2_СОРУРЕМ и рисует окончательный вид прямоугольни- ка пером по умолчанию (НОШРеп). Для упрощения кода опять была пропущена обработка сообщения ММ_РА1МТ, и поэтому так же, как и в программе 5слЬЫе, нарисованные фигуры исчезают при изменении размеров окна. Но подход к решению этой проблемы был рассмотрен в предыдущей программе 5слЬЫеАскапсес1, поэтому здесь можно применить ана- логичное решение. Улучшенное приложение для просмотра текстовых файлов Во второй главе при изучении полос прокрутки и вывода текста было создано приложение Тех1:\/1е\л/ег (см. листинг 2.2), предназначенное для чтения и вывода на экран текстового файла. В том варианте приложения сообщения мыши не учиты- вались, но при работе с полосами прокрутки АУтскпуз берет на себя первичную обработку этих сообщений. В результате этой обработки АУтскпуз направляет окну
сообщения с префиксом 5В_. Именно эти сообщения и обрабатывала оконная про- цедура приложения. Теперь, вооруженные новыми знаниями, мы хотели бы усовершенствовать приложение Тех!Л/1 е\л/ег, добавив в него обработку колесика мыши, а также возмож- ность для пользователя общаться с программой через клавиатурный интерфейс. Второе требование означает, что все операции, выполняемые мышью, должны быть продублированы на клавиатуре. Есть два пути решения второй задачи. Обрабатывая сообщение ММ_КЕУООММ, можно было бы просто продублировать полностью код, отвечающий за обработку сообщений ММ_\/$СВО1_1_ и ММ_Н$СВО1_1_. Но это некрасиво и непрактично с точки зрения дальнейшего сопровождения программы. Второй подход заключается в том, что при появлении аппаратного сообщение клавиатуры, которое дублирует действие мыши, следует вызывать тот код, который уже был написан для обра- ботки сообщений, начинающихся префиксом 5В_. Фактически, это означает, что оконная процедура должна послать сама себе сообщение 5В_. АУш32 АР1 имеет несколько функций для отправки сообщений. В данной си- туации лучше использовать функцию 5епс1Ме$$аде, имеющую следующий про- тотип: СКЕ81ЛТ 8епбМеззаде(НШ0 Шпб, 1Л1Х1Т Мзд, ПРАВАМ мРагат, КРАКАМ 1 Рагат); Параметры функции имеют тот же смысл, что и параметры, передаваемые в оконную процедуру. Когда вызывается функция 5епс1Ме$$аде, АУтйомга вызыва- ет оконную процедуру с дескриптором окна Н\Л/пс1, передавая ей эти четыре пара- метра. После того как оконная процедура заканчивает обработку сообщения, \Упк1о\\ъ передает управление следующей за вызовом 5епс1Ме$$аде инструкции. В листинге 4.5 приводится текст усовершенствованного приложения для про- смотра текстовых файлов1. Листинг 4.5. Проект ТехВ/1емегАс1у ////////////////////////////////////////////////////////////////////// // ТехШемегАсК.срр #1пс1ибе <\л/1 пбомз. И> #1пс1ибе <хтоизе.Е> // для обработки колесика мыши #1пс1ыбе "КМпб.Е" #1пс1ыбе "КОосытепб.б" #беГ1пе ЕНЕ-МАМЕ "0:\\Ргодгат Г11ез\\М1 сгозоГЕ \/1зиа1 8биб1о\\7С98\\МЕС\\8ВС\\ВЕА0МЕ.ТХТ" ЕКЕ86ЕТ САЕЕВАСК ШбРгос(НШ0, Е1МТ, ПРАВАМ, ЕРАВАМ); КОосытепб бос; //========================================== Ш ШМАР1 ШпМа1п(Н1М8ТАМСЕ Нпзбапсе, Н1М8ТАМСЕ ЕРгеу1пзбапсе, ЕР8ТВ 1рСтс1Е1пе, 1гтЕ пСтбЗЕом) { М86 тзд; 1 Не забудьте добавить к проекту файлы КУУпд.К, КМпсЕсрр, КОоситепЕ.Ь, КОоситегй.срр. Тексты после- дних двух файлов были приведены в листинге 2.2.
1Р (!бос.Ореп(ЕIЕЕ_МАМЕ)) геРигп 0; К1л1пс1 ша1 п!л1пб ("ТехР \Ле\л/ег АскапсесГ, ЫпзРапсе, пСтбЗИом, МпбРгос, ШЕЕ, СЕМЕЗЕОЕЕАиЕТ, 0, СЕМЕЗЕОЕЕАиЕТ, 0, С8_НВЕ0ВА1л1 | СЗ_\/ВЕ0ВА1л1, 1л13_0\/ЕВЕАРРЕ0ШМ001л1 | 1л/3_73СЕ0ЕЕ | 1л/3_НЗСК0ЕЕ); мИ11е (СеРМеззадеС&тзд, ШЕЕ, 0, 0)) { ТгапзТаРеМеззадеС&тзд); ОтзраРсИМеззадеС&тзд); } геРигп тзд.мРагат; } ////////////////////////////////////////////////////////////////////// ЕЕЕЗЕЕТ САЕЕВАСК ШбРгос(НШО ИШб, Е1МТ иМзд, ПРАВАМ мРагат, ЕРАЕАМ 1Рагат) { НОС НОС; РА1МТЗТЕ0СТ рз; ТЕХТМЕТЕ1С Рт; 1 пр схС11епР=0, суС11епР=0; зРаР1с 1 пр х!пс, у1пс; зИогР зРаРиз; з\л/1РсН (иМзд) { сазе Ш_СВЕАТЕ: НОС = СеРОС(И1л1пб); 6еРТехРМеРг1сз(Н0С, &Рт); бос. 1тР1 а! 1 хе(&Рт); Ве1еазеОС(Н1л1пс1, НОС); Ьгеак; сазе Ш_812Е: НОС = СеРОС (ИШб); схСИепР = Е01лЮВ0(1 Рагат); суСИепР = Н11лЮВ0(1 Рагат); 1Р (схСИепР > 0) с1ос.3сго118еРР1пдз(Н1л1пс1, схСИепР, суСИепР); Ве1еазеОС(Н1л1пс1, НОС); Ьгеак; сазе Ш_78СВ0ЕЬ: з\л/1РсН(Е01лЮВ0(\л/Рагат)) { сазе 8В-ЫМЕ0Р: у1пс = -1; Ьгеак; сазе 8В-ЫМЕ00Ш: у1пс = 1; Ьгеак; сазе ЗВ-РАЗЕОР: у1пс = -(1пР)Ьос.уз1.пРаде; Ьгеак; сазе ЗВ_РА6Е001л1М: у1пс = (1пР)с1ос. уз1 .пРаде; Ьгеак; сазе ЗВ-ТНОМВТЕАСК: у1пс = НШОВОЫРагат) - бос.уз1 .пРоз; Ьгеак; сазе ЗВ—ТОР: у1пс = -6ос.уз1.пРоз; Ьгеак; сазе ЗВ-В0ТТ0М: продолжение
Листинг 4.5 {продолжение} у!пс = бос.Уз1.пМах - бос.уз1.пРоз; Ьгеак; беГаыИ: у!пс = 0; } бос.0рбаЬе\/зсго11 (Шпб, у1пс); Ьгеак; сазе 1л/М_НЗСК0ЕЕ: зтЬсб(Ь0ШР0(мРагат)) { сазе 8В_ЫМЕЕЕЕТ: х1пс = -1; Ьгеак; сазе 8В-ЫМЕР10НТ: х1пс = 1; Ьгеак; сазе ЗВ-РАСЕЕЕГТ: х1пс = -0.8 * (1пЬ)бос.Ьз1.пРаде; Ьгеак; сазе 8В_РА0ЕРЮНТ: х1пс = 0.8 * (1пЬ)бос.Ьз1.пРаде; Ьгеак; сазе ЗВ_ТН0МВТРАСК: х1пс = НШОРВЫРагат) - бос.Ьз1 .пРоз; Ьгеак; сазе ЗВ—ТОР: х1пс = -бос.Ьз1.пРоз; Ьгеак; сазе ЗВ-В0ТТ0М: х1пс = бос.Из1.пМах - бос.Ьз1.пРоз; Ьгеак; беГаиИ: х1пс = 0; } бос.брбаЬеНзсго! 1 (Шпб, х1пс); Ьгеак; сазе Ш-МОЬЗЕШЕЕЬ: 1Г (ЕОШРВЫРагат) & МК_8Н1ЕТ) { х1пс = -3 * (зЬогЬ)НШОРВЫРагат) / ШЕЕЕ_ВЕЕТА; бос.брбаЬеНзсго! 1 (Шпб, х1пс); } е!зе { у1пс = -3 * (зЬогЬ)НШОРВЫРагат) / ШЕЕЕ_ВЕЕТА; бос.ОрбаШзсго! 1 (Шпб, у1пс); } Ьгеак; сазе Ш_КЕУВ0Ш: змПсЬ (мРагат) { сазе 7К_0Р: Ьгеак; ЗепбМеззаде(Шпб, Ш_\/8СВ0ЕЕ, 8В-ЫМЕ0Р, 0) сазе 7К_В0Ш: Ьгеак; 8епбМеззаде(Шпб, 1л/М_\/ЗСК0ЕЕ, ЗВ-ЫИЕВОШ, 0); сазе 7К_ЬЕЕТ: Ьгеак; ЗепбМеззаде (Шпб, 1л/М_НЗСК0ЕЕ, ЗВ-ЫМЕЬЕЕТ, 0); сазе 7К_РЮНТ: Ьгеак; ЗепбМеззаде (Шпб, 1л/М_НЗСК0ЕЕ, ЗВ-ЫИЕКЮНТ, 0) сазе 7К_РВЮР: Ьгеак; 8епбМеззаде(Шпб, 1л/М_\/ЗСК0ЕЕ, ЗВ-РАСЕОР, 0) сазе 7К_МЕХТ: Ьгеак; сазе 7К НОМЕ: ЗепбМеззаде (Ыл1пб, 1л/М_\/ЗСК0ЕЕ, ЗВ_РА0ЕВ01л1М, 0); зЬаЬиз = СМКеу8ЬаЬе(\/К_С0МТВ0Е);
1Т (0x80 & $Ш$) Зепс1Ме88аде(Шпс1, 1л/М_\/5СК01_1_, 5В_ТОР, 0); е18е Зепс1Ме88аде(Шпс1, Ш_Н8СТ0И, ЗВ_Т0Р, 0); Ьгеак; саее 7К_ЕМ0: 8ЬаШ = ОеЬКеуЗЬаШ 7К_С01\1ТР01_); 1Т (0x80 & 8ЬаШ) 8епЬМе88аде(Шг1с1, Ш_78СК0Ы, ЗВ_В0ТТ0М, 0); е18е Зепс1Ме88аде(Шпс1, Ш_Н8СТ0Ы, ЗВ_В0ТТ0М, 0); Ьгеак; } Ьгеак; саее Ш_РА1МТ: ВВС = Вед1 пРаапЬ(Шпс1, &р8); бос.РиЬТехЬ(Шпс1, НОС); Епс1Ра1 пЬ (Шпс1, &р8); Ьгеак; саее Ш_0Е8ТР0У: Ро8ЩпЬМе88аде(0); Ьгеак; с1еТаи!Ь: геЬигп (ОеТ1лНпЬомРгос(Шпс1, иМед, мРагаш, 1 Рагат)); } геЬигп 0; } ////////////////////////////////////////////////////////////////////// В этом приложении добавлены обработка сообщения ММ_МО115ЕМНЕЕ1_ и обра- ботка сообщения ММ_КЕУООММ. Обратите особое внимание на преобразование к типу $НоП: выражения ($НоП:) НШСО(\л/Рагат) в блоке обработки сообщения ММ_МО115ЕМНЕЕ1_. Если этого не сде- лать, вы будете поражены неприятным поведением программы. При обработке сообщения ММ_МО115ЕМНЕЕ1_ учитывается состояние клавиши 5Н1Г1:. Если эта клавиша не нажата, то прокрутка колесика мыши управляет верти- кальным скроллингом, если нажата — горизонтальным скроллингом. Сравните работу первоначального варианта приложения для просмотра тек- стовых файлов Тех^’емег и новой программы ТехЕХЛ’ехл/етАду.
5 Ресурсы УУтдоАл/з-приложения Ресурсы являются составной частью приложений для \У1пс1о\\ъ. В них определя- ются такие объекты, как пиктограммы, курсоры, растровые образы, таблицы строк, меню, диалоговые окна и многие другие. Для некоторых видов ресурсов система содержит предопределенные (стандарт- ные) объекты. Например, в листинге 1.1 уже использовались стандартная пиктог- рамма и стандартный курсор, для чего задавались соответствующие значения тем или иным полям класса окна. Все нестандартные ресурсы должны быть определены в файле описания ресур- сов (гезоигсе зспрй), который является А8СП-файлом с расширением .гс. Хотя тео- ретически такой файл можно подготовить в обычном текстовом редакторе, по- добная технология используется крайне редко, поскольку любая интегрированная среда содержит удобные редакторы ресурсов, максимально упрощающие и автоматизирующие этот процесс. Когда программист начинает работу над новым проектом, обычно в нем нет никаких ресурсов. При попытке создать новый ресурс или импортировать су- ществующий ресурс интегрированная среда М8 Ухзпа! 81пс11о 6.0 вызывает со- ответствующий редактор ресурсов. После создания ресурса его нужно сохра- нить в составе проекта при помощи команды меню Ейе ► 5ауе. Среда разработки Ухзпа! 81пс1ю предлагает для файла описания ресурсов имя по умолчанию зспрН.гс. Традиционно программисты предпочитают давать файлу описания ресурсов то же имя, которое используется и для самой программы (например, МуАрр.гс). Файл описания ресурса транслируется компилятором ресурсов (файл гс.ехе в составе интегрированной среды). В результате образуется бинарный файл с рас- ширением .ге$. Затем компоновщик включает полученный файл в выполняемый файл программы вместе с обычным кодом и данными программы из файлов с рас- ширениями .оЬ] и .11’Ь. При работе в среде Ухзпа! 81псйо после выполнения коман- ды ВиПс! все эти шаги производятся автоматически. При загрузке в память исполняемого кода программы АУшбом^ обычно оставля- ет ресурсы на диске. Потом они загружаются в память только по мере необходи- мости.
Редакторы ресурсов Редакторы ресурсов содержат инструменты и интерфейсы для быстрого и удоб- ного обслуживания ресурсов приложения. В составе М8 Ухзпа! 81псйо 6.0 имеется следующий набор редакторов ресурсов: □ графический редактор, который помогает создавать пиктограммы, курсоры, ра- стровые образы; □ редактор меню; □ редактор таблиц быстрых клавиш; □ редактор диалоговых окон; □ редактор таблиц строк; □ редактор панелей инструментов; □ редактор информации о версии; □ редактор ресурсов, определяемых программистом. Работа с конкретными редакторами описывается в процессе изучения тех ре- сурсов, которые они обслуживают. В этой главе рассматриваются пиктограммы, курсоры, растровые образы, таб- лицы строк и ресурсы, определяемые программистом1. Кроме этого приводятся функции для воспроизведения звуковых файлов. В главах 6, 7 и 8 рассматрива- ются ресурсы меню, окна диалога и панели инструментов соответственно. Пиктограммы Пиктограммы — это небольшие растровые изображения, применяемые АУтйомга для визуального представления приложений, файлов и каталогов. Пиктограмма, включенная в состав приложения, выводится на экран в левом верхнем углу стро- ки заголовка окна приложения. Кроме того, выводит на экран значок пиктограммы в списке программ меню 5^а11 (Пуск), на панели задач, когда окно приложения свернуто, и в списке файлов, отображаемых программой АУшбом^ Ехр1огег в открытой папке. Также пиктограммы отображаются на рабочем столе, предоставляя доступ к системным папкам и к ярлыкам исполняемых файлов. Ранние версии АУтсЕпуз могли отображать пиктограммы размером только 32 х 32 пиксела. Начиная с АУтсЬм^з 95, пиктограммы могут иметь один из трех типовых размеров: 16x16 пикселов для малых пиктограмм, 32 х 32 пикселов для стандартных пиктограмм и 48 х 48 пикселов для больших пиктограмм. Обычно малые и стандартные пиктограммы являются 16-цветными, а большие пикто- граммы могут использовать 256 цветов. Маленький значок используется в заголов- ке приложений, на панели задач и в списках программ меню 51аП:. Пиктограммы, 1 В литературе и документации (М8Б1У) принят термин «ресурсы, определяемые пользователем». Но он не очень удачен, так как в качестве пользователя здесь подразумевается именно программист. Это при- водит к путанице, когда в контексте упоминается настоящий пользователь — человек, эксплуатирую- щий вашу программу. Поэтому мы будем применять термин «ресурсы, определяемые программистом».
отображающиеся на рабочем столе, имеют стандартный размер. Для окон папок, открываемых \У1пс1о\\ъ Ехр1огег, пользователь может настроить режим показа маленьких или стандартных значков. Большие значки (48 х 48) могут появиться вместо стандартных значков, если только изменить настройку для размера пиктограмм в системном реестре АУшсЕпуз. Пользователь может сделать это при помощи диалогового окна, которое отобра- жается после выполнения команды Панель управления ► Экран ► Оформление. В диа- логовом окне Свойства: Экран следует выбрать тип элемента Значок и установить нужную величину в окне Размер. Графический редактор Ухзпа! 81исйо, используемый в режиме создания пик- тограммы, формирует файл с расширением лсо, содержащий один или несколько значков из следующего списка: □ Стандартный формат 32 х 32, 16 цветов (вариант по умолчанию). □ Стандартный формат 32 х 32, 256 цветов. □ Большой формат 48 х 48, 256 цветов. □ Монохромный формат 32 х 32, черно-белая пиктограмма. □ Маленький формат 16 х 16, 16 цветов. Создавая приложение, разработчик может включить в него только стандарт- ную пиктограмму. В этом случае АУшсЕпуз сформирует маленький значок из стан- дартного, просто удалив каждый второй столбец и каждую вторую строку. Но иног- да качество такого значка получается неудовлетворительным. В таких случаях стоит добавить изображение малой пиктограммы и соответствующий код для ее загрузки. Рассмотрим процесс проектирования конкретного приложения, в котором ис- пользуется ресурс пиктограммы. Потом в это приложение будут включены ре- сурсы специализированных курсоров. На следующем этапе к приложению будет добавлен ресурс битового образа с изображением государственного флага Рос- сии. В последней версии программы мы заставим ее проигрывать звуковой файл с государственным гимном России. Итак, приступим... Создайте новый проект типа \Л/1п32 АррПсаБоп с именем Киззта. Добавьте к нему новый файл с именем Кизз1’а.срр, а затем введите в файл код, приведенный в листинге 5.1. Листинг 5.1. Проект Киз81а (начальная редакция) ////////////////////////////////////////////////////////////////////// // Кы881а.срр #1пс1ис1е <\л/1 пс1о\л/8. И> #1пс1ис1е "К1л1пс1.И" СкЕ81Л_Т САИВАСК ШбРгос(ШШ, 1ЛМТ, ПРАВАМ, КРАКАМ); //============================================ 1пР ШИАР1 ШпМа1п(Н1ШТАШЕ НпаШсе, Н1МЗТАМСЕ ВРгеуШ^апсе, ЕРЗТЕ 1рСтсИ1пе, 1гД пСтсЕИом) { М86 тад; Шпс1 та1 Шпс1 ("Вы881 а ^обау", И1п8^апсе, пСтсЕИом, МпбРгос, ШЕЕ, О, О, 400, 300); мМЗе (6ИМе88аде(&т8д, ШЕЕ, 0, 0)) { ТгапзШеМеззадеС&тзд);
01 зраШМеззадеС&шзд); } геРигп пъд.мРагат; } //=============================== 0КЕ300Т САЫВАСК 1л1пс1Ргос(ШМВ Шпс1, ЩИТ иМед, АРАРАМ мРагат, ЕРАЕАМ 1Рагат) { 8\л/1РсИ (иМед) { саее Ш_0Е8Т00У: Ро8Р0и1РМе88аде(0); Ьгеак; с1еРаи1Р: геРигп ОеРШпсЫРгос(Шпс1, иМед, мРагат, 1Рагат); } геРигп 0; } ////////////////////////////////////////////////////////////////////// Скопируйте в папку проекта Ки881’а файлы КМпсЬН и К\Л/пс1.срр, которые исполь- зовались в проекте Не11о2 (листинг 1.2), и добавьте их в состав проекта Киззта. Пос- ле этого рабочий стол Ухзпа! ЗРпсйо будет выглядеть примерно так, как показано на рис. 5.1. Рис. 5.1. Рабочий стол Х/1зиа1 81ис1ю после создания проекта ки551а Обратите внимание на то, что окно Могкзрасе содержит только две вкладки: С1а88\/1’е\л/ и ЕНеХйем. В настоящий момент открыта вкладка Н 1е\йе\л/ с древовидным списком файлов, включенных в состав проекта.
Создание пиктограммы с помощью графического редактора Общий сценарий добавления к проекту новой пиктограммы включает следую- щие шаги: 1. Вызов графического редактора. В главном меню Ухзпа! 81псйо выполните ко- манду ТпзеП: ► Кезоигсе. Это приведет к отображению диалогового окна ТпзеП: Кезоигсе (рис. 5.2). Рис. 5.2. Диалоговое окно ТпзеЦ кезоигсе В окне нужно выбрать тип ресурса 1соп и нажать кнопку Мем. Появится окно графического редактора 5спр1:1 — Ю1_1С0М1 с заготовкой стандартной пикто- граммы (рис. 5.3). Рис. 5.3. Рабочий стол Х/1зиа1 5Н1с1ю после вызова графического редактора
Обратите внимание на две панели инструментов в правой части рабочего стола. Панель бгарЫсз содержит инструменты рисования, а панель Со1огз — палитру доступных цветов. Если по каким-либо причинам эти панели не будут видны, нужно щелкнуть правой кнопкой мыши на их месте и в появившемся контек- стном меню включить соответствующие флажки бгарЫсз и Со1огз. 2. Выбор формата пиктограммы. Комбинированный список 0еУ1’се в окне гра- фического редактора поначалу содержит только один формат 51апс1агс1 (32x32). Но справа от этого списка располагается кнопка Мем Оеутсе 1таде. Если ее на- жать, то появится окно Мем 1соп 1таде со списком ТагдеЕ деутсе (рис. 5.4). Рис. 5.4. Диалоговое окно Ыем 1соп 1таде Вы можете выбрать из списка любой дополнительный формат и, нажав кноп- ку ОК, добавить его в список Оеутсе. Для программы Ки$$1’а.срр нужно создать стандартную и малую пиктограммы. Поэтому выберите в предлагаемом списке строку 5та11(16х16) и нажмите кноп- ку ОК. Окно графического редактора переключится на заготовку малой пик- тограммы. 3. Назначение ресурсу идентификатора. Обратите внимание на заголовок в окне графического редактора: 5спрИ — Ю1_1С0М1 (1соп). Первая часть заголовка со- держит имя файла описания ресурсов. По умолчанию редактор предлагает имя ЗспрИ, если создаваемый ресурс является первым в данном проекте. Если же некий ресурс уже добавлялся ранее, то на этом месте отображается то имя, под которым был сохранен файл описания ресурсов (см. и. 5). Вторая часть заголовка содержит идентификатор создаваемого ресурса. По умол- чанию редактор предлагает имя Ю1_1С0М1. Если вы будете добавлять в проект другие пиктограммы, то редактор предложит идентификаторы по умолчанию Ю1_1С0М2, Ю1_1С0МЗ и т. д. Для долгосрочного сопровождения проекта такая си- стема наименований — не самое лучшее решение. Поэтому рекомендуется при- сваивать ресурсам идентификаторы, отражающие их семантику. В создаваемой программе пиктограмма будет изображать трехцветный российский флаг, по- этому более подходящим оказался бы идентификатор Ю1_ТК1СО1_О11К. Для изменения идентификатора нужно дважды щелкнуть левой кнопкой мыши на пустом месте в окне редактирования. На экране появится окно 1соп РгорегВез (рис. 5.5). На вкладке 6епега1 располагается текстовое поле Ю для ввода идентификатора и текстовое поле Ейе пате с именем файла, в котором будет сохранено изобра- жение пиктограммы. Введем для нашей программы идентификатор ресурса Ю1_ТК1СО1_О11К и изменим имя файла на ЫсоЫсо.
-Й Бепега! Ра1е11е | Ю: |Ю1_1С0Ы1 2] МИ: 16 Не!дЫ: 16 Со1огз: 16 Е|1е пате: ||соп1.юо Бе(аи11 арр||саНоп юоп Рис. 5.5. Окно 1соп Ргорегйез Прежде чем закрыть это окно, переключитесь на вкладку Ра1ейе. Здесь вы уви- дите 16 доступных цветов для изображения пиктограммы. Это те же самые цвета, которые содержит панель инструментов Со1огз. Но если вы будете рабо- тать с 256-цветным форматом, то палитра будет содержать 256 цветов, и выби- рать текущий цвет для рисования можно будет только здесь. Закройте окно 1соп РгореПп’ез, нажав кнопку с крестиком в полосе заголовка. После этого заголовок окна графического редактора изменится на 5спр1:1 — ют.тктсоюик. 4. Рисование. Сейчас в окне графического редактора находится заготовка малой пиктограммы (16 х 16). Так же, как на рис. 5.3, она представляет собой квадрат, все пикселы которого окрашены в темно-бирюзовый (багк-суап) цвет. На са- мом деле пикселы этого цвета интерпретируются графическим редактором как прозрачные1. Пользуясь инструментами рисования на панели СгарГпсз, создайте нужный рисунок. Текущий цвет выбирается на панели Со1ог$. Процедура рисования очень похожа на создание рисунка в графическом редакторе М8 Раш1, поэто- му не будем подробно ее описывать. Результат рисования малой пиктограммы показан на рис. 5.6. Рис. 5.6. Окно графического редактора с изображением малой пиктограммы Черно-белый рисунок не передает всей информации. На самом деле верхняя часть изображения содержит белый цвет, средняя часть — голубой цвет и нижняя — красный цвет. Обрамляет пиктограмму рамка белого цвета толщиной 1 пиксел2. 1 Вы можете проверить это, оставив на каком-нибудь изображении часть пикселов заготовки в ис- ходном состоянии. 2 Если вы загрузите исходный код проекта Ки$$1‘а из файлов к книге, доступных на сайте издательства «Питер» (уууууу.рйег.сот), то сможете увидеть все ресурсы-изображения в их истинном цвете.
Теперь создайте изображение стандартной пиктограммы (32x32), переключив- шись на ее заготовку при помощи списка Оеутсе. Размеры стандартной пикто- граммы позволяют сделать ее более информативной. На средней части флага надо нарисовать текстовую надпись «Кпзыа», а рамку значка выделить светло- серым цветом (рис. 5.7). Рис. 5.7. Окно графического редактора с изображением стандартной пиктограммы Если вам понадобится создать рисунок, использующий 256-цветную палит- ру, то цвет нужно будет выбирать на вкладке Ра1еИе в окне 1соп РгорегВ’ез (см. рис. 5.5). 5. Сохранение созданного ресурса в файле описания ресурсов. В главном меню Ухзпа! 81пс1ю выполните команду ЕПе ► Зауе. Если создаваемый ресурс — не первый и файл описания ресурсов уже суще- ствует, то описание созданного ресурса будет просто добавлено в существую- щий файл. Если создаваемый ресурс является первым в проекте, то появится окно диалога Зауе Аз (Сохранить как). По умолчанию в окне ЕПе Мате (Имя файла) предлагает- ся имя ЗспрН.гс. Замените часть имени ЗспрН на имя приложения, в данном слу- чае на имя Кизз1’а, и нажмите кнопку Зауе (Сохранить). Теперь в заголовке окна графического редактора будет отображаться строка Кизз1’а.гс — Ю1_ТК1СО1_О11К. Теперь необходимо добавить файл описания ресурсов к проекту. Для этого в окне Могкзрасе щелкните правой кнопкой мыши на папке Кезоигсе Ейез и в открывшемся контекстном меню выберите пункт Ас1с1 ЕНез 1:о ЕоШег. В открыв- шемся окне диалога ТпзеП: Ейез нужно выбрать необходимый файл. В нашем примере к проекту добавляется файл Кизз1’а.гс, и после этого рабочий стол Ухзпа! 81пс1ю будет иметь вид, показанный на рис. 5.8. Обратите внимание на изменения, произошедшие на вкладке ЕНе\/1еуу. Папка Кезоигсе Ейез теперь содержит два файла: файл описания ресурсов Киззта.гс и графический файл ЫсоИсо. Кроме того, в окне Могкзрасе появилась новая вкладка Резо и гее VI ем. Если переключиться на нее, то можно увидеть список ре- сурсов проекта, содержащий пока только папку 1соп с единственным ресурсом ют.тктсоюик.
Рис. 5.8. Рабочий стол У15иа1 51ис1ю после добавления к проекту файла описания ресурсов 6. Добавление к проекту заголовочного файла гезоигсе.Ь (только для первого создаваемого ресурса). Наряду с файлом описания ресурсов, редактор ресур- сов создает еще и заголовочный файл гезоигсе.Н, содержащий определения ис- пользуемых именованных констант. Если создаваемый ресурс — первый, то кроме добавления к проекту файла описания ресурсов надо также добавить и указанный заголовочный файл. Для этого в окне Могкзрасе щелкните правой кнопкой мыши на папке Неадег Ейез и в открывшемся контекстном меню вы- полните команду Ас1с1 Е1’1е$ 10 Ео1с1ег. Далее в открывшемся окне диалога Тпзег! ЕПез следует выбрать файл гезоигсе.Н. ПРИМЕЧАНИЕ------------------------------------------------------------------ Пока заголовочный файл гезоигсе.Н используется только в одном проекте, нас устраивает его имя, создаваемое редактором ресурсов. Если же проект предназначен для создания динамически заг- ружаемой библиотеки (ОН), экспортирующей ресурсы в другие проекты, то следует побеспоко- иться об уникальности имени ее заголовочного файла. Более подробно этот вопрос рассматривается в главе 11. На этом завершается рассмотрение процедуры создания и включения в про- ект новых пиктограмм. В разделе «Использование ресурса в приложении» будет показано, что нужно добавить в код программы, чтобы эти пиктограммы стали значками приложения. Импорт существующей пиктограммы Изложенный выше сценарий описывает создание новой пиктограммы. Но графи- ческий редактор позволяет добавлять к программе уже существующую пикто- грамму, позаимствованную из другого приложения.
Предположим, что вы хотите использовать в своей программе пиктограмму, изображение которой находится в файле АпуТсоплсо. Для начала требуется ско- пировать файл АпуТсоплсо в папку вашего приложения. После этого выполните следующую последовательность действий: □ В главном меню Ухзпа! 81псйо выполните команду ТпзеП: ► Кезоигсе. В появив- шемся диалоговом окне ТпзеП: Кезоигсе выберите тип ресурса 1соп и нажмите кнопку ТтроП:. □ В появившемся диалоговом окне ТтроП: Кезоигсе убедитесь, что открыта папка вашего приложения, и щелкните на имени файла АпуТсоплсо. В результате бу- дет открыто окно графического редактора с изображением импортируемой пик- тограммы. □ Назначьте идентификатор ресурсу пиктограммы в соответствии с и. 3 описан- ного выше сценария. □ Сохраните ресурс в проекте и выполните действия по модификации состава проекта, как это показано в пн. 5 и 6 описанного выше сценария. Изложенная процедура описывает использование графического редактора ъ режиме импорта. Аналогично могут импортироваться и другие графические ре- сурсы, например, курсоры и растровые изображения. Просмотр и редактирование ресурсов приложения Ресурсы приложения можно просматривать в двух разных режимах, вызывая со- ответствующий графический редактор или открывая файл описания ресурсов в текстовом режиме. Просмотр ресурса с вызовом соответствующего редактора В окне Могкзрасе перейдите на вкладку Ке$оигсе\йем и сделайте двойной щелчок мышью на идентификаторе интересующего вас ресурса. При этом будет открыт соответствующий редактор ресурса, и в его окне появится изображение, связан- ное с этим ресурсом, как, например, показано на рис. 5.8. После этого ресурс мож- но редактировать при помощи средств редактора. Если в определение ресурса были внесены изменения, то они должны быть сохранены при помощи команды меню Ейе ► 5ауе. Просмотр файла описания ресурсов в текстовом режиме Закройте все окна тех редакторов ресурсов, которые открыты в настоящий мо- мент. После этого в главном меню Ухзпа! 81псйо выполните команду ЕНе ► Ореп. В результате появится окно диалога Ореп (Открыть), внешний вид которого пока- зан на рис. 5.9. Убедитесь, что в текстовом поле Папка выбрано имя папки текущего проекта (в данном случае — Киззта). Если это не так, то следует найти искомую папку в комбинированном списке Папка. Затем откройте комбинированный список Ореп аз и выберите строку Тех!. После этого останется только щелкнуть на имени нуж- ного файла (в данном примере Кизз1’а.гс). В результате в окне редактора Ухзпа! 81пс11о появится текст файла описания ресурсов.
Рис. 5.9. Диалоговое окно Ореп Не приводя здесь полного текста файла Ки$$1’а.гс, обратим ваше внимание на следующую строку: Ю1_ТК1С01_01Ж 1СОИ 018САЕ0АВЕЕ Чп'со1.1со" в которой содержится описание ресурса созданной пиктограммы. Это так называемое однострочное описание ресурса. Оно применяется для опи- сания пиктограмм, курсоров и битовых образов. Синтаксис подобного описания выглядит следующим образом: имя _ресурса тип уаесурса 015САК0АВ1Е имя_файла Имя_ресурса интерпретируется АУтйомга либо как С-строка с завершающим нулевым символом, либо как целочисленный идентификатор. Если имя_ресурса представляет собой А8СП-изображение десятичного или шестнадцатеричного числа, то \Упк1о\\ъ воспринимает его как целочисленный идентификатор. В про- тивном случае имя_ресурса интерпретируется как С-строка, хотя формальных признаков строки (обрамляющих кавычек) у этого имени нет. Похоже, что вариант С-строки использовался в ранних версиях АУтсЕпуз, поскольку редакторы ресурсов Ухзпа! 81исйо в генерируемом ими коде всегда определяют имя ресурса в виде целочисленной константы. Так, если заглянуть в текст файла гезопгсе.й в проекте Впззха, можно найти там следующую строку: #(М1пе 1В1_ТВ1С0Е0Ш 101 То есть на самом деле после подстановки макроопределения описание пиктограм- мы будет иметь следующий вид: 101 1С0И В18САВВАВЕЕ Чг1со1.1со" Таким образом, однострочное описание ресурса, находящееся в файле Ки$$1’а.гс, связывает пиктограмму Ю1_ТК1СО1_О11К с графическим файлом ЫсоЫсо, содержа- щим два растровых изображения: стандартной и малой пиктограмм. Атрибут В18САВВАВЕЕ (выгружаемый) использовался в ранних версиях \Упк1о\\у позволяя системе освобождать занимаемую ресурсом оперативную па- мять, когда возникали проблемы со свободной памятью. В \Уш32 он фактически игнорируется.
Использование ресурса в приложении Для получения дескриптора пиктограммы, относящегося к типу Н1СО1М, вызыва- ется функция 1_оас11соп или 1_оас11таде. Беглое знакомство с функцией 1_оас11соп у нас уже состоялось в главе 1. Но здесь она будет рассмотрена более подробно. Функция 1_оас11соп загружает ресурс пик- тограммы из выполняемого файла (.ехе), ассоциированного с экземпляром при- ложения ЫпзЕапсе: Н1СОИ 1_оас11соп(Н1М5ТАМСЕ ЩпзТапсе, 1РСТЗТЕ 1р1сопМате); В качестве второго параметра, определяющего имя ресурса, функция прини- мает строку с завершающим нулевым символом. Ранее говорилось, что в файле описания ресурсов, подготовленном с помощью редактора ресурсов, имя_ресурса для пиктограммы представляет собой целочисленный идентификатор. Для реше- ния проблемы преобразования целого числа в указатель на строку ресурса необ- ходимо использовать макрос МАКЕ11МТКЕ8ОIIКСЕ (шаке ап ш1е§ег шЕо гезопгсе 81пп§), определенный в файле \ллп изег. Н следующим образом: #с1еГ1 пе МАКЕ1МТЕЕ501ЖСЕ(1) (ЕРЗТЕ) ((ОШКО) ((ШКО) (1))) Этот макрос преобразует число в указатель, но при этом старшие 16 разрядов устанавливаются в нулевое значение. Так АУтИомга узнает, что второй параметр функции ЕоасИсоп является числом, а не указателем на символьную строку. Например, для загрузки пиктограммы в программе Ки$$1’а будет использовать- ся следующий вызов: Еоас11соп(Г|1п8-Еапсе, МАКЕ1МТЕЕ30иРСЕ( 101_ТР1С0Е01Ж)); Если параметр ЫпзЕапсе имеет значение МОП, то функция ЕоасИсоп исполь- зуется для загрузки предопределенных пиктограмм. Возможные значения вто- рого аргумента для предопределенных пиктограмм приведены в табл. 1.5 (гла- ва 1). Эта возможность использовалась во всех предыдущих приложениях, так как в классе КДОпс! поле Н1соп оконного класса мс инициализируется при помощи вызова ЕоасИсоп(МЕ1ЕЕ, Ю1_АРР1_1САТЮМ). В случае успешного завершения функция ЕоасИсоп возвращает дескриптор за- груженной пиктограммы. В случае неудачи возвращается значение МОП. Если по каким-то причинам ЕоасИсоп не смогла загрузить указанную пиктограмму, то она загружает пиктограмму по умолчанию Ю1_ММ1_060. Обращаем ваше внимание на то, что функция ЕоасИсоп предназначена для за- грузки только стандартных пиктограмм. Поэтому не пытайтесь загрузить с ее по- мощью малую пиктограмму (16 х 16). Иначе вас ждет горькое разочарование. Для загрузки пиктограмм других размеров используйте функцию ЕоасИтаде. Функция ЕоасИтаде предназначена для загрузки изображений разных типов: пиктограмм, курсоров, битовых образов. Она имеет следующий прототип: НАЖЕ ЕоасПтаде(Н1М5ТАМСЕ МпзЦ, ЕРСТЗТЕ ТрзхМате, 1Л1Х1Т иТуре, 1 пТ сх0ез1гес1, 1 пТ су0ез1гес1, 1ЛИТ ТиЕоаб); Второй параметр функции определяет загружаемое изображение. Если пара- метр Н1П51: не равен И И ЕЕ и параметр ГиЕоас! не содержит флаг ЕК_ЕОАОЕКОМЕ1ЕЕ, то параметр фзгМате интерпретируется аналогично второму параметру функции ЕоасИсоп.
Если параметр Нтпз! равен М1)Ы, а параметр ГиЬоад по-прежнему не содержит флаг 1_К_ЮАОЕКОМЕ11_Е, то параметр [рзгМате специфицирует ОЕМ-изображение1. Идентификаторы ОЕМ-изображений определены в \ллпизег.Н и имеют следующие префиксы: ОВМ_ для растров, 01С_ для пиктограмм и ОСК_ для курсоров. Если параметр пзЕ равен МОП, а параметр Ги 1_оас1 содержит флаг 1_В_1_ОАОЕВОМЕ11_Е, то фзгМате задает имя файла, в котором хранится изображение загружаемого ре- сурса. Третий параметр, иТуре, определяет тип изображения и может принимать зна- чения 1МА6Е.ВПМАР, 1МА6Е-С0К50К и 1МА6Е.1С0М. Четвертый параметр, схОезп’гес!, задает ширину изображения в пикселах. Если он равен нулю и параметр Ги1_оас1 содержит флаг 1_В_0ЕЕА111-Т$12Е, то для вычисле- ния ширины изображения функция использует значения метрики $М_СХ1СОМ или 5М_СХС11К5ОК. Если же параметр равен нулю, а флаг 1_В_0ЕЕА1Л-Т$12Е не использует- ся, то функция использует фактическую ширину изображения. Пятый параметр, суОезтгед, задает высоту изображения в пикселах. Если он ра- вен нулю и параметр Ги1_оас1 содержит флаг 1_В_0ЕЕА1Л-Т$12Е, то для вычисления вы- соты изображения функция использует значения метрик 5М_СХ1СОМ или $М_СХС11 К5ОК. Если же параметр равен нулю, а флаг 1_В_0ЕЕА1ЛТ$12Е не используется, то функция использует фактическую высоту изображения. Шестой параметр, Ги1_оас1, позволяет указывать опции загрузки. Он может со- держать один или несколько флагов, указанных в табл. 5.1 (приведены наиболее часто употребляемые флаги. Полный список см. в М8Н?<). Таблица 5.1. Опции загрузки для функции коасИтаде Флаг Значение кк_ОЕРА1ЛТСОкОк Флаг по умолчанию. Означает, что изображение не монохромное кк_СкЕАТЕО1В8ЕСТЮЫ Если иТуре имеет значение 1МА6Е_В1ТМАР, то функция возвращает 016-секцию кк_ОЕЕА1ЛТ812Е Влияет на интерпретацию четвертого и пятого параметров функции кк_ЮАОЕкОМЕ1кЕ Влияет на интерпретацию второго параметра кк_МО1\ЮСНкОМЕ Изображение является черно-белым Вернемся к нашей программе Ки$$1’а. Чтобы обе загруженные пиктограммы — стандартная и малая — стали значка- ми данного приложения, их дескрипторы должны быть присвоены полям Н1соп и Н1соп5т структуры оконного класса главного окна приложения. Но так как в нашей программе главное окно создается при помощи объекта класса КМпф то эти поля уже проинициализированы в конструкторе класса К\Л/пс1 (см. текст файла К\Л/пс1.срр). Поэтому решение заключается в модификации оконного класса при помощи функции 5е1С1а88копд. Модификация оконного класса производится всег- да только в блоке обработки сообщения ММ_СКЕАТЕ, когда окно уже создано, но еще не показано на экране. Новая редакция кода Ки$$1’а.срр приведена в листинге 5.2. ОЕМ — Оп§та1 Есдпртеп!: Матй’асТигегз (производители оригинального оборудования).
Листинг 5.2. Проект Ки881а (1-я редакция) ////////////////////////////////////////////////////////////////////// // Ви881а.срр #1пс1ис1е <\л/1 пс1ом8. Ь> #1пс1ис1е "К1л/пс1.Н" #1пс1ис1е "гесоигсе.Ь" 1КЕ81ЛТ САЕЕВАСК ШЬРгос(НШО, 1ЛМТ, ПРАВАМ, ЕРАВАМ); //========================================== ппР ШИАР1 ШпМапп(НIИЗТАМСЕ ЫпсРапсе, Н1М5ТАМСЕ ЬРгеуШРапсе, ЕРЗТЕ 1рСтсИ1пе, 1пР пСтс15Но\л/) { М86 Ш8д; К1л/пс1 тап п1л/пс1 (" Ви88п а Рос1ау", ЫпсРапсе, пСтсЕИом, 1л1пс1Ргос, МЬЕЕ, О, О, 400, 300); \л/М1е (6еРМе88аде(&т8д, МЬЕЕ, 0, 0)) { Тгап81аРеМе88аде(&т8д); 018раШМе88аде(&т8д); } геРигп тсд.мРагат; } //=============================== ЕЕЕЗЕЕТ САЕЕВАСК ШЬРгос(НШО Шпс1, ЩИТ иМсд, ПРАВАМ мРагат, ЕРАВАМ 1Рагат) { Н1МЗТАМ1СЕ Ып8Р; Н1С0И Ысоп; // дескриптор стандартной пиктограммы Н1С0И ЫсопЗт; // дескриптор малой пиктограммы 8\ллРсИ (иМсд) { сасе Ш_СВЕАТЕ: ЫпсР = Зе1тМоби1 еНапсИ е(МЕ1ЕЕ); Ысоп = Еоаб1соп(ВIП8Р, МАКЕIМТЕЕЗОШСЕ(Ю1_ТВ1С0Е0ЬВ)); ЫсопЗт = (НIСОМ)Еоаб1таде(ВIП8Р, МАКЕIМТЕЕ301ЖСЕ(IВ 1_ТЕ1 С0Е0Ш), 1МА6Е_1С0М, 16, 16, Е[В_ВЕЕАиЕТСОЕОЮ; 8еРС1а88Еопд(Шпс1, 6СЕ_Н1С0М, (ЕОИЗ)К 1соп); ЗеРС! ассЕопд(Н1л/пс1, <ЗСЕ_НIСОИЗМ, (ЕОМЗ)В 1сопЗт); Ьгеак; сасе 1л1М_ВЕЗТВ0У: РосРОип РМе88аде(0); Ьгеак; с1еРаи1Р: геРигп ВеРШпЬомРгос(Шпс1, иМсд, мРагагл, 1Рагат); } геРигп 0; } ////////////////////////////////////////////////////////////////////// В блоке обработки сообщения ММ_СКЕАТЕ вызывается функция ЕоасИсоп для загрузки стандартной пиктограммы и функция ЕоасИтаде для загрузки малой пик- тограммы. Дескриптор экземпляра приложения ЫпзР, который передается функ- циям, определяется при помощи функции 6е1Мос1и1еНапс11е.
Полученные дескрипторы Ысоп и Н1соп5т передаются затем двум вызовам фун- кции 5е1:С1а$$1_опд, которая модифицирует одноименные поля (Н1соп, Н1соп5т) в структуре оконного класса ташМпсЬдос главного окна приложения. В тексте программы появилась также следующая директива: #1пс1ис1е "гезоигсе.И" без которой имя Ю1_ТК1СО1_О11К было бы неизвестно компилятору. После компиляции и запуска приложения на экран будет выведено окно про- граммы с новой пиктограммой (рис. 5.10). Рис. 5.10. Окно программы ки551а (1-я редакция) Курсоры Курсоры — это изображения размером 32 х 32 пиксела, которые отмечают поло- жение курсора (указателя) мыши. Курсоры во многом похожи на пиктограммы. Их главное отличие заключается в наличии активной точки (коброй). Активной точкой называется пиксел, который принадлежит изображению курсора и отмечает его точное положение на экране в любой момент времени. В стандарт- ном курсоре, имеющем вид стрелки, активная точка расположена в левом верх- нем углу курсора. Технологию использования в приложении курсоров, определенных програм- мистом, продемонстрируем, продолжая разработку программы Ки$$1’а. В новой редакции программа будет изменять форму курсора мыши в зависи- мости от того, в какой зоне клиентской области он находится. В основном курсор мыши будет иметь форму перекрестья, которая задается предопределенным кур- сором ЮС_СК055. Но при приближении к верхней границе клиентской области курсор будет принимать форму стрелки, направленной вверх, а при приближе- нии к нижней границе клиентской области — форму стрелки, направленной вниз. Кроме того, в окне программы будет отображаться информация о текущей пози- ции курсора, связанной с его активной точкой. Таким образом, к проекту нужно добавить два ресурса курсоров с идентификаторами ЮС_11Р и ЮС_00ММ. Процедура создания нового курсора в графическом редакторе очень похожа на рассмотренную выше процедуру создания пиктограммы. На первом шаге в диалоговом окне 1пзег1: Кезоигсе вместо типа ресурса 1соп выбирается тип Сигзог. Вызванный графический редактор будет работать в режиме создания курсора. На этапе рисования, после того как будет создано изображение курсора, необ- ходимо определить его активную точку. Например, при создании курсора ЮС_11Р окно графического редактора с созданным изображением показано на рис. 5.11.
Рис. 5.11. Результат рисования для курсора ЮС_11Р В отличие от окна графического редактора, работающего в режиме создания пиктограммы, в этом окне есть кнопка 8е1 Но±5ро±, располагающаяся на правом краю панели инструментов. Координаты активной точки отображаются слева от этой кнопки и по умолчанию равны (0, 0). Позиция активной точки указывается всегда относительно левого верхнего угла изображения. Чтобы назначить активную точку, нужно нажать кнопку 8е1 Но±5ро±, а затем щелкнуть мышью на том пикселе изображения, который должен стать активной точкой. Для нашего курсора это будет пиксел на верхнем окончании стрелки. После указанных действий надпись слева от кнопки примет вид Н 01 зро!: 15, 0. Аналогичные действия повторим и для курсора ЮС_00ММ. Но теперь активной точкой будет пиксел на нижнем конце стрелки (рис. 5.12). Рис. 5.12. Определение активной точки для курсора ЮС.ООУУМ Сохраните новые ресурсы в файле описания ресурсов, выполнив команду меню ЕНе ► 5ауе. Если теперь открыть файл Ки$$1’а.гс в текстовом режиме, то можно уви- деть две новые строчки с определениями: ЮС-СР СВВ80В В18САВВАВЕЕ "ир.сиг" ЮС 00Ш СВВ80В В18САВВАВЕЕ Юош.сиг" Завершив определение ресурсов с курсорами, займемся кодом программы. Необходимо определить дескрипторы используемых курсоров (типа НС11К50К) и загрузить ресурсы при помощи функции 1_оас1Сиг8ОГ, вызов которой аналогичен вызову функции 1_оас11соп.
В нашей программе будут использоваться три курсора: НСигзог для вывода пе- рекрестья (предопределенный курсор ЮС_СК055), НСигзогОр для вывода стрелки вверх (ЮС_11Р) и НСиг80гОо\а/п для вывода стрелки вниз (ЮС_00ММ). Курсор НСигзог должен стать основным курсором приложения. Для этого нуж- но модифицировать соответствующее поле оконного класса, вызвав следующую функцию: 8еРС1а88Еопд(Шпс1, 6СЕ_НСШ80В, (ЕСЖЖигзог); Для динамического изменения формы курсора в зависимости от его местона- хождения применяется функция Зе^Сигзог: НС1Ж80К ЗеРСисзог (НСЕ1В80В ОСигзог); Функция делает текущим курсор, который передается ей в качестве парамет- ра. При этом возвращается дескриптор предшествующего курсора. Отслеживание положения курсора обычно осуществляется при обработке со- общения ММ_МО115ЕМО\/Е. Параметр оконной процедуры 1Рагат в этот момент со- держит координаты текущей позиции курсора мыши х и у, которые размещаются в младшем и старшем словах параметра соответственно. Эта позиция определяет- ся относительно левого верхнего угла клиентской области. Вторая редакция файла Ки$$1’а.срр приведена в листинге 5.3. Листинг 5.3. Проект Ки881а (2-я редакция) ////////////////////////////////////////////////////////////////////// // Виззта.срр #1пс1ыс1е <\л/1 пс1о\л/8. И> #1пс1ыс1е <8Рс11о.И> #1пс1ыс1е "К1л/пс1.ГГ #1пс1ыс1е "гезоигсе.б" ККЕ81ЛТ САЕЕВАСК ШбРгос(НШО, С1МТ, АРАРАМ, КРАКАМ); //============================================ 1пР ШИАР1 ШпМаШНЖТАМСЕ ЩпзРапсе, Н1МЗТАМСЕ КРгеуШРапсе, ЕРЗТЕ 1рСтсИ1пе, 1пР пСтсЕИом) { М86 тзд; КЖ та1 п1л1пс1 ("Ви881 а Робау", КШРапсе, пСтсЕИом, МпбРгос, М10ЕЕ, О, О, 400, 300); \л/М1е (СеРМеззадеС&тзд, ЖЕ, 0, 0)) { Тгап81аРеМе88аде(&т8д); 01зраРсНМеззадеС&тзд); } геРигп тзд.мРапат; } //=================================================================== ЕЕЕЗЕЕТ САЕЕВАСК ШбРпос(ШИВ Шпс1, В11\1Т иМзд, ПРАВАМ мРапат, ЕРАВАМ 1Рапат) { Н1М8ТАМСЕ Е11П8Р; Н1С0И И1соп, МсопЗт; 8РаР1с НСЫВ80В КСипзоп, КСипзопир, КСигзогОош; 8РаР1с 1 пр хРо8, уРое; // координаты горячей точки курсора мыши 8РаР1с 1 пр \л/С11епР, ИСПепР; // размеры клиентской области окна НОС ВВС; РА1МТ8ТВВСТ рз; сбап РехРЕЮО]; ВЕСТ песР;
ЗеЕРесШгесЕ, 10, 0, 200, 30); 8\л/ПсН (иМзд) { сазе Ш_СРЕАТЕ: Н1п81с = 6е1аМоби’1 еНапсИ е(ШЕЕ); Ысоп = Еоаб1соп(И1п8-Е, МАКЕI ИТ^ЕЗОШСЕ (101_ТЕ1С0ЕСШ ); ЫсопЗт = (Н1СОМ)Еоаб1таде(И1п8-Е, МАКЕIМТКЕЗОЕКСЕ(101_ТК1СОЕОЕК), 1МА6Е_1С0М, 16, 16, ЕК-ОЕЕАЕЕТСОЕОЮ; Зе^СОаааЕопдСШпсЕ 6СЕ_Н1СЖ (ЬСЖ)Ысоп); Зе-ЕСО а88Еопд(Шпс!, ЗСЕ_Н1С0МЗМ, (ЕОМЗ)И 1сопЗт); ЬСигзог = ЕоабСигаог(ИЕ1ЕЕ, ЮС_СР088); ЗеЮаззЬопд(Шпс1, ЗСЕ_НСЕ1К30К, (ЬСЖЖигзог); ЬСигзогЬр = ЕоабСиг8ОГ(ИIП81., МАКЕIИТКЕЗОЕКСЕ(ЮС_ЬР)); ЬСигзогОош = ЕоасЮигзогШпзЕ, МАКЕIИТКЕЗОЕКСЕ(10С_00Ш)); Ьгеак; сазе 1л/М_317Е: \л/С11епЬ = ЕО1лЮРО(1 Рагат); ИСИепЬ = Н11лЮРО(1 Рагат); Ьгеак; сазе Ш_М0Ь8ЕМ07Е: хРоз = ЬСИлЮШ 1 Рагат); уРоз = НШ0Р0(1 Рагат); 1Т (уРоз < 16) 8еЕСигзог(ЬСигзогЬр); 1Т (уРоз > ИСИепЬ - 16) 8еЕСигзог(ЬСигзог0ош); 1пуа11 (ШеРесШ1л1пс1, йгесЕ, ТРОЕ); Ьгеак; сазе 1л/М_РА1МТ: ЬОС = Вед1 пРа1 пЬ(Шпс1, &рз); зргШШехЬ, "хРоз = Жс1, уРоз = Жс1\0", хРоз, уРоз); ОгамТехШОС, ЕехЕ, -1, йгесЕ, ОТ_ЬЕЕТ); Епс1Ра1пЕ(Шпс1, &рз); Ьгеак; сазе Ш_0Е8ТР0У: РозЕОиПМеззаде(О); Ьгеак; ЬеСаиИ: геЕигп ОеСШпсЫРгос(Шпс1, иМзд, мРагат, 1Рагат); } геЕигп 0; } ////////////////////////////////////////////////////////////////////// Ширина АЛ/СНеп! и высота НСИепЕ клиентской области окна определяются прило- жением при обработке сообщения ММ_$12Е. Величина НСИепЕ используется затем в блоке обработки сообщения ММ_МО115ЕМО\/Е, чтобы определить область в нижней части окна, где курсор должен принять форму стрелки, указывающей вниз. Вид окна работающей программы, когда курсор находится у верхнего края клиентской области, показан на рис. 5.13.
Рис. 5.13. Программа ки551а с курсорами, определенными программистом Еще один момент, на который хотелось бы обратить ваше внимание, — это вы- зов функции 1пуа11’с1а1:еКес1: в конце блока обработки сообщения ММ_М0115ЕМ0\/Е: 1пуа11(ШеКесШ1л1пс1, &гесД, ТРОЕ); Этот вызов необходим, чтобы сделать недействительной, то есть требующей обновления, часть клиентской области. В результате указанного вызова система сгенерирует сообщение ММ_РА1МТ. Обрабатывая это сообщение, приложение вы- зывает функцию Отахл/ТехЕ для отображения текста. Чтобы исключить наложение нового текста на текст, оставшийся от предыду- щего акта рисования, нужно стереть предыдущее изображение, прежде чем выво- дить новое. Система АУхпбом^з сделает это самостоятельно, выполняя вызов функ- ции Вед1 пРашС но только при одном условии. «Виновница» генерации сообщения ММ_РА1МТ — функция 1пуа11’с1а1:еКес1: — должна быть вызвана с третьим парамет- ром, равным ТКО Е. Также особое внимание следует обратить на второй аргумент в вызове функ- ции 1пуа11’с1а1:еКесЕ Он указывает, что обновляемым регионом будет только неболь- шой прямоугольник, в котором размещается текст. Если бы использовалось зна- чение МОП, то перерисовке подверглась бы вся клиентская область. Вы не заметите разницы, пока большая часть окна занята фоном и в окне не отображаются другие растровые изображения. Во всех других случаях перерисовка всей клиентской области вызывает неприятное мерцание. Растровые образы В главе 3 уже достаточно подробно рассматривалось использование в Ш1пс1о\\ъ-нро- граммировании растровых битовых образов или точечных рисунков. Там же отмеча- лось, что ВВВ-растры могут загружаться из ресурсов приложения. Фактически, пик- тограммы и курсоры тоже являются особыми видами точечных рисунков. Растровые образы чаще всего используются для решения двух классов задач. Прежде всего, с их помощью производится отображение на экране картинок. На- пример, файлы драйверов дисплеев в АУтсЕпуз содержат множество небольших битовых образов, которые используются для рисования стрелок в полосах про- крутки, галочек в раскрывающихся меню, изображений флажков, переключате- лей и других служебных изображений.
Также растровые образы применяются для создания кистей. Кисти являются шаблонами пикселов, которые АУтйомга использует для закрашивания изобража- емых на экране областей. Пример такого использования битового образа вы мо- жете найти в листинге 12.1 (глава 12). Растровые образы создаются при помощи графического редактора аналогич- но созданию пиктограмм. На первом шаге в диалоговом окне Тпзег! Кезоигсе вмес- то типа ресурса 1соп выбирается тип Вйгпар. Вызванный графический редактор будет работать в режиме создания растрового ресурса. В отличие от пиктограмм, растровые образы не ограничены в размерах. Когда вы назначаете ресурсу идентификатор (на третьем шаге), окно Вйтар РгорегВез (рис. 5.14) позволяет установить любые размеры для рисунка, а также выбрать палитру, содержащую 16 или 256 цветов. Иногда бывает удобнее создать рисунок во внешнем графическом редакторе, обладающем более широкими возможностями, и добавить его в состав ресурсов приложения, выбрав на первом шаге режим Импорт. Ранее уже рассматривался сценарий импорта существующей пиктограммы. Импорт растрового образа осу- ществляется аналогично. Рис. 5.14. Диалоговое окно ВЛтар Ргорегйез Для использования растра в программе необходимо определить дескриптор типа НВ1ТМАР и присвоить ему значение, возвращаемое функцией коадВИтар: НВ1ТМАР Ьоас1ВПтар( Н1М8ТАМСЕ ЩпзРапсе, // дескриптор экземпляра приложения 1РСТЗТР 1рВ1 ЦпарМате // имя растрового ресурса ); Второй параметр функции интерпретируется так же, как для функции коасИсоп. Растровые ресурсы хранятся в модулях АУш32 в формате упакованного В1В- растра. Функция коадВИтар находит растровый ресурс, загружает его в память, получает дескриптор упакованного ВТВ-растра, затем на его базе создает ВВВ-растр, совместимый с текущим экранным режимом, и возвращает дескриптор ВО В-растра. Вывод растрового образа в окно осуществляется при помощи функции ВДВ 11: или 51ге1:сЬ ВК с использованием совместимого контекста в памяти. Подробно эта технология рассматривалась в главе 3. Продолжим разработку программы Ви$$1’а. На этом этапе мы добавим к ней ресурс растрового образа. Для подготовки изображения воспользуемся внешним графическим редакто- ром М8 Ратк. Создайте с его помощью новый рисунок размером 400 х 280 пиксе- лов, содержащий изображение российского государственного флага с наложени- ем надписи «Впззха Еос1ау» (рис. 5.15).
Рис. 5.15. Создание файла ки551аТос1ау.Ьтр с помощью М5 Ра1п1 Этот рисунок нужно сохранить в файле с именем РизПад.Ьтр, выбрав 256-цвет- ный формат. Поместите файл с рисунком в папку проекта Киззга1. Добавьте к нашему приложению ресурс растрового образа в режиме Импорт, связав его с файлом КизНад.Ьтр и назначив идентификатор ЮВ_К11$Е1_А6. Если после сохранения ресурса открыть файл Ки$$1’а.гс в текстовом режиме, то можно найти строку с определением нового ресурса: ЮВ-КиЗЕКАС В1ТМАР 013САР0АВ1Е "РизПад.Ьтр" Теперь займемся кодом программы. Для создания третьей редакции проекта нужно сделать несколько дополнительных вставок. Добавьте в теле оконной процедуры определения переменных $Шс НВ1ТМАР ВВтрРизПад; // дескриптор растра НОС НМетВС; // контекст устройства в памяти 81са1о1 с В1ТМАР Ьт; // параметры растра В блоке обработки сообщения ММ_СРЕАТЕ добавьте инструкции ИВтрРизПад = ВоабВпШар(ВIпШ, МАКЕ1МТВЕ30иВСЕ(IВВ_ВиЗЕ1_АС)); беШэдесШВтрРизПад, зыеоГОэт), (ЕРЗТЮ&Ьт): В блоке обработки сообщения ММ_РА1МТ сразу после вызова функции Вед1 п Ра1 п1: разместите следующий фрагмент кода: йМетВС = СгеаШСотраШ1еВС(ВВС); 8е1есШЬдесШМетВС, ВВтрРизПад); ВНВ1ШВС, 0, 0, Ьт.ЬпМШН, Ьт.ЬтНе1дШ, ВМетВС, О, О, 8ВСС0РУ); ВеШеВСШМетВС); После компиляции и запуска приложения его основное окно будет выглядеть, как показано на рис. 5.16. 1 Вы можете взять готовый файл с рисунком, если загрузите исходный код данного проекта из фай- лов к книге, доступных на сайте издательства «Питер» (уууууу.рйег.сот).
Ресурсы, определяемые программистом 263 Рис. 5.16. Программа ки551а. Курсор мыши находится у нижней границы окна Перемещая курсор мыши в окне программы, понаблюдайте за изменением формы курсора и за качеством отображения картинки. Теперь проведите такой эксперимент. Замените второй аргумент (&гес1:) в вызове функции 1пуа11’с1а1:еКес1: значением МОП и перекомпилируйте программу. Посмотрите, как изменится ее поведение при перемещении курсора мыши. Ресурсы, определяемые программистом Ресурсы, определяемые программистом (изег-Ае/тес! гезоигсе), позволяют вклю- чать в выполняемый файл данные любого типа, после чего программа может лег- ко получить к ним доступ во время своей работы. Эти ресурсы могут иметь любой формат: текстовый, двоичный или даже смешанный. Предположим, что есть файл ргодНеф.Ш, в котором содержится текст подска- зок для пользователей приложения. Его можно включить в проект в качестве ре- сурса, если добавить в файл описания ресурсов следующую строку: НЕ1РТЕХТ ТЕХТ 013САР0АВЕЕ ргодЫр.Ш Имя ресурса (НЕ1_РТЕХТ) и его тип (ТЕХТ) в этом выражении могут быть лю- быми. Для имени ресурса стоит использовать прописные буквы, так как компи- лятор ресурсов в любом случае преобразует символы этого имени в символы вер- хнего регистра. В процессе инициализации программы, например при обработке сообщения ММ_СКЕАТЕ, можно получить дескриптор этого ресурса, а через него — доступ к данным в файле ргодНефЛхЕ Другой пример: вы можете включить в программу в виде ресурса мультиме- дийный файл с записанным звуковым сопровождением. Пример такого исполь- зования ресурсов, определенных программистом, приводится в разделе «Воспро- изведение звуковых файлов». К сожалению, редактор ресурсов Ухзпа! 81псйо не очень удобен для добавле- ния ресурсов, определяемых программистом. Поэтому проще будет открыть файл
описания ресурсов в текстовом формате и добавить строку описания вашего ре- сурса в следующем формате: имя _ресурса тип уаесурса 015САК0АВ1Е имя_файла Рассмотрим технику доступа к ресурсам, которые определены программистом. Она немного сложнее, чем для ресурсов стандартных типов. Доступ к данным в ресурсе Для получения доступа к данным ресурса, определенного программистом, исполь- зуются функции Нпс1Ке$оигсе, 1_оас1 Кезоигсе и 1_оскКе$оигсе. Функция Нпс1 Кезоигсе ищет ресурс указанного типа в соответствующем модуле. Она имеет следующий прототип: НВ8ВС ЕШВезоигсеШМООВЕЕ ИМос1и1е, 1РСТ8ТВ 1рМате, 1РСТЗТР 1рТуре); Параметр Н Мод и 1е позволяет указывать дескриптор модуля выполняемого фай- ла, который содержит указанный ресурс. Если НМоди1е равен МОП, то АУшсклуз ищет ресурс в модуле, создавшем текущий процесс. Параметр фМате содержит имя ресурса, представленное, как правило, строкой. Но если первым символом в строке является знак «#», то остальная часть представ- ляет целое значение идентификатора ресурса. Параметр фТуре задает тип ресурса. При успешном выполнении функция возвращает дескриптор информационного блока указанного ресурса, а не дескриптор самого ресурса. Чтобы получить деск- риптор ресурса, нужно передать дескриптор информационного блока функции 1_оас1Ке$оигсе. В случае неудачи функция Нпс1Ке$оигсе возвращает значение МОП. Функция 1_оасШе$оигсе загружает ресурс, найденный функцией ПпсШезоигсе, в глобальную область памяти. Прототип этой функции выглядит следующим образом: НСЕОВАЕ ЕоаЖезоигсеСНМООВЕЕ ИМос1и1е, НкЗкС ИКезТпТо); Параметру НКезТпГо должен передаваться дескриптор информационного бло- ка, возвращенный функцией НпсШезоигсе. Хотя адрес, возвращаемый функцией 1_оас1Ке$оигсе, в принципе, можно исполь- зовать для доступа к данным ресурса, М8ВХ рекомендует получать указатель на эти данные, вызывая функцию 1_оскКе$оигсе: ЕРУОЮ ЕоскРезоигсе(Н6Е0ВАЕ ВРезОа^а); Функция осуществляет блокировку указанного ресурса в памяти. Параметр Н КезОаЕа — это дескриптор ресурса, который должен быть заблокирован. В каче- стве параметра необходимо передать адрес, возвращенный функцией 1_оасШе$оигсе. В случае успешного выполнения функция 1_оскКе$оигсе возвращает указатель на первый байт данных ресурса, в случае неудачи — значение МОП. После использования данных ресурса его разблокировка не требуется, так как система автоматически удаляет ресурс из памяти при закрытии приложения. Если вернуться к примеру с файлом ргодНеф.Ш, который был добавлен к при- ложению в виде определенного программистом ресурса, то доступ к данным этого ресурса можно получить при помощи следующего фрагмента кода: Н8Е0ВАЕ ВРезоигсе = Еоас1Резоигсе(А1пз^апсе, Е1пс1Резоигсе(к1пз^апсе, "НЕЕРТЕХТ", "ТЕХТ")); сВаг* рНе!рТех^ = (сВаг*) ЕоскРезоигсе(ВРезоигсе);
Другой пример использования ресурсов, определенных программистом, будет рассмотрен в следующем разделе. Воспроизведение звуковых файлов Добавление звука к приложению может сделать его более эффективным для пользо- вателя, а в каких-то случаях и более привлекательным. Например, можно исполь- зовать специальные звуки или воспроизведение аудиоклипов, чтобы привлечь вни- мание пользователя при обнаружении ошибок или при завершении длительной операции. Другие очевидные сферы применения музыкальных и звуковых эффек- тов — это обучающие программы, а также приложения, предназначенные для пре- зентаций или рекламы. АУш32 АР1 поддерживает работу только со звуковыми файлами, записанными в формате ШУЕ (\\а\тИопп апсйо Й1е ЕошаФ). Файлы этого формата имеют расши- рение .мау. Они содержат оцифрованные с некоторой частотой 8- или 16-битовые звуковые данные. Хотя при записи ШУЕ-формата информация сжимается как аппаратными, так и программными средствами, она содержит все необходимые данные о реальном звуковом потоке. А потому файлы с расширением .мау, к сожа- лению, довольно громоздки и занимают сотни килобайтов на каждую минуту записи. Для воспроизведения звуковых файлов вызывается функция Р1ау5оипс1, кото- рая имеет следующий прототип: ВООЕ Р1ау8оипс1( ЕРС8ТР рзгЗоипсЕ НМОООЕЕ Гшюс1, ОШРО Шл/8оипс1 // строка, специфицирующая воспроизводимый звук // дескриптор экземпляра приложения // флаги воспроизведения Первый параметр функции задает либо имя файла на диске, либо строку име- ни звукового ресурса. Если при вызове Р1ау5оипс1 первый параметр равен МОП, это вызывает прекращение воспроизведения любого «играемого» файла. Второй параметр используется только при воспроизведении звука из звуково- го ресурса. Если же функция вызвана для воспроизведения звука из файла, то второй параметр устанавливается в МОП. Третий параметр управляет загрузкой и воспроизведением звука. В табл. 5.2 приведены наиболее часто используемые значения этого параметра. Таблица 5.2. Значения параметра Гс1\л<5оипс1 Флаг 5ЫО_Р11_ЕЫАМЕ 8ЫО_кЕ8О1ЖСЕ 8ЫО_8У1\1С 8ЫО_А8У1\1С Описание Параметр рзгЗоипс! представляет собой имя файла Параметр рзгЗоипс! представляет собой идентификатор ресурса Синхронное воспроизведение звука. Возврат из функции Р1ау8оипс1 происходит только по завершении воспроизведения звука Асинхронное воспроизведение звука. Возврат из функции Р1ау8оипс1 происходит немедленно после начала воспроизведения звука. Для прекращения асинхронного воспроизведения вызывается функция Р1ау8оипс1, параметр рзгВоипс! которой равен 1\1111_1_ продолжение &
Таблица 5.2. {продолжение} Флаг Описание 5МО_1_ООР Звук воспроизводится циклически до тех пор, пока не будет вызвана функция Р1ау8оипс1, параметр рзгЗоипс! которой равен 1\1111_1_. Вместе с этим флагом должен быть определен флаг 8ЫО_А8У1\1С 5ЫО_МООЕРА1Л.Т Звук по умолчанию не используется. Если указанный звук не найден, Р1ау5оипс1 завершает работу, не воспроизводя при этом звук по умолчанию 8М0_Р1Ж6Е Если параметр рзгЗоипс! не равен 1\1111_1_, то прекращается воспроизведение всех экземпляров указанного звука. Если параметр имеет значение 1\1111_1_, то прекращается воспроизведение всех звуков вызывающего приложения 8Ш_1\1СМ/А1Т Если драйвер занят, то функция немедленно возвращает управление без воспроизведения звука Функция Р1ау5оипс1 содержится в мультимедийной библиотеке АУтйомга. Что- бы ее имя и расположение стали известны компоновщику Ухзпа! ЗГисйо, необхо- димо подключить к проекту мультимедийную библиотеку мптт.ИЬ1. Таким образом, наиболее простой способ озвучить ваше приложение — это выз- вать функцию Р1ау5оипс1 в режиме воспроизведения из файла. Конечно, при этом звуковой файл .мау должен находиться в одной папке с исполняемым ехе-файлом программы. Это не очень удачное решение, если предполагается передавать про- грамму сторонним пользователям. Не все специалисты обладают достаточной под- готовкой для корректного общения с компьютером. Например, чрезмерно усерд- ный пользователь может освобождать пространство на диске, и вдруг — вот сюрприз! — нет больше звукового файла ®... Именно поэтому как растровые, так и звуковые образы, используемые в программе, рекомендуется хранить в ехе-файле приложения в виде ресурсов. Функция Р1ау5оипс1 предоставляет такую возможность — режим воспроизве- дения звука из ресурса. Ее использование довольно просто и в этом случае, так как не требует предварительного вызова функций НпсШезоигсе, 1_оасШе$оигсе и 1_оскКе$оигсе. Все действия по загрузке и получению доступа к данным ресурса выполняются автоматически самой функцией Р1ау5оипс1. Использование функции Р1ау5о ип с1 продемонстрируем на примере дальнейшей разработки программы Ки$$1’а. Мы обещали добавить к программе воспроизведе- ние государственного гимна России, и теперь выполним это обещание. В послед- ней версии программы, названной Киззп’аТодау, также будут удалены ресурсы кур- соров, определенных программистом, и вывод на экран текущей позиции курсора мыши. Эта тема уже пройдена, поэтому код программы можно сделать более про- зрачным. В тексте файла Ки$$1’аТос1ау.гс необходимо добавить строку описания звукового ресурса МУ_8СШ0 1л/АУЕ 013САЕ0АВ1Е "КизНутп.шу" Необходимо также поместить в папку проекта звуковой файл КизНутп.дау с гимном России2. 1 Для У1зпа1 81исНо 6.0 надо выполнить команду меню Рго]есТ ► ЗеНп'пдз и найти на вкладке Ипк тексто- вое поле ОЬзесТ/ИЬгагу тос1и1.е$. 2 Гимн России можно скачать из Интернета и преобразовать его к формату АУАУЕ либо воспользо- ваться файлом КпзНутп.луау в составе данного проекта. Исходные тексты к примерам этой книги доступны на сайте издательства «Питер»: уууууу.рйег.сот.
Исходный код основного файла программы приведен в листинге 5.4. Листинг 5.4. Проект Ки881аТос1ау ////////////////////////////////////////////////////////////////////// // Визз1аТос1ау .срр #1пс1ис1е <\л/1 пс1омз. Ь> #1пс1ис1е <зЬс11о.Ь> #1пс1ис1е "К1л1пс1.И" #1пс1ис1е "гезоигсе.Ь" сЬаг 828оипс1Мате[] = "МУ_80ВМВ"; // имя звукового ресурса СКЕ81ЛТ САЕЕВАСК ШбРгос(НШО, 1ЛМТ, ПРАВАМ, ЕРАВАМ); //========================^ Ш М1МАР1 1л11 пМаап(Н1ИЗТАИСЕ ЫпзЬапсе, Н1М8ТАМСЕ ЬРгеу1пзЬапсе, ЕРЗТЕ 1рСтсИ1пе, Ш пСтс18Но\л/) { М8С тзд; К1л/пс1 та1 п1л1пс1 ("Вызз1 а Ьос1ау", ЫпзЬапсе, пСтсЕИом, 1л1пс1Ргос, М1Л_1_, О, О, 400, 300); \л/М1е (СеЬМеззадеС&тзд, МВЕЕ, 0, 0)) { ТгапзШеМеззадеС&тзд); ВтзраЬсИМеззадеС&тзд); } геРигп тзд.мРагат; } //========================================== ЕЕЕЗЕЕТ САЕЕВАСК ШВРгос(НШВ Шпс1, ЮТ иМзд, ПРАВАМ мРагат, ЕРАВАМ 1Рагат) { Н1М8ТАМСЕ Н1П8-С; Н1С0И Ысоп, ЫсопЗт; НОС ВВС; РА1М1Т8ТВВСТ рз; зРаРпс НВ1ТМАР ИВтрВизПад; НОС ЬМетВС; зРаРп с В1ТМАР Вт; зм1РсЬ (иМзд) { сазе Ш_СВЕАТЕ: В1пзВ = ЗеС-Моби! еНапс11 е(ИВЕЕ); В1соп = ЕоасНсоп(Н1пз-Е, МАКЕIМТЕЕ80ВЕСЕ(IВ1_ТЕ1С0Е0ВЮ ); В1соп8т = (Н1СОМ)Еоаб1таде(Н1пз-Е, МАКЕIМТЕЕ801ЖСЕ(IВ 1_ТЕ1 СОЕОВЮ , 1МА8Е_1С0М, 16, 16, ЕЕ_ВЕЕАВЕТСОЕОЮ; 8егааззЕопд(Шпс1, ССЕ_Н1СОМ, (Е0И8)И 1соп); 8еЮ аззЕопд(61л1пс1, <ЗСЕ_НIС0М8М, (Е0И8)К 1соп8т); 6ВтрВизЕ1ад = Еоас1В1 Ртар(61пз^, МАКЕIМТЕЕ80ВЕСЕ(IВВ_ЕВ8ЕЕА8)); СеРОЬОесР(ВВтрВизЕ1 ад, зыеоШт), (ЕРЗТЮ&Ьт); // Воспроизведение звука из ресурса Р1ау8оипс1 (згЗоипВМате, 61пзР, 8МВ_ВЕ80ВВСЕ | 8МВ_А8У1\1С); Ьгеак; сазе 1л/М_РА1МТ: ВВС = ВедппРаппЬ(Шпс1, &рз); ЬМетВС = СгеаЬеСотраЫЫеВС(ЬВС); 5е1есЬОЬ0есШМетВС, ЬВтрВизЕ! ад);
Листинг 5.4. {продолжение} ВПВ1Ш0С, О, О, Ьт.ЬтШ Лк, Ьт.ЬтНе! дЛ, ИМетВС, О, О, 8ВСС0РУ); Ое1 еЬеВС(ИМетВС); Епс1Ра! пЬ(Шпс1, &рз); Ьгеак; сазе Ш_0Е8Т00У: РоЛОы!ЬМеззаде(0); Ьгеак; ЛГаьПЬ: геЬигп ОеГШпЛмРгос(Шпс1, иМзд, мРагат, 1 Рагат); } геЬигп 0; } ////////////////////////////////////////////////////////////////////// Откомпилировав и запустив программу Ки$$1’аТос1ау на выполнение, вы увидите почти такое же окно, как на рис. 5.16, только без специализированных курсоров и без отладочного вывода текста, сообщающего о позиции курсора. Но теперь одно- временно с появлением окна приложения на экране вы услышите торжественные звуки государственного гимна России! Если у вас хорошая звуковая карта и качественные колонки, то, несомненно, вам захочется встать и... испытать чувство глубокого удовлетворения от пройден- ной темы. А теперь — бонус! Хотя АУш32 АР1 поддерживает только МАУЕ-формат звуко- вых файлов, все-таки есть способ заставить зазвучать МРЗ-файлы! Для этого нуж- но использовать следующий вызов функции ЗНеИЕхесиЕе: 8Ье! 1 ЕхесЛеЖпЛ "ореп", "МуЗоипЛтрЗ", МЬЕЕ, МЬЕЕ, 81л1_8Н01л1) ; Выполнение функции ЗНеИЕхесиЕе с указанными параметрами приводит к вы- зову программы, связанной по умолчанию с файлами с расширением .трЗ. Ско- рее всего, это будет приложение АУхпАшр, если оно установлено на компьютере, либо приложение АУшсклуз Месйа Р1ауег. В любом случае вызванное приложение обеспечит воспроизведение файла Му$оипс1.трЗ. Таблицы строк Ресурс таблицы строк — это список строк, связанных с уникальными численны- ми идентификаторами. В файле описания ресурсов таблица строк имеет пример- но следующий вид: 8ТРЖТАВЕЕ ВЖАРВАВЬЕ ВЕСИМ 1В8_8ТРЖ1 "Открыть файл" 1В8_8ТРЖ2 "Закрыть файл" 1В8_8ТРЖЗ "Выход из программы" ЕИВ Это так называемое многострочное описание ресурса. Многострочное описа- ние используется также для ресурсов меню и диалоговых окон. Необходимость или полезность использования ресурса таблицы строк для на- чинающих АУшсЬмгз-программистов не вполне очевидна. Казалось бы, использо-
вание обычных символьных строк, объявленных в виде переменных или констант в исходном тексте программы, не приносит никаких проблем. Зачем еще допол- нительные строковые ресурсы? Тем не менее, в двух ситуациях использование ресурса таблицы строк дает ощутимые преимущества: □ при модификации программы с целью адаптации для иноязычного пользова- теля; □ при использовании строковых ресурсов для вывода текста в окнах подсказок (1ооШр) или в строке состояния {зШиз Ьаг) приложения. Ресурс таблицы строк значительно облегчает перевод программы на другие языки. Если вместо непосредственного использования символьных строк в ис- ходном тексте программы вынести их в файл описания ресурсов, а затем извле- кать строки по их идентификаторам, то вся текстовая информация окажется со- средоточенной в одном месте. Поэтому в случае разработки иноязычной версии сделанной ранее программы достаточно перевести текст в файле описания ресур- сов и перекомпилировать программу. Вторая сфера рекомендуемого использования ресурса таблицы строк связана с упрощением вывода подсказок в элементе управления Тоо1Вр или в строке со- стояния приложения. Эти вопросы будут рассмотрены в главе 8. Для добавления к приложению ресурса символьных строк при помощи редак- тора ресурсов выполните следующую последовательность действий: 1. В главном меню Ухзпа! 81псйо выполните команду меню ТпзеН: ► Кезоигсе. В по- явившемся диалоговом окне ТпзеН: Кезоигсе укажите тип ресурса 51ппд ТаЫе и нажмите кнопку Мем. В результате будет отображено окно редактора табли- цы строк, показанное на рис. 5.17. Рис. 5.17. Окно редактора таблицы строк 2. Сделайте двойной щелчок мышью на первой пустой строке в таблице. Появится диалоговое окно 51л пд РгорегВез, показанное на рис. 5.18. Рис. 5.18. Диалоговое окно для ввода строки Введите нужный идентификатор в текстовом поле Юи символьную строку в окне СарВоп. Закройте диалоговое окно.
3. Повторите действия, описанные в п. 2, для каждой строки, которую необходи- мо внести в ресурс. 4. Сохраните ресурс с помощью команды меню ЕНе ► 5ауе. В составе ресурсов приложения может быть только одна таблица строк. Мак- симальный размер каждой строки составляет 255 символов. В строке не может быть управляющих символов языка С, за исключением символа табуляции \1:. Однако символьные строки могут содержать восьмеричные константы \011 (та- буляция), \012 (перевод строки) и \015 (возврат каретки). Эти управляющие сим- волы распознаются функциями Стахл/Тех! и МеззадеВох. Вы можете использовать функцию 1оас151:ппд для копирования строки из ре- сурса в символьный буфер згВиГЕег: 1_оас181зг1 пд(ЩпзФапсе, 1 с1, згВиССег, 1Мах1_епдШ; Параметр 1’с1 соответствует идентификатору строки в файле описания ресурсов, а в параметре пМахкепдИз указывается максимальное число копируемых символов. Давайте рассмотрим пример программы, в которой для вывода на экран трех сообщений о статусе некоторого файла используется ресурс таблицы строк. Создайте проект с именем ЗЫпдТаЫе и добавьте к нему ресурс таблицы строк по описанной выше процедуре. Введите в таблицу три строки: Идентификатор Строка Ю5_Р11_ЕМ0ТР0иМ0 Ю8_РИЕТООВ1С Ю8_1=11_ЕкЕАООМ1У Файл %5 не найден Файл %5 слишком велик для редактирования Файл %з доступен только для чтения После сохранения этой информации в файле описания ресурсов БСлпдТаЫе.гс вы можете открыть его в текстовом режиме и увидеть следующее описание табли- цы строк: 8ТРЖТАВЕЕ В18САРВАВЕЕ ВЕ61И Ю8_Е1ЕЕМ0ТЕ0иМ0 I В8_1-1ЕЕТООВ18 I В8_1-1ЕЕВЕАВОИЕУ "Файл %з не найден." "Файл %з слишком велик для редактирования." "Файл %з доступен только для чтения." ЕИВ Теперь добавьте к проекту файлы КУУпскН, К\Л/пс1.срр и 51:лпдТаЫе.срр. Файл БФпдТаЫе.срр должен содержать текст, приведенный в листинге 5.5. Листинг 5.5. Проект 51ппдТаЫе ////////////////////////////////////////////////////////////////////// // 8ФгЩдТаЫе.срр #1пс1ыс1е <\л/1 пбо\л/8. И> #1пс1ис1е <8Рс11о.И> #1пс1ыс1е "К1л1пс1.И" #1пс1ыс1е "гезоигсе.К" сКаг згАррИатеЕ] = "8Рг1пдТаЫе"; Ш 8ФаФизЕ11еМзд(НШВ Г|\л/пс1, 1пР зФаФиз, сИаг* ГПеИате): Е^ЕЗВЕТ САЕЕВАСК ШбРгос(НШВ, С1МТ, ПРАВАМ, ЕРАРАМ); //========================================== 1пР ШИАР1 ШпМаШНЖТАМСЕ ЩпзРапсе, Н1М8ТАМСЕ Г1Ргеу1пзРапсе, ЕР8ТВ 1рСтс1Е1пе, 1пР пСгткОом)
{ М86 тзд; К1л/пс1 та1 п1л/пс1 ("8Тг1 пдТаЫе", ЫпзЬапсе, пСгткОом, 1л1пс1Ргос); мИ11е (8еЬМеззаде(&тзд, МЫЕЕ, 0,0)) { ТгапзШеМеззадеС&тзд); ВтзраЬсИМеззадеС&тзд); } геЬигп тзд.мРагат; } //============================================ ЬРЕЗиЬТ САЕЕВАСК 1л1пс1Ргос(ШМВ Шпс1, 1ИМТ иМзд, ПРАВАМ мРагат, ЕРАВАМ 1Рагат) { 8\л/1ТсН (иМзд) { сазе Ш_СВЕАТЕ: 8ЬаЬизЕ11еМзд(Шпс1, 1В8_ЕIЬЕВЕАВОМЕУ, "Е11е_Г); 8ЬаЬизЕ11еМзд(Шпс1, 108_Г1 ЕЕТООВИЗ, "Е11е_2"); 8ЬаЬизЕ11еМзд(Шпс1, IВ8_ГIЕЕМОТГОВМВ, "Е11е_3"); Ьгеак; сазе Ш_ВЕ8ТВ0У: РозЩПЬМеззадеСО); Ьгеак; ЬеТаиИ: геЬигп ВеТШпсЫРгос(Шпс1, иМзд, мРагат, 1Рагат); } геЬигп 0; } /=========================== 1пЬ 8ЬаЬизЕ11 еМзд(ШИВ Н\л/пс1, 1пЬ зЬаЬиз, скаг* ТПеИате) { Н1МЗТАМСЕ ЫпзЬ; скаг 32ЕогтаЬ[80]; скаг згВиТТег[200]; Н1пзТ = беТМоби! еНапсП е(ШЕЕ); Ьоас18Ьг1 пд(кIпзЬ, зЬаЬиз, згЕогтаЬ, 80); зрнпШзгВиТТег, згЕогтаЬ, ТПеИате); геЬигп МеззадеВох(И\л/псЕ згВиТТег, згАррИате, МВ_ОК | МВ_1СОМЕХСЕАМАТ1ОМ); } ////////////////////////////////////////////////////////////////////// В этом примере определена функция 51:а1:и$Е1ТеМ$д, отображающая информа- цию о статусе некоторого файла. Имя файла передается в параметре ГйеМате, а его текущий статус — в параметре з'Ьа'Ьиз. Содержание сообщения, соответствую- щего текущему статусу, извлекается из ресурса таблицы строк. В оконной проце- дуре функция 51:а1:и$Н1еМ$д вызывается три раза для демонстрации ее работы. Теперь можно откомпилировать проект и посмотреть, как будет выполняться программа. В главе 8 приводятся другие примеры программ, в которых используется ресурс таблицы строк. Такими примерами являются приложение Тоо1Ваг (листинг 8.1) и приложение 51а1:и$Ваг (листинг 8.5).
6 Меню и быстрые клавиши Меню является важнейшим элементом большинства традиционных АУтскпуз- приложений. Практически в каждом приложении под строкой заголовка главно- го окна отображается полоса меню (шепп Ьаг), содержащего набор пунктов. На- пример, приложение МхсгозоЙ Ухзпа! 81псйо 6.0 имеет меню, состоящее из пунктов Ейе, ЕсШ:, \йем, 1пзег1: и многих других, как показано на рис. 6.1. Рис. 6.1. Меню приложения \/15иа1 81ис1ю 6.0 Меню большинства АУтсЬм^з-приложен и й выглядят сходным образом. Это об- легчает освоение новых программ для пользователей. Поэтому при разработке новых приложений рекомендуется придерживаться сложившегося стиля оформ- ления меню. Организация и виды меню Меню, располагающееся ниже заголовка окна приложения, называется главным меню (тат тени), или меню верхнего уровня. Главное меню относится ко всему приложению. Иногда в приложениях используются контекстные меню, появляющиеся под курсором при щелчке правой кнопкой мыши. Такие меню ассоциируются с неко- торым объектом, например окном или пиктограммой, на который указывает кур- сор мыши в момент щелчка. Контекстные меню применяются для доступа к опе- рациям, которые поддерживаются объектом. Любое меню содержит пункты меню. Пункт меню обозначается на полосе меню своим именем — словом или короткой фразой.
Имя пункта может содержать выделенный символ, показанный подчеркива- нием. Этот символ называют также мнемоническим символом. Он определяет го- рячую клавишу (ИоЪкеу) для выбора пункта при помощи клавиатуры. Типы пунктов меню Различают два типа пунктов меню: □ пункт-команда; □ пункт-подменю. Пункт-команда — это терминальный (конечный) пункт на иерархическом де- реве меню. Выбирая такой пункт, пользователь либо изменяет внутреннее состояние приложения, либо заставляет его выполнить некоторое действие. В программном коде пункту-команде сопоставлен уникальный целочисленный идентификатор. Пункт-подменю — это заголовок вызываемого меню следующего, более низ- кого уровня. Навигацию по меню пользователь осуществляет при помощи мыши или кла- виатуры. Когда курсор мыши указывает на некоторый пункт главного меню, \Упк1о\\ъ выделяет его, изображая в виде объемной кнопки. Щелчок левой кноп- кой мыши осуществляет выбор выделенного пункта меню. Реакция АУтйомга на выбор пункта меню зависит от типа пункта. Если выбра- на команда, то АУхпбом^з посылает приложению сообщение ММ_СОММАМО, содер- жащее идентификатор этой команды. Если пользователь выбрал подменю, то \Упк1о\\ъ выводит на экран прямоугольную полосу этого подменю, называемого также всплывающим меню (рорир тепи). Для главного меню все подменю «всплы- вают» ниже выбранного пункта. На рис. 6.2 показано подменю ЕПе в окне прило- жения Ухзпа! 81псйо. Рис. 6.2. Подменю Н1е для приложения Х/1зиа1 8Щс1ю 6.0 В отличие от главного меню, имеющего горизонтальное размещение элемен- тов, пункты подменю размещаются в виде вертикального столбца. При навига- ции по подменю пункты выделяются инверсией цвета (см. пункт Мем).
Восприятие подменю с большим количеством пунктов облегчается, если они разделены на некоторые логические группы. Разделители обозначаются горизон- тальными линиями серого цвета. Стоит обратить внимание на некоторые различия в изображении пунктов под- меню Е1 1е. Имена пунктов Мем, Ореп и некоторых других завершаются многото- чием. Это многоточие обращает внимание пользователя на то, что при выборе данного пункта приложение выведет на экран диалоговое окно. Диалоговые окна значительно расширяют возможности графического интерфейса пользователя, но работа с ними будет рассматриваться только в следующей главе. Создавая меню, разработчик должен сам позаботиться о правильном именовании пунк- тов меню, в том числе и о многоточиях. Хотя это и не обязательно, но, как уже говорилось, следование сложившемуся стилю повышает удобство эксплуатации программы. Пункты Кесеп!: Ейез и Кесеп!: Могкзрасез помечены символом стрелки. Это озна- чает, что они открывают подменю следующего уровня. Если пользователь выде- ляет такой пункт, подведя курсор мыши, то АУтсЕпуз сразу выводит на экран со- ответствующее подменю, размещая его справа от выделенного пункта. Для этого уровня подменю используется также термин каскадное меню. Справа от имен некоторых пунктов можно увидеть строки СЮ+М, СЮ+О и дру- гие подобные клавиатурные сочетания. Это обозначения так называемых быст- рых клавиш, работа с которыми рассматривается в конце данной главы. Системное меню Каждое окно, имеющее заголовок, может предоставлять доступ к системному меню, которое вызывается щелчком левой кнопки мыши на пиктограмме, расположен- ной в левой части заголовка окна. Системное меню открывается как всплываю- щее меню. В настоящее время этим меню редко пользуются, так как оно дублиру- ет другие элементы управления окна. Клавиатурный интерфейс меню Пользователь может работать с меню без использования мыши, так как АУтсЬм^з предоставляет дублирующий клавиатурный интерфейс. Чтобы начать работу с главным меню, нужно нажать клавишу А1± или ЕЮ, пос- ле чего меню активизируется и в нем автоматически выделяется первый пункт. Выбор нужного пункта главного меню можно осуществить одним из двух спо- собов: 1. С помощью клавиш со стрелками влево/вправо выделить требуемый пункт и нажать клавишу ЕпЕег. 2. Нажать «горячую» клавишу, соответствующую мнемоническому символу в имени пункта. Выбор нужного пункта всплывающего меню осуществляется точно так же, но для навигации используются стрелки вниз/вверх. При использовании «горячих» клавиш следует помнить, что текущий язык вво- да с клавиатуры, установленный пользователем, должен соответствовать языку, на котором отображается имя пункта меню. Иначе выбор пункта не сработает.
Статус пунктов меню Пункты меню могут быть разрешенными (епаЫесС), запрещенными (сИзаЫесС) и не- доступными (руауеФ). По умолчанию пункт меню является разрешенным. Когда пользователь выби- рает такой пункт, система посылает сообщение ММ_С0ММАМ0 или отображает со- ответствующее подменю, в зависимости от типа пункта. Запрещенный и недоступный пункты с точки зрения поведения одинаковы. Их можно выделить, но нельзя выбрать. То есть и при щелчке мышью, и при на- жатии клавиши Еп1:ег ничего не происходит. Различаются запрещенный и недо- ступный пункты только своим внешним видом. Запрещенный пункт выглядит так же, как разрешенный, а недоступный пункт отображается серым цветом. Если вы хотите, чтобы пользователь знал, что пункт меню «отменен», делайте его недо- ступным. В хорошо продуманном интерфейсе пользователя приложение должно управ- лять статусом пунктов меню в зависимости от текущего состояния программы. Например, команда вставки данных из буфера обмена АУтсЕпуз не имеет смысла, если буфер обмена пуст. В такой ситуации лучше отменить и выделить серым цветом соответствующий пункт меню. Вообще, рекомендуется делать недоступ- ными те пункты меню, использование которых в данный момент бессмысленно или даже небезопасно с точки зрения устойчивости работы приложения. Каков же смысл использования запрещенных пунктов, которые внешне не от- личаются от разрешенных пунктов? М8^^ указывает, что их можно использо- вать в обучающих приложениях, когда некоторое окно выводится на экран толь- ко для иллюстрации и не должно реагировать на действия пользователя. Для изменения статуса пунктов меню применяется функция ЕпаЫеМепиКет. Отметка пунктов меню Иногда пункт меню используется в роли флажка (скеск Ъох). Флажок может быть установлен или сброшен. Переход из одного состояния в другое происхо- дит при каждом выборе пункта меню. Установленный флажок помечается сим- волом «галочка», а сброшенный флажок теряет отметку. Флажки обычно объе- диняются в группы и обеспечивают выбор либо одной, либо нескольких опций одновременно. Пункты меню могут использоваться также в роли переключателей (гасИо Ъи11оп). Переключатели, как и флажки, обычно используются в группе. В отли- чие от флажков, переключатели связываются только с взаимоисключающими опциями, поэтому в группе можно выбрать только один переключатель. Выб- ранный переключатель отмечается жирной точкой или маленьким закрашен- ным кружком. На рис. 6.3 показано окно приложения Мепи0ето1, в котором вызвано каскад- ное меню, содержащее две группы пунктов. Первая группа пунктов управляет выбором ВСВ-составляющих цвета. В ней может быть выбрана любая комбина- ция этих флажков. Вторая группа предназначена для выбора интенсивности цве- та из трех вариантов: темный, средний, светлый. Эти пункты интерпретируются как переключатели.
Рис. 6.3. Отметка пунктов-флажков и пунктов-переключателей в подменю Со1ог Для управления отметкой пунктов-флажков применяется функция СНескМепикет. Для управления отметкой пунктов-переключателей — функция СНескМепиКасИокет. Пункт меню, применяемый по умолчанию Подменю может содержать один пункт, выполняемый по умолчанию. Имя этого пункта выделяется жирным шрифтом. Когда пользователь открывает подменю двойным щелчком мыши, АУтскпуз автоматически выполнит команду по умолча- нию, закрыв при этом подменю. Если пункт по умолчанию отсутствует, то двой- ной щелчок приводит просто к открытию подменю. Атрибут «применяемый по умолчанию» может быть назначен любому пункту подменю при помощи функции 5е1:Мепи0еГаи11:кет. Определение меню в виде ресурса Вы можете создать меню одним из трех способов: □ на основе шаблона меню, определенного в файле описания ресурсов; □ при помощи функций Сгеа1:еМепи и Аррепс1Мепи; □ на основе шаблона меню, определяемого в памяти во время выполнения про- граммы при помощи функции 1_оас1МепиТпсйгесЕ Чаще всего используется первый способ. Именно он и будет рассматриваться в этой главе. Шаблон меню Определение меню в файле описания ресурсов имеет следующий вид: имя_меню МЕМО В18САВВАВЕЕ ВЕСЛИ Описание 0-го пункта Описание 1-го пункта Описание (п-1)-го пункта ЕИВ
Имя меню интерпретируется \У1пс1о\\ъ либо как С-строка с завершающим ну- левым символом, либо как целочисленный идентификатор. Точно такой же меха- низм использовался для обозначения имени пиктограммы в файле описания ре- сурсов (см. главу 5). Синтаксис описания 7-го пункта меню (? = 0,1,..., п - 1) зависит от типа пункта. Описание пункта-подменю имеет следующий вид: РОРЕР имя_пункта [, параметры] ВЕСЛИ Описание 0-го пункта Описание 1-го пункта Описание (п-1)-го пункта ЕИО Легко заметить, что за исключением заголовка (первая строка) синтаксис и семантика описаний меню и подменю совпадают. Описание пункта-команды имеет следующий вид: МЕМ1ЛТЕМ имя_пункта идентификатор [, параметры] Если в имени пункта встречается символ &, то следующий за ним символ яв- ляется мнемоническим символом. Если вместо имени пункта использовано слово 5ЕРАКАТ0К, это приведет к тому, что вместо пункта меню будет отображена горизонтальная разделительная линия между группами пунктов. Более подробно рассматривать определение шаблона меню нет нужды, пото- му что оно создается автоматически при использовании редактора меню. Вызов редактора меню В главном меню Ухзпа! 81исйо выполните команду Тпзег! ► Кезоигсе. В появившем- ся диалоговом окне 1п$еП: Кезоигсе укажите тип ресурса Мепи и нажмите кнопку Мем. В результате будет открыто окно редактора меню с заготовкой полосы нового меню (рис. 6.4). Рис. 6.4. Окно редактора меню с заготовкой полосы меню По умолчанию редактор присваивает первому из создаваемых шаблонов меню имя ЮК_МЕМ1П. В файле гезоигсе.Н этот идентификатор определяется как цело- численная константа. Если вы хотите изменить назначенное имя, то сделайте двой- ной щелчок мышью на полосе меню и в открывшемся диалоговом окне Мепи РгорегБез введите необходимый идентификатор в текстовом поле Ю. Например, для шаблона главного меню можно оставить имя по умолчанию ЮК_МЕМ1И, а для шаблонов контекстных меню назначить имена, отражающие их назначение. В начале полосы создаваемого меню будет отображен пунктирный прямоуголь- ник нулевого пункта меню. Нумерация пунктов начинается с нуля.
Атрибуты пункта меню Если сделать двойной щелчок мышью на прямоугольнике пункта меню, то будет отображено диалоговое окно Мепи Кет РгорегУез (рис. 6.5), предназначенное для ввода или изменения атрибутов этого пункта. Рис. 6.5. Диалоговое окно Мепи Кет Ргорегйез Предназначение наиболее часто используемых атрибутов показано в табл. 6.1. Таблица 6.1. Атрибуты пункта меню Атрибут Описание Ю Сарйоп Идентификатор пункта Имя пункта (если в имени встречается символ «&», то следующий за ним символ является мнемоническим) 5ерага1ог СНескеб Рор-ир Пункт представляет собой горизонтальную разделительную линию При выводе на экран пункт помечается слева галочкой Пункт определяет подменю, если флажок отмечен. В противном случае пункт является обычной командой Сгауеб Тпасйуе Вгеак Пункт недоступен в исходном состоянии (несовместим с атрибутом 1пасйуе) Пункт запрещен в исходном состоянии (несовместим с атрибутом Сгауеб) Этот атрибут может принимать одно из трех значений: Ыопе — обычный пункт меню; Со1итп — для меню верхнего уровня пункт выводится с новой строки, а для подменю — в новом столбце; Ваг — дополнительный столбец подменю отделяется вертикальной линией Как показано на рис. 6.5, изначально окно атрибутов пункта настроено на оп- ределение пункта-подменю (флажок Рор-ир установлен). Если вам нужно определить данный пункт как пункт-команду, то флажок Рор-ир необходимо сбросить. В этом случае текстовое поле Ю становится доступным для ввода идентификатора (рис. 6.6). Рис. 6.6. Диалоговое окно Мепи Кет Ргорегйез со сброшенным флажком Рор-ир
Обычно идентификаторы меню начинаются с префикса ЮМ_ (Ш Гог а шепн). Например, определяя пункт-команду для открытия файла, вы вводите в поле СарТп’оп текстовую строку &Ореп..., а в поле Ю — текстовую строку ЮМ_0РЕМ. ПРИМЕЧАНИЕ--------------------------------------------------------------- Иногда мы будем отступать от этого правила и назначать для отдельных пунктов-команд иденти- фикаторы, начинающиеся с префикса Ю_. Это удобно, если демонстрируется решение одной и той же задачи с применением разных средств. Например, в рассматриваемой ниже программе МепиОето1 выбор вида рисуемой фигуры осуществляется при помощи команд меню 10_кЕСТАМ61_Е, Ю_кНОМВ, Ю_ЕШР5Е. В главе 7 решение аналогичной задачи в приложении О1дОето2 реализу- ется через вызов диалогового окна с элементами управления типа «переключатель», которые имеют такие же идентификаторы. Применяя для сходных опций одни и те же идентификаторы в разных проектах, мы облегчаем сравнительный анализ этих решений. Уровни меню Меню верхнего уровня обычно относится к нулевому уровню. Если пункт этого меню определяет некоторое подменю, то такое подменю будет называться меню первого уровня. Если пункт меню первого уровня, в свою очередь, определяет не- которое подменю, то такое подменю будет называться меню второго уровня. Прежде чем излагать процедуру создания меню при помощи редактора меню, опишем рекурсивную процедуру определения произвольного пункта для меню 7-го уровня. Процедура определения пункта для меню /-го уровня 1. Начало. 2. Двойным щелчком мыши на прямоугольнике пункта меню вызовите диалого- вое окно Мепи Кет РгореПп’ез. 3. Если пункт определяет подменю, то есть меню (у + 1)-го уровня, выполните следующую последовательность действий: установите флажок Рор-ир; введите имя пункта. Если оно содержит мнемонический символ, то перед таким символом нужно вставить знак &; при необходимости задайте другие опции пункта; закройте окно Мепи Кет РгорегУез; для каждого пункта меню (/ + 1)-го уровня выполните настоящую процеду- ру, подставив на месте параметра I значение I + 1. 4. Если пункт определяет команду, то выполните следующую последовательность действий: сбросьте флажок Рор-ир; введите имя пункта, предваряя мнемонический символ знаком &; введите идентификатор пункта;
при необходимости задайте другие опции пункта; закройте окно Мепи Кет РгореПп’ез. 5. Если пункт определяет горизонтальную разделительную линию, то выполни- те следующую последовательность действий: сбросьте флажок Рор-ир; установите флажок 5ерага1:ог; закройте окно Мепи Кет РгорегУез. 6. Конец. Обратите внимание на то, что на шаге 3 алгоритма процедура вызывает саму себя. Именно поэтому она является рекурсивной. При кажущейся простоте алгоритма на самом деле он может описывать до- вольно сложный процесс. Вообразите себе виртуальный стек, на вершине которо- го запоминается очередной рекурсивный вызов процедуры из шага 3 алгоритма. Когда разработчик, создающий меню, достигает шага 6 алгоритма, это в общем случае вовсе не конец процесса. Если виртуальный стек не пуст, то нужно вер- нуться к прерванной процедуре, из которой произошел вызов выполняемой про- цедуры. И, конечно, удалить запомненный вызов с вершины стека. И только ког- да воображаемый стек окажется пуст, создание рассматриваемого пункта меню /-го уровня будет завершено. Теперь можно описать процедуру создания меню верхнего уровня при помо- щи редактора меню. Процедура определения меню нулевого уровня 1. Для каждого пункта меню выполните описанную выше процедуру определе- ния пункта для меню г-го уровня, полагая значение I равным нулю. 2. Если возникнет необходимость перегруппировать пункты меню в пределах кон- кретного уровня, то перетащите пункты в нужную позицию при помощи мыши. Сохраните ресурс меню аналогично сохранению ресурса пиктограммы1. Добавление меню к окну приложения После определения меню в файле описания ресурсов оно еще не появится в составе окна приложения. Чтобы это случилось, меню нужно присоединить к окну. Для этого можно использовать несколько способов. Наиболее традиционным способом является присваивание полю фзгМепиМате структуры \Л/М0СЕА55ЕХ значения указателя на имя меню, что надо сделать еще пе- ред регистрацией класса окна. Если имя меню определено как целочисленный идентификатор (например, ЮК_МЕМ1П), то применяйте макрос МАКЕТМТКЕ5011КСЕ для получения значения, присваиваемого полю фзгМепиМате. 1 См. главу 5, раздел «Создание пиктограммы с помощью графического редактора».
Так как во всех примерах программного кода в данной книге главное окно прило- жения создается с использованием объекта класса К\Л/пд, то рассматриваемый способ присоединения меню реализуется передачей значения МАКЕ1МТКЕ5ОиКСЕ(ЮК_МЕМи1) параметру тепиМате конструктора класса К\Л/пс1 (чуть позже вы увидите, как это делается, в листинге 6.1). Напомним, что оконный класс1, регистрируемый с использованием структуры мс типа \Л/М 0С1А55ЕХ, определяет меню, используемое по умолчанию всеми окнами этого класса. А что значит «используемое по умолчанию»? Только то, что при со- здании окна с помощью функции Сгеа1е\Л/1’пс1о\л/ параметру НМепи этой функции передается значение МОП. Это видно в коде конструктора класса К\Л/пс1. Вы можете связать с окном при его создании другое меню, отличающееся от того, которое применяется по умолчанию. В этом способе требуемое меню снача- ла загружается при помощи функции 1_оас1Мепи, имеющей следующий прототип: НМЕМС1 1оас1Мепи(Н1МЗТАМСЕ ЩпзШсе, ЕРСТЗТЕ 1рМепиМате); Затем дескриптор меню НМепи, возвращаемый функцией 1_оас1Мепи, передается параметру НМепи функции Сгеа1е\ЛЛтс1о\л/. Есть и еще один способ назначения меню, когда для этой цели используется функция 5е1Мепи: ВООЕ ЗеЦМепш (НШО Шпс1, НМЕИО ИМепи); В качестве параметров данная функция принимает дескриптор окна и деск- риптор меню, возвращенный функцией 1_оас1Мепи. Новое меню заменяет старое меню, если оно уже было. Внесение изменений в меню Обычно в сложных приложениях возникает необходимость изменять меню в ходе выполнения программы. АУш32 АР1 предоставляет соответствующие функции для модификации меню и его отдельных пунктов. Состав меню можно изменять с помощью следующих функций: □ АррепдМепи — добавляет новый элемент в конец меню; □ 0е1е1еМепи — удаляет существующий пункт меню и уничтожает его; □ 1пзег1:Мепи — вставляет в меню новый пункт; □ КетоуеМепи — удаляет существующий пункт меню. Необходимо хорошо понимать, как различаются функции 0е1е1еМепи и КетоуеМепи, когда они применяются к пунктам-подменю. Функция 0е1е1еМепи уничтожает это подменю, а функция КетоуеМепи — нет. Для пунктов меню наиболее часто применяются такие изменения, как смена символьной строки имени пункта, установка или снятие отметки, изменение ста- туса пункта. Функция 5е1Мепи11:ет1пГо позволяет выполнять сразу несколько та- ких операций в одном вызове функции. Можно также использовать более конк- забываете, что оконный класс — это термин АУтболуз, не имеющий никакого отношения к клас- сам С++.
ретные функции для выполнения отдельных операций, которые приведены в сле- дующем списке: □ СНескМепиКет — управляет отметкой пунктов-флажков; □ СНескМепиКасйоКет — управляет отметкой пунктов-переключателей; □ ЕпаЫеМепиКет — изменяет статус пункта меню; □ Мос1йуМепи — изменяет имя пункта меню. Некоторые из функций стоит рассмотреть более подробно. Функция СЬескМепиПет Функция ставит или снимает отметку на пункте меню, который интерпретирует- ся как флажок: ОШКО СНескМепыКет ( НМЕИи Итепи, // дескриптор меню ШИТ иЮСНескКет, // идентификатор или позиция пункта меню ШИТ ыСНеск // интерпретация второго параметра и выполняемое действие ); Пункт меню, для которого применяется функция, задается вторым парамет- ром. Этому параметру передается либо идентификатор пункта меню в файле опи- сания ресурсов, либо позиция пункта меню. Выбор варианта интерпретации зада- ется в третьем параметре. Третий параметр, иСНеск, задается как побитовая операция объединения двух флагов. Первый флаг может содержать одно из значений: МЕ_ВУСОММАМО или МЕ_ВУР05Ш0М. Второй флаг может принимать одно из значений: МЕ_СНЕСКЕО или МЕ_11МСНЕСКЕО. Интерпретация указанных значений приведена в табл. 6.2. Таблица 6.2. Значения флагов для параметра иСИеск Значение Описание МЕ_ВУСОММАЫО МР_ВУРО5ШОЫ МЕ_СНЕСКЕО МР_С11\1СНЕСКЕО Значение иЮСНескИет содержит идентификатор пункта меню Значение иЮСНескИет содержит относительную позицию пункта меню с отсчетом от нуля Поместить отметку слева от имени пункта меню Снять отметку слева от имени пункта меню Функцию СНескМепиКет так же, как и функцию СНескМепиВасПоКет, можно применять только для пунктов подменю. Пункты главного меню не могут быть помечены. ВНИМАНИЕ------------------------------------------------------------------- Используйте всегда флаг МЕ_ВУСОММАЫО, передавая параметру иЮСНескКет идентификатор пункта меню. Это предотвратит возможные проблемы в процессе сопровождения программы, если потребуется модификация меню, изменяющая относительные позиции пунктов меню. Данная рекомендация относится и к последующим функциям, интерфейс которых предоставля- ет два варианта передачи ссылок на пункты меню. Функция С11ескМепиКас1!о1(ет Эта функция ставит отметку на выбранном пункте-переключателе и снимает от- метку со всех остальных пунктов-переключателей в указанной группе пунктов:
В001 СИескМепиКасПоКет ( НМЕШ 1 Ишепи, // дескриптор меню 1ЛМТ 1с1Е1 гзТ, // идентификатор или позиция первого пункта в группе ЮТ ЫЕазб, // идентификатор или позиция последнего пункта в группе ЮТ 1 сЮИеск, // идентификатор или позиция выбранного пункта ЮТ иЕ1адз // интерпретация параметров 1бЕ1гзб, 1сИазТ и ЩСИеск ); Если параметр иЕ1ад$ имеет значение МЕ_ВУС0ММАМ0, то параметры со второго по четвертый указывают идентификаторы пунктов меню. Если параметр иЕ1ад$ равен МЕ_ВУР05Ш0М, то эти параметры указывают позиции пунктов меню. ВНИМАНИЕ----------------------------------------------------------------------- Задавая в редакторе пункты меню для группы переключателей, следует убедиться, что их иденти- фикаторы в файле гезоигее.Н имеют сквозную нумерацию и упорядочены в соответствии с позици- ями этих пунктов на полосе меню. При нарушении этого условия функция С1тескМепикас1ю11:ет может работать некорректно. Функция ЕпаЫеМепиКет Функция позволяет изменять статус пункта меню. Она имеет следующий про- тотип: В001 ЕпаЫеМепиКет ( НМЕМВ ИМепи, 1ЛМТ иЮЕпаЫеКет, ЮТ иЕпаЫе // дескриптор меню // идентификатор или позиция пункта // интерпретация второго параметра и выполняемое действие Как и при работе с функцией СНескМепиКет, параметр иЕпаЫе должен содер- жать флаг МЕ_ВУСОММАМО или МЕ_ВУР05Ш0М, а также один из трех флагов: МЕ_ЕМАВ1_ЕО (пункт разрешен), МЕ_015АВ1_Е0 (пункт запрещен) или МЕ_6ВАУЕ0 (пункт недоступен). Если в результате применения функции изменяется статус пункта главного меню, то следует обязательно вызвать функцию ОгамМепиВаг для повторного ото- бражения изменившейся полосы меню. Функция МосПГуМепи Функция изменяет существующий пункт меню: ВООЕ МосПТуМепи ( НМЕИО ИМпи, 01 ИТ иРозИспоп, 01ИТ иЕ1адз, 01М1Т-РТР иЮМеЮет, ЕРСТЗТР 1 рМеЮет // дескриптор меню // идентификатор или позиция пункта // флаги // новый идентификатор пункта // новое содержание пункта Параметр иЕ1ад$ должен содержать один из флагов: МЕ_ВУСОММАМО или МЕ_ВУ- Р05Ш0М, — значение которых рассматривалось ранее. Кроме того, параметр и Е1ад$ может содержать другие флаги, в частности, влияющие на интерпретацию парамет- ра 1рМ е\л/11:ет. Значения некоторых дополнительных флагов приведены в табл. 6.3. В этой книге мы рассматриваем меню только с традиционным текстовым пред- ставлением его пунктов. Применяя функцию МосПГуМепи для меню с текстовым представлением пунктов, используйте флаг по умолчанию МЕ_5ТР1М6.
Таблица 6.3. Флаги, влияющие на интерпретацию параметра 1рЫе\л/кет Флаг Значение параметра 1рМе\л/Пет МР_8Тк11\1С Содержит указатель на С-строку. Этот флаг используется по умолчанию МР_В1ТМАР Параметр содержит дескриптор растра. В этом случае пункт меню будет отображен не в виде текстовой строки, а в виде рисунка МР_СМ/ЫЕкОКА\Л/ Содержит данные, используемые для рисования пункта меню способом О\л/пегОга\л/ Отметим, что во всех рассмотренных функциях в качестве первого параметра указывается дескриптор меню. К счастью, АУш32 АР1 предоставляет функции, позволяющие определять значения дескрипторов как для главного меню, так и для любого подменю. Функции для получения дескриптора меню Дескриптор меню верхнего уровня, связанного с окном Н\Л/пс1, можно получить при помощи функции беРЧепи: НМЕМи 6е^Мепи(НШ0 Шпс1); Функция возвращает значение МОП, если окно Н\Л/пс1 не имеет меню. Эту функ- цию нельзя применять для дочерних окон. Дескриптор подменю определяется вызовом функции 6е1:5иЬМепи: НМЕМО (МЗиЬМепи ( НМЕМЫ ИМепи, // дескриптор родительского меню Ш пРоз // позиция пункта-подменю в родительском меню ); Относительная позиция пРоз для пункта родительского меню отсчитывается от нуля. Если пункт с позицией пРоз не активизирует всплывающее меню, а явля- ется пунктом-командой, то функция возвращает значение МОП. Сообщения меню Система АУшсклуз посылает сообщение ММ_С0ММАМ0 при каждом выборе пунк- та меню, определяющего команду. Обычно это единственное сообщение, обра- батываемое приложением, которое может поступить от меню. При выборе пунк- тов системного меню вместо указанного сообщения отправляется сообщение ДОМ_$У$СОММАМО. Иногда в программе может потребоваться обработка сообщений ММ_1М1ТМЕМ11 и ММ_11\11ТМЕ1\111РОР11Р. Они отправляются непосредственно перед активизацией главного меню или всплывающего меню. Эти сообщения позволяют приложению изменить меню перед тем, как оно будет отображено на экране. Сообщение ММ_МЕМ11СНАК отправляется, если пользователь пытается исполь- зовать клавиатурную «горячую» клавишу, которая не соответствует ни одному из мнемонических символов меню. Это позволяет обрабатывать несколько «горя- чих» клавиш для одного пункта меню или отображать сообщение об ошибке. При навигации по меню система отправляет также сообщение ММ_МЕМ11$Е1_ЕСТ. Оно более универсально по сравнению с ММ_С0ММАМ0, так как инициируется даже тогда, когда выделен недоступный или запрещенный пункт. Это сообщение мо-
жет использоваться для формирования контекстной справки меню, которая ото- бражается в строке состояния приложения. В большинстве программ все сообщения от меню, кроме ММ_С0ММАМ0, переда- ются на обработку в функцию ОеГМ’п дом Ргос. Окно получает и обрабатывает сообщение ММ_С0ММАМ0 при помощи своей оконной процедуры: КкЕ81Л_Т САЫВАСК ШбРсос ( НШО Шпс1, // дескриптор окна 01 ИТ иМзд, // Ш_С0ММАМВ ОРАВАМ мРагат, // идентификатор пункта меню ЬРАРАМ 1Рагат // 0 ); На самом деле источником сообщений ММ_СОММАМО могут быть не только пунк- ты меню, но и быстрые клавиши, а также другие элементы управления (кнопки, списки, текстовые поля и т. д.). Сообщения от меню или быстрых клавиш всегда имеют нулевое значение параметра [Рагат. Между собой эти два вида сообщений различаются значением старшего слова доРагат (табл. 6.4). Таблица 6.4. Интерпретация параметров \л/Рагат и 1Рагат для сообщений от разных источников Источник Младшее слово шРагат Старшее слово шРагат 1Рагат Меню Идентификатор пункта меню 0 0 Быстрая клавиша Идентификатор быстрой клавиши 1 0 Элемент управления Идентификатор элемента управления Код уведомления (нотификационного сообщения) Дескриптор элемента управления (НУ\Д\Ю) Обработка сообщения ММ_СОММАМО в оконной процедуре осуществляется в блоке оператора здойсН. Например, в программе Мепи0ето1, рассматриваемой ниже, для обработки команд подменю ЕПе используется следующий фрагмент кода: сазе Ш_С0ММАМВ: 8\л/ПсН (ЬОШРВЫРагат)) { сазе 10М_0РЕМ: МеззадеВох(Шпс1, "Выбран пункт 'Ореп'", "Меню П1е", МВ_ОК); Ьгеак; сазе ЮМ-СЕОЗЕ: МеззадеВох(Шпс1, "Выбран пункт 'СТезе'", "Меню П1е", МВ_ОК); Ьгеак; сазе 1ВМ_ЗАУЕ: МеззадеВох( Шпс1, "Выбран пункт '8ауе'", "Меню П1е", МВ_ОК); Ьгеак; сазе 1ВМ-ЕХ1Т: 8епс1Меззаде(Шпс1, Ш_ВЕ8ТР0У, 0, 0); Ьгеак; } // ...
Поскольку этот пример является учебным, то вся обработка для команд ЮМ_0РЕМ, ЮМ_СЮ5Е и ЮМ_$А\/Е сводится к выводу окна сообщений при помощи функции МеззадеВох. Но обработка команды ЮМ_ЕХ1Т содержит вполне реальный код, вы- полнение которого вызывает завершение работы приложения. Приложение МепиОето! Для демонстрации техники использования меню приведем пример разработки специального учебного приложения Мепи0ето1. Окно программы МепиОето! показано на рис. 6.3. Эта программа должна рисо- вать в центре окна прямоугольник, ромб или эллипс по выбору пользователя. Фигура закрашивается однородной кистью. Цвет кисти задается пользователем как комбинация цветовых составляющих Кед, Сгееп, В1ие с учетом интенсивности, выбираемой из трех вариантов: Оагк (темный цвет), МесИит (средний цвет), ПдН! (светлый цвет). Также по команде пользователя программа должна увеличивать размеры фигур до полного заполнения клиентской области окна, а также возвра- щать их к исходному размеру. Программа должна позволить пользователю скрыть изображение, а потом снова показать его. Также в приложении должно функцио- нировать подменю Ейе, пункты которого вызывают окна сообщений с информа- цией о выбранном пункте. Приступим к разработке. Создайте новый проект с именем Мепи0ето1. Скопируйте в папку проекта фай- лы Ю/Упд.Н и КУУпсЬсрр из листинга 1.2 и добавьте их в состав проекта. Определите ресурс меню по описанной выше процедуре с сохранением в файле Мепи0ето1.гс. Главное меню должно содержать пункты, которые приведены в табл. 6.5. Таблица 6.5. Пункты главного меню (0-й уровень) Имя пункта Тип пункта Идентификатор Позиция &Н1е Подменю — 0 8Л/1е\л/ Подменю — 1 &Ога\л/ зНаре Подменю — 2 &1псгеазе! Команда ЮМ_КЕ512Е 3 &АЬои1... Команда ЮМ_АВО11Т 4 Имя пункта Тпсгеазе завершается восклицательным знаком. Это подсказка для пользователя, информирующая, что данный пункт главного меню является ко- мандой, а не заголовком подменю. Подменю Ейе должно содержать пункты, перечисленные в табл. 6.6. Таблица 6.6. Пункты подменю РНе (1-й уровень) Имя пункта Тип пункта Идентификатор &Ореп... Команда ЮМ_ОРЕЫ &С1озе... Команда ЮМ_С1_О5Е &8ауе... Команда ЮМ_5АУЕ — 8ЕРАКАТОН — Е&хК Команда ЮМ_ЕХ1Т
Подменю \йе\л/ должно содержать пункты, перечисленные в табл. 6.7. Таблица 6.7. Пункты подменю \Ле\л/ (1-й уровень) Имя пункта Тип пункта Идентификатор &8Ном зНаре &Н1с1е зНаре Команда ЮМ_5НО\Л/_5НАРЕ Команда ЮМ_НЮЕ_8НАРЕ Подменю Огам зНаре должно содержать пункты, перечисленные в табл. 6.8. Таблица 6.8. Пункты подменю Огам зИаре (1-й уровень) Имя пункта Тип пункта Идентификатор &8Наре &Со1ог Подменю — Подменю — Подменю 5Наре должно содержать пункты, перечисленные в табл. 6.9. Таблица 6.9. Пункты подменю 5Иаре (2-й уровень) Имя пункта Тип пункта Идентификатор &Иес±апд1е ИИот&Ь &ЕШрзе Команда Ю_ИЕСТАМ61_Е Команда Ю_ИНОМВ Команда Ю_Е1_1_1Р5Е Подменю Со1ог должно содержать пункты, перечисленные в табл. 6.10. Таблица 6.10. Пункты подменю Со1ог (2-й уровень) Имя пункта Тип пункта Идентификатор &Иес1 &Сгееп &В1ие Команда Ю_ИЕО Команда Ю_СИЕЕЫ Команда Ю_В1_11Е — 8ЕРАИАТОИ — &Оагк &Месйит &идЫ: Команда Ю_ОАИК Команда Ю_МЕО111М Команда Ю_1_ЮНТ Теперь добавьте к проекту новый файл с именем МепиОето1.срр и введите в файл исходный код, приведенный в листинге 6.11. Листинг 6.1. Проект МепиОето1 ////////////////////////////////////////////////////////////////////// // МепиОето!.срр #1пс1ис1е <\л/1 пс1ом8. Н> #1пс1ис1е #1пс1ис1е "К1л1пс1.Н" #1пс1ис1е "гезоигсе.Н" #с!еТ1пе 1л1 200 // ширина фигуры #с!еТ1пе Н 140 // высота фигуры продолжение 1 Исходные тексты программ, приведенных в книге, можно скачать с сайта издательства ЬНр:// уууууу.рйег.сот.
Листинг 6.1 {продолжение} епыт ЗНареЗые { МАХ, М1И }; ТуребеТ зТгисТ { Ш 10_зКаре; // идентификатор фигуры В001_ ТВеб; // компонент красного цвета В001_ ТОгееп; // компонент зеленого цвета В001_ ТВ1ие; // компонент синего цвета Ш 1 с1_Ьг1 дИР; // идентификатор яркости цвета } ЗКареОаТа; ВВЕ51ЛТ САЕЕВАСК ИпОРгос(НШВ, В1МТ, ИРАРАМ, ЕРАВАМ); //========================================== 1пР ШИАР1 ШпМапп(НIМЗТАМСЕ ШзТапсе, Н1М5ТАМСЕ КРгеуТпзТапсе, ЕРЗТВ 1рСтсИ1пе, Ш пСтОЗКом) { МЗО тзд; Шпб тапп1л/пб("МепиВето!", К1пзТапсе, пСтОЗКом, ИпОРгос, МАКЕIМТРЕЗОВРСЕ (ЮР_МЕМВ1), 100, 100, 400, 300); \л/М1е (ОеТМеззадеС&тзд, МЫЕЕ, 0, 0)) { ТгапзТаТеМеззадеС&тзд); ОтзраТсНМеззадеС&тзд); } геТигп тзд.мРагат; } //========================= ЕВЕЗВЕТ САЕЕВАСК ИпОРгосШШО Шпс1, В11\1Т иМзд, ИРАРАМ мРагат, ЕРАВАМ 1 Рагат) { НОС ВВС; РА1ИТЗТВВСТ рз; ВЕСТ гесР; зТаТпс НМЕИВ КМепи; // дескриптор главного меню Ш хО, уО, х1, у1, х2, у2; РОЮТ рРЕ4]; 8ТаТпс ЗНареЗые зНареЗые = М11Х1; зТаШ ВООЕ ЬЗОом = ТВОЕ; зТаТ1с НВВВЗН ПВгизН, ПОЮВгизН; сВаг* ПетРез1хеМате[2] = { "Оесгеазе!", "1псгеазе!"}; Ш Шепз1Ту[3] = { 85, 170, 255 }; // интенсивность РОВ-компонентов цвета Ш ЬглдИТпезз; зТаШ ЗКареОаТа зКареОаТа; змПсН (иМзд) { сазе ОМ_СРЕАТЕ: КМепи = 0еТМепи(Шпс1); // ЮМ—ОРЕМ - пункт меню, применяемый по умолчанию ЗеТМепи0еТаиИ1Тет(0еТЗиЬМепи(КМепи, 0), ЮМ_0РЕМ, ЕАЕЗЕ); // Исходные отметки пунктов меню СКескМепиРас11о1Тет(0еТЗиЬМепи(ТМепи, 1), I0М_ЗН0И_ЗНАРЕ, ЮМ-НЮЕ-ЗНАРЕ, ЮМ-ЗНОВ-ЗНАРЕ, МЕ_ВТСОММАМО); СНескМепиРа01о1Тет(0еТЗиЬМепи(НМег1и, 2), I0_РЕСТАМЗЕЕ, Ю-ЕЕЕ1РЗЕ, Ю-РЕСТАМЗЕЕ, МЕ_ВТСОММАМО); СКескМепиРасМо1Тет(0еТЗиЬМепи(КМепи, 2), 10_0АРК, Ю-ЕЮНТ, Ю-0АРК, МЕ-ВТСОММАМО); зКареОаТа.10-ЗКаре = Ю_РЕСТАМЗЕЕ;
збареОаТа. 1 с1_Ьг1 дН1з = 1О_ОАВК; Ьгеак; сазе 1л/М_СОММАИО: з\л/1ТсИ (ЬОШКО(мРагат)) { сазе ЮМ-ОРЕМ: МеззадеВох( Шпс1, "Выбран пункт 'Ореп'", "Меню П1е", МВ_ОК); Ьгеак; сазе ЮМ-СЕОЗЕ: МеззадеВох(Шпс1, "Выбран пункт 'С1озе'", "Меню П1е", МВ_ОК); Ьгеак; сазе ЮМ_ЗА7Е: МеззадеВох(Шпс1, "Выбран пункт '8ауе'", "Меню П1е", МВ_ОК); Ьгеак; сазе 1ОМ-ЕХ1Т: 8епс1Меззаде(Шпс1, Ш_0Е8ТВ0У, 0, 0); Ьгеак; сазе ЮМ_ЗН01л/_ЗНАРЕ: ЬЗЬом = ТВОЕ; СЬескМепиВас11о1Тет(6еТ8иЬМепи(ЬМепи, 1), ЮМ_8Н00_8НАРЕ, ЮМ-НЮЕ-ЗНАРЕ, ЬОШВОЫРагат), МЕ_ВУСОММАМО); ЕпаЫеМепи1Тет(ЬМепи, ЮМ_ВЕ812Е, МЕ_ВУСОММАМО | МЕЗ_ЕИАВЕЕ0); ОгамМепиВаг(ШпО); Ьгеак; сазе 10М_Н10Е_ЗНАРЕ: ЬЗЬом = ЕАЕЗЕ; СЬескМепиВас11о1Тет(6еТ8иЬМепи(ЬМепи, 1), ЮМ_8Н00_8НАРЕ, ЮМ_НЮЕ_ЗНАРЕ, ЬОШВОЫРагат), МЕ_ВУСОММАМО); ЕпаЫеМепи1Тет(ЬМепи, ЮМ_ВЕ812Е, МЕ_ВУСОММАМО | МЕЗ_ЗВАУЕ0); ОгамМепиВаг(Шпб); Ьгеак; сазе Ю_ЕЕСТАМСЕЕ: збареОаТа. 1с1_зЬаре = Ю_ ВЕСТАМОЬЕ; СЬескМепиВасМо1Тет(6еТ8иЬМепи(ЬМепи, 2), Ю_ВЕСТАМОЬЕ, Ю_ЕЕЕ1РЗЕ, ЬОШВО(мРагат), МЕ_ВУСОММАМО); Ьгеак; сазе Ю_ВНОМВ: збареОаТа. 1с1_зЬаре = Ю_ВНОМВ; СЬескМепиВасМо1Тет(6еТ8иЬМепи(ЬМепи, 2), Ю_ВЕСТАМОЬЕ, Ю_ЕЕЫРЗЕ, ЬОШВО(мРагат), МЕ_ВУСОММАМО); Ьгеак; сазе Ю_ЕЕЕ1РЗЕ: збареОаТа. 1с1_зЬаре = Ю_ЕЕЫР8Е; С0ескМепиВасМо1Тет(6еТ8иЬМепи(ЬМепи, 2), Ю_ВЕСТАМОЬЕ, Ю-ЕЕЫР8Е, ЕОШВО(мРагат), МЕ_ВУСОММАМО); Ьгеак; сазе Ю_КЕО: збареОаТа ,ТВес1 = -збареОаТа ,ТВес1; СЬескМепи1Тет(6еТ8иЬМепи(ЬМепи, 2), Ю_КЕО, МЕ-ВУСОММАИО | збареОаТа.ТВеО? МЕ_СНЕСКЕО : МЕ_ОМСНЕСКЕО); Ьгеак; сазе Ю-ОВЕЕИ: збареОаТа.ТОгееп = -збареОаТа.ТОгееп; продолжение
Листинг 6.1 {продолжение} СИескМепи1Ьет(СМ8иЬМепи(кМепи, 2), Ю_0РЕЕМ, МЕ_ВХС0ММАМ0 | зкареОаЬа .ГОгееп? МЕ_СНЕСКЕО : МЕ_1ЖНЕСКЕ0); Ьгеак; сазе 1В_ВЕВЕ: зкареОаЬа.Шие = -зкареВаЬа. ГВ1 ие; СкескМепи1Ьет(СМ8иЬМепи(кМепи, 2), Ю_ВЕОЕ, МГ-ВУСОММАИВ | зкареОаЬа,ГВ1ие? МЕ_СНЕСКЕО : МЕ_1ЖНЕСКЕ0); Ьгеак; сазе 10—ВАВК: зИареВаЬа. 1с1_Ьг1дкЬ = 1В_ВАКК; СИескМепиРасМо1Ьет(беЬ8иЬМепи(ИМепи, 2), 1В_ВАКК, 1В-Ы6НТ, ЬОШРОЫРагат), МЕ_ВХСОММАМО); Ьгеак; сазе 1В-МЕВ1ВМ: зкареВаЬа. 1с1_Ьг1дкЬ = Ю_МЕ010М; СкескМепиРасМо1Ьет(беЬЗиЬМепи(кМепи, 2), 1В_ВАКК, 1В-Ы6НТ, ЬОШРОЫРагат), МЕ_ВХСОММАМО); Ьгеак; сазе 10_Ы6НТ: зкареВаЬа. 1с1_Ьг1дкЬ = 10_Ы6НТ; СкескМепиРасМо1Ьет(беЬЗиЬМепи(кМепи, 2), Ю_ОАРК, 1В-Ы6НТ, ЬОШРОЫРагат), МЕ_ВХСОММАМО); Ьгеак; сазе ЮМ_РЕ312Е: зкареЗые = (зкареЗые == М11М)? МАХ : М1И; МосНТуМепи(ИМепи, ЮМ_РЕ312Е, МЕ_ВХСОММАМО, ЮМ_РЕ312Е, 1ЬетРез1хеМате[зкаре31хе]); ВгамМепиВаг(Шпс1); Ьгеак; сазе ЮМ-АВОВТ: МеззадеВох( Шпс1, "МепиОето1\п\/егз1оп 1.0\пСоруг1 дкЬ: ПпезоГЬ СогрогаЫоп, 2005.", "АЬоиЬ МепиОето!", МВ_ОК); Ьгеак; с1еГаи!Ь: Ьгеак; } 1пуа11 (ШеРесЬ(Шпс1, ИВЕЕ, ТРОЕ); Ьгеак; сазе 1л/М_РА1МТ: ВВС = ВедапРа1 пЬ(Шпс1, &рз); Ьг1д1тЕпезз = 1 пЬепз1 Ьу[зкареОаЬа. 1 с1_Ьг1 дкЬ - Ю_0АРК]; 1Г (ЬЗком) { ИВгизк = СгеаЬеЗо!1ОВгизк(Р6В( зкареОаЬа.ТРеб? Ьп’дкЬпезз : О, зкареОаЬа.Гбгееп? Ьг1д1тЕпезз : О, зкареОаЬа.ГВ1 ие? Ьг1д1тЕпезз : 0)); кОЮВгизк = (НВРЬЗН)8е1 есЬОЬ0есЬ(ВВС, ИВгизЮ; // Определение центра фигуры (хО, уО) СМС11епЬРесЬ(Шпс1, &гесЬ);
хО = гесЬ.г1д1тЕ / 2; уО = гесЬ.ЬоШт / 2; // Координаты прямоугольника и эллипса 1Т (збареЗые == М1Ю { Х1 = хО - 1л1/2; у1 = уО - Н/2; х2 = хО + 1л1/2; у2 = уО + Н/2; } е!зе { х1 = у1 = 0; х2 = гесЬ.г1д1тЕ; у2 = гесЬ.Ьоббот; } // Координаты ромба рб[0].х = (х1 + х2) / 2; рб[0].у = у1; рб[1].х = х2; рб[1].у = (у1 + у2) / 2; рб[2].х = (х1 + х2) / 2; рб[2].у = у2; р-ГЦЗН .х = х1; рЬ[3].у= (у! + у2) / 2; змтЕсН (зЬареОаба. 1 с1_зЬаре) { сазе Ю-КЕСТАМбЬЕ: Кесбапд!е(НОС, х1, у1, х2, у2); Ьгеак; сазе Ю-КНОМВ: Ро1удоп(НОС, рб, 4); Ьгеак; сазе Ю_ЕЕЕ1РЗЕ: Е111рзе(ЬОС, х1, у1, х2, у2); Ьгеак; } □е! е’ЬеОЬзес'Ь(5е1 есЬОЬаесЬ(НОС, П01 сИЗгизП)): } ЕпбРал пб(Ыл1пс1, &рз); Ьгеак; сазе Ш_0Е8ТК0У: РозЬОьпЬМеззаде(О); Ьгеак; ЬеСаиИ: гебигп ОеСШпсЫРгос(Шпс1, иМзд, мРагат, 1Рагат); } гебигп 0; } ////////////////////////////////////////////////////////////////////// Обратите внимание на некоторые детали реализации в программном коде. Так, меню, определенное в файле ресурсов МепиОето!.гс с идентификатором ЮК_МЕМ111, подключается к окну приложения при объявлении объекта тап’пУУпс! класса К\Л/пс1: К1л/пс1 та1 п1л/пс1("МепиОето!", Ыпзбапсе, пСтс18Ьом, 1л1пбРгос,МАКЕ1МТКЕ80ЬКСЕ(10К_МЕМи1),, 100, 100, 400, 300); Текущие параметры рисуемой фигуры хранятся в переменной зНареОа^а струк- турного типа 8Наре0а1а. В блоке обработки сообщения ММ_СКЕАТЕ сначала определяется значение дес- криптора НМепи главного меню, а затем вызывается функция ЗеВЧепиОеГаиИКет
для установки атрибута «применяемый по умолчанию» пункту Ореп подменю Ейе. Дескриптор подменю Ейе, передаваемый первому параметру данной функ- ции, получен вызовом функции 6е1:5иЬМепи(НМепи, 0). Далее трижды вызывается функция СНескМепиКасйоКет для установки исход- ных отметок в группах пунктов, интерпретируемых как переключатели: Группа Первый пункт в группе Последний пункт в группе Подменю \/1е\л/ ЮМ_5НО\Л/_5НАРЕ ЮМ_НЮЕ_8НАРЕ Подменю 8Наре Ю_кЕСТА1\161_Е 1М_Е1_1_1Р5Е Подменю Со1ог Ю.ОАкК Ю_1_16НТ Отметки ставятся на пунктах, заданных четвертым параметром функции СНескМепиКасйоКет. Следует отметить, что эти пункты соответствуют началь- ным значениям переменных Ь5Нодо, $НареОа1:алс1_$Наре и $НареОа1:алс1_ЬпдН1:. В дальнейшем при обработке соответствующих сообщений отметки переходят на другие пункты внутри каждой группы. Пункты другой группы подменю, Со1ог, интерпретируемые как флажки, полу- чают свои отметки при помощи функции СНескМепиКет, когда обрабатывается сообщение ММ_С0ММАМ0 с параметром доРагат, младшее слово которого равно ЮМ_КЕ0, ЮМДЖЕЕМ или ЮМ_В1_11Е. Пункт главного меню Тпсгеазе! с идентификатором ЮМ_КЕ512Е используется как флажок, переводящий переменную зНареЗые из значения М1М в значение МАХ и обратно. Но пункты главного меню не могут получать отметку взведенного флажка. Поэтому, обрабатывая сообщение ММ_С0ММАМ0 с параметром доРагат, равным ЮМ_КЕ512Е, программа изменяет имя пункта от Тпсгеазе к Оесгеазе и обратно. Эта смена имени обеспечивается вызовом функции МодйуМепи. Символьная строка, назначаемая пункту, извлекается из массива йетРезыеМате. После возврата из Мос1йуМепи вызывается функция ОгамМепиВаг, чтобы немедленно отобразить из- менения в полосе главного меню. Когда изображение скрыто после выполнения команды ЬПде зНаре из подменю \йем, действия по изменению размеров фигуры теряют смысл. Интерфейс про- граммы только выиграет, если в этой ситуации пункт меню ЮМ_КЕ512Е сделать недоступным. При обработке сообщения ММ_С0ММАМ0 с параметром мРагат, рав- ным ЮМ_НЮЕ_5НАРЕ, вызывается функция ЕпаЫеМепиКет, которой передается флаг МЕ$_6ВАУЕ0. В результате этого указанный пункт меню становится недоступ- ным. При обработке сообщения ММ_С0ММАМ0 с параметром доРагат, равным ЮМ_$НОМ_$НАРЕ, вызывается функция ЕпаЫеМепиКет с флагом МЕ$_ЕМАВ1_Е0, и пункт меню ЮМ_КЕ512Е снова становится доступным для пользователя. При обработке сообщения ММ_С0ММАМ0 с параметром мРагат, равным ЮМ_АВ011Т, вызывается функция Ме$$адеВох, которая выводит окно сообщений с информаци- ей о программе и ее разработчиках. В финальной части обработки сообщения ММ_С0ММАМ0 вызывается функция 1пуа11’с1а1:еКес1:, чтобы перерисовать клиентскую область окна с учетом изменивше- гося состояния приложения. Рекомендуем вам откомпилировать эту программу и посмотреть, как она ра- ботает.
Работа с контекстным меню Контекстное меню, называемое иногда меню быстрого вызова (зИогксиЪ тени), по- является в любой части окна приложения при щелчке правой кнопкой мыши. Оно выглядит как всплывающее меню, но без привязки к меню верхнего уровня. Обыч- но содержание контекстного меню изменяется в зависимости от «контекста» — места, где произведен щелчок правой кнопкой мыши. В сложных приложениях контекстные меню часто содержат пункты, дублирующие команды основного меню, но сгруппированные иначе, чтобы максимально облегчить пользователю работу с приложением. После выбора пункта контекстного меню оно исчезает с экрана. Конечно же, для программиста создание и обработка контекстного меню име- ют свою специфику. Определение шаблона контекстного меню Создавая шаблон контекстного меню с помощью редактора меню, определите ну- левой пункт меню нулевого уровня как подменю, имеющее какое-нибудь услов- ное имя. Этот пункт нигде не будет отображаться. Он необходим только для по- лучения дескриптора подменю с помощью функции 6е1:5иЬМепи. Затем определите подменю (меню 1-го уровня) по процедуре, описанной выше. Сохраните созданный шаблон в файле описания ресурсов, назначив ему под- ходящий идентификатор, например ЮК_МЕМ11_МУС0МТЕХТ. Загрузка меню Загрузка контекстного меню осуществляется в блоке обработки сообщения ММ_СКЕАТЕ при помощи функций 1_оас1Мепи и 6е15иЫЧепи: НМЕМС1 ИМепиМуСогДех^; ИМепиМуСогДех^ = Ьоас1Мепи(Шнапсе, МАКЕ1МТЕЕ501ЖСЕ(IОЕ_МЕМи_МУСОМТЕХТ)); ИМепиМуСогДех^ = бе^ЗиЬМепиШМепиМуСогДех^, 0); Функция 1_оас1Мепи возвращает дескриптор меню, определенного в файле ре- сурсов с идентификатором 10К_МЕ1\111_МУС01\1ТЕХТ. Этот дескриптор относится к фиктивному меню нулевого уровня, которое не должно отображаться на экране. Содержанием контекстного меню является нулевой пункт указанного меню, по- этому окончательное значение дескриптора НМепиМуСогйех!: определяется вызо- вом функции 6е15иЫЧепи. Полученный дескриптор НМепиМуСогйех!: потом передается функции Тгаск Рорир- МепиЕх, которая выводит всплывающее контекстное меню на экран. Вызов меню Когда пользователь делает щелчок правой кнопкой мыши, АУшсклуз отправляет окну приложения сообщение ММ_КВиТТ0М00М1\1, содержащее клиентские коорди- наты1 курсора мыши в момент щелчка. Кроме того, АУшсЬауз отправляет сообще- ние ММ_С0МТЕХТМЕМ11, содержащее экранные координаты (зсгееп соогНтаЪез) курсора 1 Координаты указываются относительно левого верхнего угла клиентской области окна.
мыши. Вы можете организовать вызов контекстного меню, обрабатывая любое из этих сообщений, при помощи функции ТгаскРорирМепиЕх: В001 ТгаскРорирМепиЕх ( НМЕИи ктепи, // дескриптор контекстного меню ЩИТ ТиЕ1 адз, // флаги Ш х, // горизонтальная позиция 1пТ у, // вертикальная позиция И Ыпс1, // дескриптор окна ЕРТРМРАВАМЗ 1рТрт // область экрана, которую меню не должно перекрывать ); Второй параметр функции может содержать флаги, управляющие размещени- ем полосы меню относительно позиции курсора мыши (х, г/), и флаги, задающие некоторые другие характеристики. Обычно можно воспользоваться флагами по умолчанию, передавая этому параметру нулевое значение. По умолчанию левый верхний угол всплывающего меню привязывается к точке (х, у). Параметрам х и у необходимо передать экранные координаты курсора мыши в момент щелчка правой кнопкой. Координаты курсора мыши можно извлечь из параметра [Рагат сообщения ММ_КВ11ТТОМООММ или сообщения ММ_СО1\1ТЕХТМЕ1\111. Нужно только учесть, что сообщение ММ_КВ11ТТОМООММ содержит клиентские ко- ординаты, поэтому при работе с этим сообщением необходимо преобразовать кли- ентские координаты в экранные координаты при помощи функции СПепНоЗсгееп. Параметр Нмпс1 содержит дескриптор окна, владеющего контекстным меню. Все сообщения от меню будут направляться этому окну. При помощи параметра 1р1:рт можно определить область экрана, которую меню не должно перекрывать. Чаще всего этому параметру передается значение МОП. Функция не возвращает управление до тех пор, пока работа пользователя с меню не будет завершена выбором пункта или отказом от выбора. Обработка сообщений, направляемых окну-владельцу от контекстного меню, не отличается от обработки сообщений, источником которых является основное меню. Приемы работы с контекстным меню продемонстрируем на примере разработ- ки конкретного приложения. Приложение МепиОето2 Приложение Мепи0ето2 решает те же задачи, что и приложение Мепи0ето1, отли- чаясь от него только организацией меню. Главное меню приложения Мепи0ето2 содержит те же пункты, что и приложение Мепи0ето1, за вычетом пункта Отам зНаре. Подменю второго уровня 5Наре и Со 1ог для отсутствующего пункта ОгамзНаре долж- ны быть реализованы в виде контекстных меню. Первое контекстное меню, всплывающее при щелчке мышью в левой полови- не окна, позволяет выбрать вид фигуры. Второе контекстное меню, всплывающее при щелчке мышью в правой половине окна, позволяет выбрать цвет заливки фигуры. Создайте новый проект с именем Мепи0ето2. Скопируйте из папки проекта Мепи0ето1 в папку проекта Мепи0ето2 файлы с расширениями .срр, .Н и .гс, скор- ректировав их имена заменой МепиОето! на Мепи0ето2. Добавьте эти файлы в состав проекта.
Откройте в окне редактирования файл гесоигсе.Н и найдите строку коммен- тария // Ызес! Ьу МепиЬето1.гс Замените ее следующей строкой: // 0зес1 Ьу Мепи0ето2.гс В окне Могкзрасе перейдите на вкладку Кезоигсе\/1’едо, откройте в иерархическом списке папку Мепи и двойным щелчком мыши на элементе ЮК_МЕМ1И вызовите редактор меню. Из меню нужно удалить пункт Ога\л/ зНаре, выделив его мышью и нажав клави- шу Ие1е1:е. Среда разработки Ухзпа! ЗЬпсйо не позволит сделать это сразу, выведя предупреждение в виде диалогового окна (рис. 6.7). Рис. 6.7. Предупреждение об уничтожении всех пунктов подменю Ога\л/ зИаре Нажмите клавишу ОК для подтверждения удаления пункта меню. Теперь нужно определить два новых шаблона меню, которые будут использо- ваться для контекстных меню. Эти шаблоны должны получить идентификаторы ЮК_МЕМ11_5НАРЕ и ЮК_МЕМи_СОЮК соответственно. Меню ГОК_МЕМи_$НАРЕ должно иметь фиктивный пункт нулевого уровня с ус- ловным именем зНаре, а подменю зНаре должно содержать пункты, указанные в табл. 6.9. Меню ЮК_МЕМ11_СО1_ОК должно иметь фиктивный пункт нулевого уров- ня с условным именем со1ог. Подменю со1ог должно содержать пункты, указанные в табл. 6.10. Код модуля Мепи0ето2.срр приведен в листинге 6.2. Листинг 6.2. Проект МепиОето2 ////////////////////////////////////////////////////////////////////// // Мепи0ето2.срр #1пс1ис1е <\л/1 пс1ом8. Ь> #1пс1ис1е <зЬс11о.Ь> #1пс1ис1е "К1л1пс1.Ь" #1пс1ис1е "гезоигсе.Ь" #с!еТ1пе Ы 200 // ширина фигуры #с!еТ1пе Н 140 // высота фигуры епит ЗИареЗые { МАХ, М1И }; Ьурес1еТ зЬгисЬ { 1пЬ ч’сКЬаре; // идентификатор фигуры ВООЬ ТВес1; // компонент красного цвета ВООЬ ТОгееп; // компонент зеленого цвета ВООЬ ТВ1ие; // компонент синего цвета 1гтЬ 1 с1_Ьг1 дЬЬ; // идентификатор яркости цвета } ЗЬареОаЬа; ЬВЕЗОЬТ САЬЬВАСК ЫпЬРгос(НШО, 01ИТ, ЫРАВАМ, ЬРАВАМ);
Листинг 6.2 {продолжение} //============================ Ш ШИАР1 ШпМапп(НIМЗТАМСЕ Шнапсе, Н1М5ТАМСЕ НРгеу1пзТапсе, ЕРЗТР 1рСтс1Е1пе, 1пТ пСтс15Ио\л/) { МЗО тзд; Шпс1 та1 п1л/пс1 ("Мепи0ето2", Н1пзТапсе, пСтбЗНом, ИпбРгос, МАКЕ1МТРЕ30иРСЕ( ЮВ_МЕМ01), 100, 100, 400, 300); \л/М 1 е (ОеТМеззадеС&тзд, ШЕЕ, 0, 0)) { Тгап81аТеМе88аде(&тзд); ОтзраТсНМеззадеС&тзд); } геТигп тзд.мРагат; } //========================= ЕРЕЗЕЕТ САЕЕВАСК ИпбРгос(ШИВ Шпс1, ЕIИТ иМзд, НРАВАМ мРагат, ЕРАВАМ 1Рагат) { НОС НОС; РА1МТЗТРЕСТ рз; ВЕСТ гесТ; зТаТ1с НМЕМЕ НМепы; // дескриптор главного меню зТаТ1с НМЕМЕ НМепыЗНаре; // дескриптор 1-го контекстного меню зТаТ1с НМЕМЕ НМепыСо1ог; // дескриптор 2-го контекстного меню РОЮТ ропПО, рТ1пС11епТ; 1пТ хО, уО, х1, у1, х2, у2; РОЮТ рТ[4]; зТаТ1с ЗИареЗаге зНареЗые = М11\1; зТаТ1с ВООЕ ЬЗНом = ТВОЕ; зТаТ1 с НВВЕЗН НВгизН, НОЮВгизН; сНаг* 1ТетВез1хеМате[2] = { "Оесгеазе!", "1псгеазе!"}; 1пТ 1пТепз1Ту[3] = { 85, 170, 255 }; // интенсивность ВОВ-компонентов цвета 1пТ ЬндНТпезз; зТаТ1с ЗНареОаТа зНареОаТа; з\л/1ТсН (иМзд) { сазе ИМ_СВЕАТЕ: НМепи = 0еТМепи(Шпс1); НМепиЗНаре = ЕоаЕМепи(ЗеТ.МоЕи1 еНапЕО е(ИЕЕЕ), МАКЕIИТВЕЗООВСЕ(IО В_М Е И Е_ЗНАР Е)); НМепиЗНаре = ОеТЗиЬМепиШМепиЗНаре, 0); НМепиСо1ог = ЕоаЕМепи(ЗеТ.МоЕи1 еНапЕО е(ИЕЕЕ), МАКЕIИТВЕЗООВСЕ(IОВ_МЕИЕ_СОЕОР)); НМепиСо1ог = 0еТЗиЬМепи(НМепиСо1ог, 0); // ЮМ—ОРЕМ - пункт меню, применяемый по умолчанию ЗеТМепи0еТаи1Т1Тет(0еТЗиЬМепи(НМепи, 0), 10М_0РЕМ, ЕАЕЗЕ); // Исходные отметки пунктов меню СНескМепиВасНо1Тет(0еТ8иЬМепи(НМепи, 1), I0М_ЗН0И_ЗНАРЕ, ЮМ-НЮЕ-ЗНАРЕ, IОМ—ЗНОЕ—ЗНАРЕ, МЕ_ВМСОММАМО); СНескМепиВасНо1Тет(НМепиЗНаре, I0_РЕСТАМЗЕЕ, 10-ЕЕЕ1РЗЕ, Ю_ВЕСТАМЗЕЕ, МЕ_ВМСОММАМО); СНескМепиВас11о1Тет(НМепиСо1ог, IО_ОАВК, 10_ЕЮНТ, Ю—0АРК, МЕ_ВМСОММАМО); зНареОаТа.1б_зНаре = I0_РЕСТАМЗЕЕ;
зЬареВаба. 1 с1_Ьг1 дЬЬ = Ю_ВАРК: Ьгеак: сазе 1л/М_СОМТЕХТМЕМЬ: роШ.х = ЕО1лЮРВ(1 Рагат): роШ.у = Н11лЮРВ(1 Рагат): рЫпСИепЬ = ро1пб: ЗсгеепТоС! 1 епЬ(Шгб, &рЫпС11 егтЕ); 6еЬС11 епЬРесЬ( Шгб, &гесЬ); 1Т (рЫпСИепЬ.х < гесЬ.п’дИЬ / 2) ТгаскРорирМепиЕх(ЬМепи8Ьаре, 0, роШ.х, роШ.у, Шгб, МЫ_1_); е!зе ТгаскРорирМепиЕх(ЬМепиСо1ог, 0, роШ.х, роШ.у, Шгб, МЕ1ЕЕ); Ьгеак: сазе 1л/М_СОММАМЬ: з\л/1Ьсб (ЬОШРВЫРагат)) { /* код обработки для значений параметра: 1ВМ_ОРЕМ, ЮМ_СЕ08Е, 1ВМ_8А7Е, 1ОМ-ЕХ1Т, ЮМ_8Н01л1_8НАРЕ, IВМ_НIВЕ_ЗНАРЕ - из листинга 6.1 */ сазе 1В_РЕСТАМОЕЕ: зЬареВаба. 1б_зИаре = IВ_РЕСТАМОЕЕ: СЬескМепиРаЫо1Ьет(ЬМепи8Ьаре, IВ_РЕСТАМОЕЕ, Ю-ЕЕЕ1Р5Е, ЬОШРВЫРагат), МЕ_ВУСОММАМО); Ьгеак: сазе 1В_РНОМВ: збареОаЬа.1с1_зИаре = 1В_РНОМВ: СЬескМепиРаЫо1Ьет(ЬМепи8Ьаре, IВ_РЕСТАМОЕЕ, 1В-ЕЕЕ1Р5Е, ЬОШРВ(мРагат), МЕ_ВУСОММАМВ); Ьгеак: сазе 1В-ЕЕЕ1Р5Е: збареВаЬа.1с1_зИаре = Ю_ЕЕЕ1Р8Е: СЬескМепиРаЫо1Ьет(ЬМепи8Ьаре, IВ_РЕСТАМОЕЕ, 1В-ЕЕЕ1Р5Е, ЬОШРВ(мРагат), МЕ_ВУСОММАМВ); Ьгеак: сазе 1В_РЕВ: зЬареВаба.ТВеб = -зЬареВаба. ТКеб: СЬескМепи1Ьет(ЬМепиСо1ог, IВ_РЕВ, МЕ_ВУСОММАМВ | зЬареВаба. ТРеб? МЕ_СНЕСКЕВ : МЕ_Ь1\1СНЕСКЕВ); Ьгеак: сазе 1В_6РЕЕМ: зЬареВаба.ббгееп = -зЬареВаба.ббгееп: СЬескМепи1Ьет(ЬМепиСо1ог, IВ_6РЕЕМ, МЕ-ВУСОММАИВ | зЬареВаба.ббгееп? МЕ_СНЕСКЕВ : МЕ_ВМСНЕСКЕВ) Ьгеак: сазе 1В-ВЕВЕ: зЬареВаба. ТВ1 не = -зЬареВаба.Шие: СЬескМепи1Ьет(ЬМепиСо1ог, Ю_ВЕЬЕ, МЕ-ВУСОММАИВ | зЬареВаба. Ш ие? МЕ_СНЕСКЕВ : МЕ_В1\1СНЕСКЕВ); Ьгеак: сазе 1В_ВАРК: зЬареВаба.1б_Ьг1дЬЬ = 1В_ВАРК:
Листинг 6.2 {продолжение} СЬескМепиРаЫо1Ьет(ЬМепиСо1ог, Ю_ВАРК, 1В_Ы6НТ, ЬОШРВЫРагат), МЕ_ВУСОММАМВ); Ьгеак; сазе 1В_МЕВ1Е1М: зЬареВаба. 1с1_Ьг1дЬЬ = 1В_МЕВ1ЬМ; СЬескМепиРаЫо1Ьет(ЬМепиСо1ог, Ю_ВАРК, 10-Ы6НТ, ЬОШРВЫРагат), МЕ_ВУСОММАМО); Ьгеак; сазе 1В-Ы6НТ: зкареВаЬа. 1с1_Ьг1дЬЬ = 1В_Ы6НТ; СЬескМепиРас11о1Ьет(кМепиСо1ог, IВ_ВАРК, 1В-Ы6НТ, ЬОШРВЫРагат), МЕ_ВУСОММАМВ); Ьгеак; /* код обработки для сазе ЮМ_РЕ812Е - из листинга 6.1 */ сазе ЮМ-АВОЬТ: МеззадеВох(Шпс1, "МепиВето2\п\/егз1оп 1.0\пСоруг1 дЬЬ: ПпезоГЕ СогрогаЫоп, 2005.", "АЬоиЬ МепиВето2", МВ_ОК); Ьгеак; ЬеГаиИ: Ьгеак; } 1пуа11с1аЬеВесЬ(Шпс1, МОЕЕ, ТРЬЕ); Ьгеак; сазе Ш-РА1ИТ: /* код обработки сообщения ММ_РА1МТ - из листинга 6.1*/ Ьгеак; сазе Ш_ВЕ8ТВ0У: РозЩпЬМеззаде(О); Ьгеак; беГаиИ: гебыгп ВеГ1л11 пбомРгос(Шпс1, иМзд, мРагат, 1 Рагат); } гебигп 0; } ////////////////////////////////////////////////////////////////////// Рис. 6.8. Окно приложения МепиОето2 с контекстным меню в правой части окна
При обработке сообщения ММ_СОМТЕХТМЕМ11 необходимо выяснить, в какой ча- сти окна произведен щелчок правой кнопкой мыши. Для этого экранные коорди- наты курсора мыши, сохраненные в переменной ро1П±, преобразуются к клиентс- ким координатам, представленным переменной рЧпСПепЕ Размеры клиентской области окна определяются в переменной гее! после вызова функции 6е1С11 еп'ЬКес'!:. Если щелчок произошел в левой части окна, то функции ТгаскРорирМепиЕх пе- редается дескриптор меню НМепиЗНаре. Если щелчок был выполнен в правой час- ти окна, то функции ТгаскРорирМепиЕх передается дескриптор меню НМепиСо1ог. На рис. 6.8 показан вид окна приложения с контекстным меню НМепиСо1ог. Быстрые клавиши Быстрая клавиша (кеуЪоагб ассе1ега1;ог) — это клавиша или комбинация клавиш, которые при нажатии генерируют сообщение ММ_С0ММАМ0 или ММ_$У$С0ММАМ0. Обычно быстрые клавиши дублируют пункты меню, предоставляя пользова- телю альтернативный способ вызова команд. На рис. 6.2 мы уже видели обозначе- ния быстрых клавиш на полосе подменю ЕПе в окне приложения УНпа! 81исйо. Например, команду Мем пользователь может вызвать, либо выбрав пункт ЕПе ► Мем, либо нажав сочетание клавиш СЕгЕ+М. Очевидно, что второй способ быстрее. Хотя традиционно быстрые клавиши являются эквивалентом пунктов-команд меню, они могут генерировать и такие команды, которых нет в меню. Для многооконных приложений с множеством оконных процедур быстрые кла- виши очень важны. Как известно, АУтйомга посылает сообщения клавиатуры окон- ной процедуре того окна, которое в данный момент имеет фокус ввода. Однако при работе с быстрыми клавишами АУтсЕгмз посылает сообщение ММ_СОММАМ 0 той окон- ной процедуре, чей дескриптор был передан функции ТгапзЕаЕеАссеЕегаЕог. Как пра- вило, это будет оконная процедура главного окна приложения, то есть именно того окна, в котором расположено меню. Следовательно, нет необходимости дублиро- вать логику обработки быстрых клавиш в каждой оконной процедуре. Чтобы добавить в приложение обработку быстрых клавиш, нужно выполнить последовательность действий: 1. Модифицировать определение ресурса меню, добавив к имени каждого дуб- лируемого пункта информацию о быстрой клавише. 2. Определить таблицу быстрых клавиш в файле описания ресурсов. 3. Обеспечить загрузку таблицы быстрых клавиш в память приложения. 4. Модифицировать цикл обработки сообщений в функции УУтпМатп. Модификация определения ресурса меню Говоря о модификации, мы подразумеваем, что ресурс меню уже был определен ранее, без учета использования быстрых клавиш. В принципе, ничто не мешает вам заранее продумать вопрос дублирования меню быстрыми клавишами и сде- лать те добавления, о которых говорится здесь, на этапе создания меню. Для модификации определения пункта вызовите диалоговое окно Мепи Кет РгорегВ’ез (см. рис. 6.5) и в конце имени пункта, которое указано в поле СарВ’оп, добавьте текстовую строку \[<обозначение быстрой клавиши>.
Например, если добавляется быстрая клавиша С1г1+0 для дублирования пункта &Ореп... из подменю ЕНе, то следует добавить к прежнему имени текстовую строку \Ют1+0. После модификации поле СарВоп будет содержать строку &Ореп...\1С1:г1+0. Таблица быстрых клавиш Эта таблица обычно создается при помощи редактора таблиц быстрых клавиш. Для его запуска нужно выполнить команду меню ТпзеН: ► Кезоигсе в главном меню приложения Ухзпа! 81пс1ю. В появившемся диалоговом окне 1пзег1: Кезоигсе следу- ет указать тип ресурса Ассе1ега1юг, после чего нажать кнопку Мем. В результате бу- дет открыто окно редактора таблицы быстрых клавиш (рис. 6.9). Рис. 6.9. Окно редактора таблицы быстрых клавиш По умолчанию редактор присваивает таблице быстрых клавиш имя ЮК_АС СЕ ГЕ- КАТОК!. В файле гезоигсе.Н этот идентификатор определяется как целочисленная константа. В большинстве случаев можно согласиться с этим именем по умолча- нию. После двойного щелчка на пустой строке таблицы откроется диалоговое окно Ассе1 РгорегВез (рис. 6.10). Рис. 6.10. Диалоговое окно Ассе! Ргорегйез В поле Ю введите идентификатор быстрой клавиши. Если быстрая клавиша дублирует пункт меню, то этот идентификатор должен совпадать с идентифика- тором пункта меню. Например, если определяется быстрая клавиша для пункта &Ореп... подменю ЕНе для приложения МепиОето!, следует ввести идентификатор ЮМ.ОРЕМ. В группе переключателей Туре оставьте включенным переключатель VIгЕКеу, так как в поле Кеу будет указываться виртуальная клавиша. Коды виртуальных кла- виш можно посмотреть в табл. 4.3 (глава 4). Введите в поле Кеу идентификатор нужной клавиши. Для цифр и латинских букв в качестве идентификатора вводится сам символ. В группе Мосйбегз установите те флажки, которые соответствуют дополнитель- ным клавишам, входящим в определяемую комбинацию клавиш. Например, если определяется быстрая клавиша СЕгЕ+О для пункта &Ореп..., то следует оставить включенным флажок С!г1, а в поле Кеу ввести символ 0 (строчная буква «О» английского алфавита).
СОВЕТ----------------------------------------------------------------------------- Теоретически, можно определить быструю клавишу почти для каждой виртуальной или сим- вольной клавиши в сочетании с клавишами 8ЫЙ, СТг1 или АН. Однако надо избегать использова- ния тех быстрых клавиш, которые имеют традиционное применение во многих приложениях \Мпс1о\л/5. Например, Р1 обычно используется для вызова контекстной справки, а клавиши Р4, Е5 и Р6 — для специальных функций многооконного интерфейса приложений. Нежелательно на- значать быстрыми клавишами клавиши ТаЬ, Еп1ег, Езс и ЗрасеЬаг, поскольку они часто исполь- зуются для системных функций. Например, А11+Р4 закрывает приложение или окно, АИ+ТаЬ переключает на следующее окно, а СШ+Езс вызывает меню Старт. Описанная процедура определения строки в таблице быстрых клавиш повто- ряется для задания информации о каждой быстрой клавише в обрабатываемом меню. После этого определенный ресурс сохраняется в файле описания ресурсов. Загрузка таблицы быстрых клавиш Во время работы программы для загрузки таблицы быстрых клавиш в память и получения ее дескриптора используется функция 1оас1Ассе1ега1:ог8: НАССЕ1 ЕоабАссе!егаСогз ( Н1М5ТАМСЕ Щпзбапсе, // дескриптор экземпляра приложения ЕРСТЗТЕ 1рТаЫеМате // имя таблицы быстрых клавиш ); Для передачи второму параметру имени таблицы ЮК_АССЕ1_ЕКАТ0К1, назначен- ного редактором ресурсов, необходимо использовать макрос МАКЕ1МТКЕ5011КСЕ. В случае успешного выполнения функция возвращает дескриптор таблицы бы- стрых клавиш. В случае неудачного завершения функция возвращает значение МОП. Модификация цикла обработки сообщений Для обработки быстрых клавиш приложение должно перехватывать сообщения клавиатуры, анализировать их коды и в случае совпадения с кодом, определенным в таблице быстрых клавиш, направлять соответствующее сообщение в оконную процедуру главного окна. Все это может выполнить функция Тгап$1а1:еАссе1ега1:ог: Ш Тгап81абеАссе1егабог ( Ш Шпб, II дескриптор окна - получателя сообщения НАССЕЕ ИАссТаЫе, // дескриптор таблицы быстрых клавиш ЕРМЗЗ 1рМзд // указатель на структуру М86 ); Функция преобразует сообщение ММ_КЕУООММ или ММ-БУБКЕУООММ1 в сообще- ние ММ_СОММАМО или ММ_$У$СОММАМО, если таблица акселераторов содержит соот- ветствующий код виртуальной клавиши (с учетом состояния клавиш Йг1, АЙ и 5Нйй). Сформированное сообщение, содержащее идентификатор быстрой клавиши в младшем слове параметра \л/Рагат, отправляется непосредственно в оконную про- цедуру, минуя очередь сообщений. Возврат из функции Тгап$1а1:еАссе1ега1:ог проис- ходит только после того, как оконная процедура обработает посланное сообщение. Если функция Тгап$1а1:еАссе1ега1:ог возвращает ненулевое значение, это зна- чит, что преобразование комбинации клавиш и обработка отправленного сооб- щения завершились успешно. В этом случае приложение не должно повторно 1 Сообщения клавиатуры рассматривались в главе 4.
обрабатывать эту комбинацию клавиш при помощи функций Тгап$1а1:еМе$$аде и О1’$ра1:сНМе$$аде. Данное требование можно выполнить, если применяемый до сих пор традици- онный цикл обработки сообщений \л/Гп 1 е (бе^МеззадеС&тзд, М1Л_1_, 0,0)) { ТгапзШеМеззадеС&тзд): Отзра^сИМеззадеС&тзд); } преобразовать к следующему виду: \л/Гп 1 е (6е^Ме88аде(&т8д, М1Л_1_, 0,0)) { 1Г (! Тгап81 а1аеАссе1 ега1аог (Г|Ма1 п1л/пс1, ИАссе!, &тад)) { ТгапзШеМеззадеС&тзд); 018ра^сИМе88аде(&т8д); } } где НМап’пУУпс! — дескриптор главного окна приложения. Приложение МетЮетоЗ Приложение МепиОетоЗ является улучшенной версией приложения Мепи0ето1. Оно выполняет те же функции и имеет точно такое же меню, но теперь программа позволяет использовать быстрые клавиши, дублирующие некоторые команды меню. Дублируемые пункты меню и соответствующие им быстрые клавиши пе- речислены в табл. 6.11. Таблица 6.11. Спецификации быстрых клавиш для программы МепиОетоЗ Подменю Имя пункта Идентификатор Быстрая клавиша Н1е &Ореп... ЮМ_ОРЕЫ сш+о За&уе... ЮМ_5АУЕ СШ+5 ЗНаре &кес1апд1е Ю_кЕСТА1\1С1_Е Р2 кНот&Ь Ю.кНОМВ РЗ йЕШрзе Ю_Е1_1_1Р5Е Р4 Со1ог &кес! Ю.кЕО СШ+к &Сгееп Ю.СкЕЕЫ СШ+С &В1ие Ю_ВШЕ сш+в 8Юагк Ю.ОАкК РЮ йМесйит Ю-МЕОИ1М 1=11 &идЫ: ю_|_1бнт 1=12 Создайте новый проект с именем МепиОетоЗ. Скопируйте из папки проекта Мепи0ето1 в папку проекта МепиОетоЗ файлы с расширениями .срр, .Н и .гс, скор- ректировав их имена заменой Мепи0ето1 на МепиОетоЗ. Добавьте скопированные файлы в состав проекта. В окне Могкзрасе перейдите на вкладку Ке$оигсе\йем и вызовите редактор меню, сделав двойной щелчок мышью на значке ЮК_МЕМ1П. Теперь следует отредактировать пункты меню, указанные в табл. 6.11, добавив к имени каждого пункта символьную строку \^<обозначение быстрой клавиши>, взяв обозначения быстрых клавиш из четвертого столбца табл. 6.11. Вызовите редактор таблицы быстрых клавиш. Определите быстрые клавиши, используя информацию из третьего и четвертого столбцов табл. 6.11. Для функ-
циональных клавиш Е2, ЕЗ и им подобных в поле Кеу следует указывать идентифи- каторы \/К_Е2, \/К_ЕЗ] и т. д. Также следует сбросить флажок С±г1. Исходный код файла МепиОетоЗ.срр приведен в листинге 6.3. Листинг 6.3. Проект МепиОетоЗ ////////////////////////////////////////////////////////////////////// // МепиОетоЗ.срр #1пс1ис1е <\л/1 пс1о\л/8. Н> #1пс1ис1е <8бс11о.И> #1пс1ис1е "К1л1пс1.Н" #1пс1ис1е "гезоигсе.б" #с!еТ1пе 1л1 200 // ширина фигуры #с!еТ1пе Н 140 // высота фигуры епит ЗИареЗые { МАХ, М1И }; Сурес1еТ 8бгисб { 1пб 1с1_8баре; // идентификатор фигуры В001_ ТВеб; // компонент красного цвета В001_ ТСгееп; // компонент зеленого цвета В001_ ТВ1 ие; // компонент синего цвета 1пб 1 с1_Ьг1 дИб; // идентификатор яркости цвета } ЗбареВаба; 0ВЕ300Т САЕЕВАСК ШбРгос(НШО, ЮТ, АРАРАМ, ЕРАВАМ); //============================= Ш ШИАР1 ШпМа1п(НIМ8ТАИСЕ НШапсе, Н1М8ТАМСЕ КРпеуШбапсе, ЕРЗТВ 1рСтсЮ1пе, 1пб пСтс18Но\л/) { НАССЕЕ КАссе!; М56 тзд; К1л1пс1 та1 п1л1пс1("МепиОетоЗ", КТпзбапсе, пСтсВИом, 1л1пс1Ргос, МАКЕ1МТВЕ800ВСЕ( 10В_МЕШ1), 100, 100, 400, 300); // Загрузка таблицы быстрых клавиш КАссе! = Еоас1Ассе1епабоп8(б1п8бапсе, МАКЕ1МТВЕ800ВСЕ(I0В_АССЕЕЕВАТ0В1)); \л/М1е (6ебМе88аде(&тзд, МОЕЕ, 0, 0)) { // Если сообщение не от быстрой клавиши, // то выполнить стандартную обработку 16 (!Тпап81абеАссе1епабоп(та1п1л1пс1.6ебШпс1(), КАссе!, &тзд)) { Тгап81абеМе88аде(&т8д); 018рабсбМе88аде(&т8д); } } геТигп тзд.мРагат; } //========================= ЕВЕ80ЕТ САЕЕВАСК ШбРгос(НШ0 Шпс1, ЮТ иМзд, ПРАВАМ мРагат, ЕРАВАМ 1Рагат) { /* Текст функции из листинга 6.1 */ } ////////////////////////////////////////////////////////////////////// Откомпилируйте проект и протестируйте программу МепиОетоЗ.ехе, вызывая команды меню с помощью быстрых клавиш. 1 Коды виртуальных клавиш приведены в табл. 4.3 (глава 4).
Диалоговые окна Диалоговые окна, или окна диалога (сИа1о^ Ьох), являются одним из важнейших элементов графического интерфейса пользователя АУхпбом^з-приложений. Обыч- но диалоговые окна используются для ввода пользователем дополнительной ин- формации, а также для вывода результатов работы приложения. Диалоговое окно имеет вид всплывающего окна с одним или несколькими эле- ментами управления (соп1:го1з), которые являются для него дочерними окнами. Используя элементы управления, пользователь вводит текст, выбирает указан- ные опции (флажки, переключатели, элементы списка) и нажимает кнопки, вы- зывающие различные действия приложения. От обычных окон диалоговые окна отличаются тем, что они создаются на базе предопределенного в АУтскпуз класса диалоговых окон. Оконная процедура этого класса, спрятанная в недрах АУтсЬм^з, обеспечивает обработку сообщений, посту- пающих в диалоговое окно, а также задает специфическое поведение элементов управления диалогового окна. Например, она управляет передачей фокуса ввода от одного элемента другому или переносит фокус ввода между группами элемен- тов при нажатии клавиши ТаЬ. Эту невидимую для программиста оконную проце- дуру иногда называют менеджером диалогового окна (Д1а1о& Ьох тапарег). Менеджер диалогового окна передает многие сообщения в функцию, опреде- ленную в вашем приложении, которая называется процедурой диалогового окна, или просто диалоговой процедурой {йга1о§ ргосейиге). Эта процедура похожа на обычную оконную процедуру, но все же имеет некоторые важные особенности, которые будут рассмотрены ниже. Второе отличие диалоговых окон от обычных окон состоит в том, что они все- гда связаны с шаблоном диалога, содержащим размеры окна, состав и расположе- ние элементов управления. Шаблон диалогового окна можно определить двумя способами: а) в файле описания ресурсов, используя редактор диалоговых окон, б) создавая шаблон в памяти в процессе работы приложения. В книге рассматри- вается только первый способ, благо он наиболее простой и распространенный. Типы диалоговых окон Диалоговые окна бывают модальные (тойаГ) и немодальные (тос1е1е88). Они раз- личаются по влиянию на дальнейшее выполнение программы.
Когда в программе вызывается модальное диалоговое окно, оно ожидает выпол- нения некоторого действия со стороны пользователя, прежде чем программа смо- жет продолжить свое выполнение. Пользователь не может переключиться между диалоговым окном и другими окнами программы. Он должен явно закрыть диало- говое окно, что обычно делается щелчком на кнопке ОК или Сапсе1. Однако пользо- ватель может переключаться в другие программы, не закрыв диалоговое окно. Существует также специальный вид модальных диалоговых окон — систем- ные модальные (зуз1:ет тоба1) окна, которые не позволяют переключаться даже в другие программы. Они сообщают о серьезных проблемах, и пользователь дол- жен закрыть системное модальное окно, чтобы продолжить работу в АУтйомга. Немодальное диалоговое окно не приостанавливает выполнение программы. Оно может получать и терять фокус ввода. Это значит, что пользователь может свободно переключаться между диалоговым окном и другими окнами програм- мы. Окна этого типа предпочтительней использовать в тех случаях, когда они содержат элементы управления, которые должны быть в любой момент доступ- ны пользователю. Элементы управления в диалоговом окне Основную функциональную нагрузку в диалоговом окне выполняют элементы управления. Все версии АУтскпуз поддерживают так называемые базовые элемен- ты управления, перечисленные в табл. 7.1. Таблица 7.1. Базовые элементы управления Элемент управления Описание Предопределенный оконный класс Рисунок (Р1сЩге сопйо!) Элемент управления, отображающий пус- тую прямоугольную рамку, закрашенную прямоугольную область или растровый образ 8ТАТ1С Надпись (81айс 1ех1) Текстовая строка. Обычно используется как метка (поясняющая надпись) рядом с полем ввода или элементом управления другого типа. Может применяться как самостоятель- ная информационная надпись 8ТАТ1С Рамка (Сгоир Ьох) Прямоугольная рамка с надписью, использу- емая для группирования набора связанных элементов управления В11ТТ0П Кнопка (Вийоп) Элемент, который пользователь «нажимает», чтобы выполнить какое-либо действие В11ТТ0П Флажок (СИеск Ьох) Элемент, который может быть либо установ- лен, либо сброшен для выбора или отмены опции, которая не связана с другими опциями В11ТТ0П Переключатель (Нас1ю Ьийоп) Элемент, используемый для выбора одной из группы взаимоисключающих опций. В группе переключателей может быть выбран только один из них В11ТТ0П Список (1151 Ьох) Прямоугольное окно со списком элементов (строк), из которого пользователь может выб- рать любой элемент Ы8ТВОХ продолжение &
Таблица 7.1. {продолжение} Элемент управления Описание Предопределенный оконный класс Окно редактирования или текстовое поле (Ес1к Ьох) Прямоугольное окно для ввода текста с кла- виатуры. Элемент предоставляет определен- ные средства редактирования текста ЕО1Т Комбинированный список (СотЬо Ьох) Элемент, объединяющий список с окном ре- дактирования СОМВОВОХ Полоса прокрутки (ЗсгоН Ьаг) Элемент управления линейкой прокрутки ВСНОИВАк Начиная с \У1пс1()\\ъ 95, в системе используется библиотека элементов управ- ления общего пользования (соттоп сопёго! ИЬгагу). Новые элементы управления, включенные в эту библиотеку, дополняют базовые элементы управления и позво- ляют придать приложениям более совершенный вид. Работа с элементами управ- ления общего пользования рассматривается в следующей главе. Обычно элементы управления определяются в шаблоне диалогового окна на языке описания шаблона диалога. В случае создания шаблона с помощью редакто- ра диалоговых окон эти определения элементов генерируются автоматически. Одним из атрибутов описания элемента управления в шаблоне диалога является идентификатор элемента управления. Каждый элемент управления, описанный в шаблоне диалога, реализуется \Упк1()\\ъ в виде окна соответствующего класса. Это окно является дочерним ок- ном по отношению к диалоговому окну. Как всякое окно, оно идентифицируется своим дескриптором типа Н\Л/М0. Вместо термина «дескриптор окна элемента уп- равления» в документации (М8В?<) обычно используется более короткий тер- мин «дескриптор элемента управления». Если элемент управления определен в шаблоне диалога, то программисту из- вестен только его идентификатор. В то же время многие функции, работающие с элементом управления, принимают в качестве параметра его дескриптор. Для получения дескриптора элемента управления по его идентификатору использу- ется функция беЮфИет: НШО Се-ЬОПдШет ( НШО И01д, // дескриптор диалогового окна 1гД пI001дШет // идентификатор элемента управления ); В случае успешного завершения функция возвращает дескриптор элемента управления, в случае ошибки — значение МОП. Иногда возникает необходимость обратного преобразования, чтобы по деск- риптору элемента управления определить его идентификатор. Такую проблему решает вызов функции 6е1:01дС1:г110: Ш беШдСШЮ ( НШО Ыпс1Ш // дескриптор элемента управления ); Ранее было сказано, что элементы управления обычно определяются в шаблоне диалогового окна. Существует, однако, и альтернативный способ создания и разме- щения элемента управления при помощи функции Сгеа1еМ1’пс11’о№, первому параметру которой передается имя предопределенного оконного класса (см. табл. 1.7 в главе 1). Но такой подход используется гораздо реже.
Элементы управления могут быть разрешенными (епаЫесС) или запрещенными {сИхаЫед}. По умолчанию все элементы управления имеют статус разрешенных элементов. Запрещенные элементы выводятся на экран серым цветом и не вос- принимают пользовательский ввод с клавиатуры или от мыши. Изменение стату- са элементов управления осуществляется при помощи функции ЕпаЫе\Л/1пс1о\л/. Создание и обработка диалогового окна Создание диалогового окна и работа с ним требуют выполнения следующей пос- ледовательности действий: 1. Определение шаблона диалогового окна. 2. Определение диалоговой процедуры. 3. Вызов функции создания диалогового окна. 4. Обмен данными между диалоговой процедурой и вызывающей функцией окна. Реализация некоторых шагов различается для модальных и немодальных диа- логовых окон. Шаблон диалогового окна Определение шаблона диалогового окна в файле описания ресурсов имеет следу- ющий вид: имя_диалога 01АЮ6 015САК0АВ1Е х, у, , ЬетдЫ: [ опции_диалога ] ВЕСИМ Определение элементов диалогового окна ЕИО Имя_диалога интерпретируется АУтскпуз либо как С-строка с завершающим нулевым символом, либо как целочисленный идентификатор. Правила интер- претации такие же, как и для имени пиктограммы в файле описания ресурсов (см. главу 5). Параметры х, у, \л/1с11:Н, НетдЫ: задают позицию и размеры диалогового окна. Коор- динаты х, у измеряются относительно левого верхнего угла окна, из которого выз- вано диалоговое окно. Все координаты и размеры задаются не в пикселах, а в спе- циальных шаблонных единицах диалогового окна. Опции диалога — это необязательные инструкции, в которых можно указы- вать значения 5ТУ1Е, САРПОМ, ЕОМТ и многие другие параметры. Инструкция 5ТУ1Е напоминает параметр с1 \л/51у 1е функции Стеа1:е\Л/1’пс1одо. Для мо- дальных окон диалога обычно используются флаги М$_Р0Р11Р и М$_01_6ЕКАМЕ. Инструкция САРПОМ применяется для объявления диалоговых окон, которые имеют стиль М$_САРТ10М. Наличие заголовка позволяет пользователям переме- щать окно по экрану. Заголовок может также служить напоминанием о том, какое действие выполняется в этом окне. Синтаксис описания элементов диалогового окна зависит от типа элементов. Нет нужды более подробно рассматривать язык описания шаблона диалогового окна, так как это описание создается автоматически при работе с редактором диа- логовых окон.
Шаблонная система единиц В описании шаблона диалогового окна применяется специальная шаблонная систе- ма единиц. Размеры и расположение диалогового окна, задаваемые в параметрах х, у, Не1дН1:, а также всех элементов управления должны быть указаны в единицах этой системы, называемых также шаблонными единицами (сИакц* Ъетркйе ипИз). Шаблонные единицы определяются отдельно по горизонтали и по вертикали через ширину и высоту используемого в диалоговом окне шрифта. Одна шаблон- ная единица по горизонтали составляет четверть средней ширины символов ис- пользуемого шрифта. Одна шаблонная единица по вертикали составляет восьмую часть высоты символов. По умолчанию в диалоговом окне используется системный шрифт. Поскольку высота символов системного шрифта примерно вдвое больше его ширины, то раз- меры делений по осям X и У примерно одинаковы. Среднюю ширину и высоту символов для системного шрифта можно опреде- лить с помощью функции 6еШ1’а1одВазеIIпйз. Она возвращает 32-разрядное значе- ние, младшее слово которого равно средней ширине символов в пикселах (ЬазеипйХ), а старшее слово — высоте символов в пикселах (ЬазеишТУ). Таким образом, для пересчета шаблонных единиц 1етр1а1:еит1:Х, 1етр1а1:еит’1:У в пикселы можно вос- пользоваться следующими выражениями: р1хе!Х = ШрШеипШ * ЬазеитДХ / 4; р1хе!Х = РетрШеипШ * ЬазеипШ / 8; Подобная система единиц измерения позволяет операционной системе пра- вильно масштабировать диалоговое окно независимо от размера шрифта, уста- новленного в настройках экрана. По умолчанию АУтйомга использует «мелкий шрифт» (96 точек на дюйм). В этом режиме функция СеЮ1’а1одВазеОпИз возвращает значения ЬазеитТХ = 8, ЬазеитТУ = 16. Но пользователь может выбрать более крупный шрифт. Для смены шрифта нужно щелкнуть правой кнопкой мыши на поверхности рабочего стола и в по- явившемся диалоговом окне Свойства: Экран нажать кнопку Дополнительно..., а за- тем в окне Свойства перейти на вкладку Общие. Комбинированный список Размер шрифта позволяет выбрать другой шрифт. Если пользователь выберет «крупный шрифт» (120 точек на дюйм) и переза- грузит систему, чтобы новые параметры вступили в силу, то после этого функция 6е1:01а1одВа$е11п11з будет возвращать значения ЬазеишТХ = 10, ЬазеишТУ = 20. АУшбом^з автоматически увеличит размеры диалоговых окон, и поэтому все текстовые над- писи по-прежнему будут размещены правильно. Применяемая шаблонная система единиц дает еще одну возможность управ- ления видом и размерами диалогового окна на стадии описания шаблона. Для этого достаточно в параметрах диалога выбрать другой шрифт при помощи инст- рукции Е0 МТ. Модальный диалог Техника добавления к приложению простейшего модального диалогового окна будет рассмотрена на примере разработки приложения 01д0ето1.
Приложение 01д0ето1 является модификацией программы МепиОето1, кото- рая была рассмотрена в предыдущей главе. Напомним, что в этой программе при выборе пункта меню АЬои1... вызывается функция Ме$$адеВох для вывода окна сообщений, содержащего информационный текст. Окно сообщений тоже являет- ся модальным диалоговым окном, но всю заботу о его создании и обработке берет на себя \У1пс1о\\ъ. Но есть и обратная сторона подобной простоты создания ин- формационного окна. Возможности программиста повлиять на внешний вид и со- держимое подобного окна очень ограничены. В приложении 01д0ето1 при выборе пункта меню АЬои1... будет вызываться настоящее модальное диалоговое окно, вид и поведение которого полностью оп- ределяются программистом. В остальном приложение 01д0ето1 должно работать так же, как и приложение МепиОето1. Как обычно, сначала следует создать новый проект с именем 01д0ето1. Скопи- руйте из папки проекта МепиОето! (см. листинг 6.1) в папку проекта 01д0ето1 файлы с расширениями .срр, .Н и .гс, скорректировав их имена заменой МепиОето! на 01д0ето1. Добавьте эти файлы в состав проекта. Откройте в окне редактирования файл гесоигсе.Н и замените в его тексте стро- ку комментария // Ызес! Ьу МепиОето!.гс следующей строкой: // Ьзес1 Ьу 01д0ето!.гс Модификацию программы начнем с определения шаблона диалогового окна. Вызов и использование редактора диалоговых окон В главном меню Ухзпа! ЗЬпсйо выполните команду ТпзеП: ► Кезоигсе. В появившем- ся окне ТпзеН: Кезоигсе выберите тип ресурса О1’а1од и нажмите кнопку Мем. В ре- зультате будет открыто окно редактора диалоговых окон с формой нового окна (рис. 7.1). Изначально в нем есть только кнопки ОК и Сапсе1. Рис. 7.1. Заготовка диалогового окна в окне редактора диалоговых окон Также в окне приложения Ухзпа! 81псйо справа должна появиться панель ин- струментов Соп1го1$ (рис. 7.2). Эта панель содержит кнопки для создания элемен- тов управления всех типов, которые можно добавлять в диалоговое окно.
=! «1 к Щ- — Рю!иге 8!а!ю Тех! — -Ля зЫ - — ЕсП! Вох (Эгоир Вох — -□ О- — Вийоп СКеск Вох — -|Х ®- — Нас1ю Вийоп СогпЬо Вох ЕЕ - — Ыз! Вох Нопгоп!а1 ЗсгоИ Ваг — -ЕЕ - — Уегйса! 8сго11 Ваг 8р1п — — ф П~пт] _ — Ргодгезз 8Нс1ег — - *- - — Но! Кеу 1лз! Соп!го1 — Й=- — Тгее Соп!го1 ТаЬ Соп!го1 — -□0- — Ап1та!е РюН ЕсН1 — -2* 0 - — Оа!е Т1те Р1скег Мопй1 Са1епс1аг — 1РАс1с1ге88 Сиз!от Соп!го1 — - © 6Й - — Ех!епс1ес1 СотЬо Вох Рис. 7.2. Панель инструментов Соп!го1з Если по каким-то причинам панель инструментов СопЕго 1$ не появится, то нужно щелкнуть правой кнопкой мыши на пустом месте окна Ухзпа! 81исйо и в открыв- шемся контекстном меню выбрать пункт Соп!:го1$. Проектирование шаблона диалогового окна начинается с установки его свойств. Чтобы вызвать диалоговое окно 01а 1од РгореПп’ез (рис. 7.3), нужно сделать двой- ной щелчок левой кнопкой мыши на форме диалога. Это окно можно вызвать и другим способом, щелкнув на форме правой кнопкой мыши и выбрав во всплыв- шем контекстном меню пункт РгореПп’ез. Рис. 7.3. Окно О1а1од Ргорегйез Введите в поле Ю идентификатор Ю0_АВ011Т, а в поле СарВ’оп — текст АЬоиТ: 01д0ето1. Кнопка ЕопЕ... предназначена для вызова диалога, позволяющего выбрать шрифт, но сейчас можно оставить шрифт, используемый по умолчанию (М8 8апз 8егй, раз- мер 8). Другие параметры диалогового окна тоже можно оставить в стандартном варианте. Если переключиться на вкладку 8Еу 1е$, то можно увидеть, что по умолча- нию используются стиль Рорир и тип рамки 01а 1од Егате, а также включены отметки флажков Т1Т1е Ьаг и 5уз1:ет теп и. После установки необходимых параметров окно свойств диалога нужно закрыть. Теперь займемся «начинкой» диалогового окна. Мы хотели бы поместить ка- кой-нибудь рисунок слева на форме, затем отобразить необходимые информаци- онные надписи, а в правом нижнем углу расположить кнопку ОК. С помощью мыши увеличьте размеры формы примерно в полтора раза. Удали- те кнопку Сапсе1. Чтобы удалить элемент управления, достаточно выделить его мышью и нажать клавишу 0е1е!:е.
Переместите кнопку ОК в правый нижний угол окна. Элементы управления перемещаются на форме или мышью, или при помощи клавиш-стрелок. В случае использования клавиатуры элемент предварительно нужно выделить. Добавление элемента управления Рисунок Предположим, что рисунок, который нужно добавить на форму диалога, находит- ся в файле ВиНегйу.Ьтр и содержит изображение размером 64 х90 пикселов, пока- занное на рис. 7.4. Рис. 7.4. Растровый образ в файле ВиИегПу.Ьтр Вы можете взять готовый рисунок, если загрузите исходный код проекта 01д0ето1 из файлов, размещенных на сайте издательства «Питер» (ммм.рйег.сот). Впрочем, можно подготовить при помощи любого графического редактора дру- гой рисунок примерно таких же размеров. Файл с рисунком должен находиться в папке проекта. Прежде чем поместить растровый образ на форму диалога, нужно сначала вклю- чить его в состав ресурсов проекта. Эта процедура рассматривалась в пятой главе. Добавьте к создаваемому приложению ресурс растрового образа в режиме Импорт, связав ресурс с файлом ВиНегйу.Ьтр и назначив ему идентификатор ЮВ_В11ТТЕКНУ. Подготовительная работа завершена, и теперь можно приступить к размеще- нию рисунка на форме диалога. Щелкните мышью на кнопке Рк^иге, которая на- ходится на панели инструментов Соп1нго15. Повторите щелчок мышью в том месте формы диалога, куда вы хотите добавить этот элемент (рис. 7.5). Рис. 7.5. Размещение элемента управления Р1сЩге Во время дальнейшей разработки место размещения элемента можно скоррек- тировать, используя мышь или клавиши-стрелки. Двойным щелчком мышью на элементе управления Р1с±иге вызовите диалоговое окно свойств этого элемента (рис. 7.6). ПРИМЕЧАНИЕ---------------------------------------------------------------- Обычно идентификаторы элементов управления начинаются с префикса ЮС_ (Ю Гог а сопГго!). Впрочем, иногда мы будем отступать от этого правила и назначать для отдельных элементов управления идентификаторы, начинающиеся с префикса Ю_.
Рис. 7.6. Окно Р1сЩге Ргорегйез Введите в поле Ю идентификатор ЮС_5ТАТ1С_ВЕ1_У. В открывающемся списке Туре выберите значение Вйтар. После этого станет доступным комбинированный спи- сок 1таде. В нем пока можно найти только один идентификатор ЮВ_В11ТТЕКЕ1У, по- скольку других растровых образов в файле описания ресурсов пока что нет. Имен- но этот идентификатор и нужно выбрать в списке. Значения остальных свойств на вкладке 6епега1 оставьте такими, какими они применяются по умолчанию. Стоит обратить внимание на включенную отметку флажка У1$1Ые. Она указыва- ет, что элемент управления по умолчанию является видимым. В ходе выполнения приложения вы можете скрыть любой элемент управления при помощи функции 8Г|О\л/\Л/1’пс1о\л/, передав ей в первом параметре дескриптор элемента управления, а во втором параметре — константу $М_НЮЕ. Теперь перейдите на вкладку 81у1е8 и включите отметку флажка 5ипкеп, чтобы создать «вдавленную» рамку вокруг элемента управления. Значения остальных свойств на вкладке 81у 1е$ следует оставить неизменными. Заметим, что если уста- новить флажок 1М оИТу, то элемент управления будет посылать родительскому окну нотификационные сообщения $ТМ_С1_1СКЕО и $ТМ_ОВ1_С1_К, когда пользователь де- лает одинарный или двойной щелчок мышью на элементе управления. Закройте окно Р1с1:иге РгорегВез. После этих действий элемент управления Рк^иге будет отображать картинку с бабочкой, как показано на рис. 7.7. Но прямоугольной рамки элемента 51:а1:1’с в этот момент еще нет. Рис. 7.7. Размещение на форме диалога элемента управления 81айс Тех1 Добавление элементов управления Надпись Займемся информационными надписями. Щелкните мышью на кнопке З'ЬаНс Тех! и повторите щелчок на форме диалога правее от картинки. На форме появится прямоугольная рамка элемента З'ЬаНс, как показано на рис. 7.7.
Растяните мышью ограничивающий прямоугольник элемента З'ЬаНс так, что- бы его размеры позволяли ввести нужный текст. Двойным щелчком мыши вызо- вите окно свойств элемента управления (рис. 7.8). Рис. 7.8. Окно Тех1 Ргорегйез Редактор диалога присваивает по умолчанию всем элементам 51а1г1С Тех! иден- тификатор ЮС_5ТАТ1С. Если в ходе выполнения программы не требуется изменять атрибуты элемента управления (например, его местоположение или значение свой- ства СарВоп), то можно оставить значение идентификатора по умолчанию. В про- тивном случае необходимо назначить элементу уникальный идентификатор. Замените идентификатор в поле Ю на значение ЮС_5ТАТ1С_1, так как в ходе работы программы будут изменяться некоторые атрибуты этого элемента. Поле СарВоп содержит текстовую строку, которая будет отображаться внутри ограничивающего прямоугольника элемента З'ЬаНс Тех!. В строке могут использо- ваться управляющие символы \1 (табуляция) и \п (перевод строки). Введите в поле СарВоп текст 01д0ето1. Теперь перейдите на вкладку ЗЕу 1е$ (рис. 7.9). Рис. 7.9. Свойства элемента 81айс Тех1 на вкладке 81у1ез Открывающийся список АПдп 1ех1 позволяет задать выравнивание текста по горизонтали. Можно использовать значение ЕеТЕ (по умолчанию), СепЕег или Ид НЕ Флажок СепЕег уегВсаНу задает центрирование текста по вертикали. Способы вы- равнивания указываются по отношению к ограничивающему прямоугольнику. Флажок Мо ргеЛх влияет на интерпретацию символа &. Если этот флажок уста- новлен, то символ & отображается. В противном случае символ & предшествует подчеркиваемому символу, что позволяет задавать «горячую» клавишу. Флажок Вогдег создает рамку вокруг элемента управления. Выберите значение СепЕег из списка АПдп ±ех± и установите флажки СепЕег уегВсаНу и Вогдег. Закройте окно Тех! РгорегВез. Теперь форма диалога примет вид, показанный на рис. 7.10. Осталось добавить еще две информационные надписи. Для этого надо повто- рить описанные выше действия и поместить два элемента управления 51аВс Тех!
ниже надписи 01д0ето1. Свойства этих элементов определите в соответствии со следующей таблицей: го Сарйоп АНдп 1ех1 Установлены флажки ЮС_5ТАТ1С Уегзюп 1.0 Сеп1ег Сеп1ег уегйсаПу ЮС_5ТАТ1С СорупдН: НпезоГ! Согрогайоп, 2005. Сеп1ег Сеп1ег уегйсаПу После этого диалоговое окно примет примерно такой вид, какой показан на рис. 7.11. Рис. 7.10. Форма диалога после установки элемента ЮС_5ТАТ1С_1 Рис. 7.11. Форма диалога после первичного размещения элементов управления Но что-то в этой форме вызывает смутное раздражение. Попытаемся в этом разобраться... Выравнивание элементов управления на форме диалога В процессе размещения элементов управления часто возникает желание придать этому размещению эстетически приятный вид. Например, хорошо смотрится диа- логовое окно, в котором однотипные элементы имеют одинаковые размеры, хотя бы по вертикали. Также окно радует глаз пользователя, если позиционирование некоторой группы элементов выровнено по положению одного из элементов. Интегрированная среда Ухзпа! 81псйо содержит удобный инструмент для ре- шения таких задач. Когда программист работает с редактором диалоговых окон, в главном меню Ухзпа! 81пс11о появляется пункт 1ауои1, выбор которого вызывает соответствующее подменю. Команды выравнивания из подменю кауои! применя- ются к группе выделенных элементов.
Чтобы выделить группу элементов, нужно нажать клавишу С1:г1 и, удерживая ее в нажатом состоянии, последовательно щелкать мышью на элементах, включа- емых в группу. Следует помнить, что последний элемент, включаемый в группу, считается эталонным элементом. Именно по этому эталонному элементу будут подстраиваться характеристики остальных элементов в группе при выполнении некоторых команд из подменю ЕауоиБ После выделения группы элементов вызывается нужная команда. Например, если необходимо сделать одинаковыми размеры элементов, то выбирается команда Маке 5ате 51/е, а всплывающее каскадное меню предоставляет на выбор варианты М’сИН (по ширине), НетдЫ: (по высоте), ВоНэ (по ширине и высоте одновременно). Для выравнивания позиционирования элементов выбирается команда АПдп. Чаще всего при работе с этой командой используются параметры 1_еТ± (по левой границе) и ИдН! (по правой границе). Команда 5расе Еуеп1у позволяет выровнять промежутки между элементами при размещении по горизонтали (опция Асгозз) или при размещении по вертикали (опция Оомп). Команда Сеп1:ег 1’п О1’а1од перемещает всю группу выделенных эле- ментов, центрируя ее либо по горизонтали (опция Нопноп^а!.), либо по вертикали (опция УегБса1). Команда Аггапде ВиНопз применяется только для кнопок и позволяет разме- щать группу кнопок либо по правому краю, либо по нижнему краю формы диало- гового окна. Воспользуемся инструментом 1_ауои1: для улучшения внешнего вида создавае- мого диалогового окна в приложении 01д0ето1. Применим команду Маке 5ате 5ые, чтобы сделать одинаковой ширину всех элементов 51:а1:1с Тех!, команду АПдп — для позиционирования всех элементов по левому краю надписи 01д0ето1 и команду 5расе Еуеп1у — для выравнивания про- межутков между элементами по вертикали. На рис. 7.12 показан результат этих манипуляций. Рис. 7.12. Форма диалога после выравнивания элементов управления Итак, мы завершили проектирование шаблона диалога. Ради любопытства посмотрим на свойства кнопки ОК, которую нам «подарил» редактор диалога. На рис. 7.13 показано окно свойств этого элемента управления, которое, как обычно, вызывается двойным щелчком мыши. Обратите внимание на включенную отметку флажка ТаЬ $1ор. Благодаря этому элемент управления получает стиль М5_ТАВ5ТОР, означающий, что кнопка ОК мо- жет получать фокус ввода при работе с диалоговым окном через клавиатурный интерфейс. Этот способ общения пользователя с окном мы рассмотрим позже.
РияЬ ВиИоп РгорегНея -Й Т Бепега! | 5(у1ез | Ек1епс1ес15(у1ез | Ю: |Ю0К 3 СарНоп: |Ш \Лз|Ые I- Бгоир I- Не1рЮ I- Б|заЫес1 Г/ ТаЬз1ор Рис. 7.13. Свойства кнопки ОК Если перейти на вкладку ЗЕу 1е$, то можно увидеть, что на ней установлен фла- жок ОеГаиП: ЬиНоп. Он определяет данную кнопку как кнопку, применяемую по умолчанию. Это означает, что когда ни один из элементов управления не имеет фокуса ввода, то клавиатурный ввод переправляется именно этой кнопке. Определение диалоговой процедуры и вызов диалога Реализацию этих этапов рассмотрим на примере исходного кода приложения 01д0ето1. Файл 01д0ето1.срр, входящий в состав проекта, в настоящий момент является точной копией файла МепиОетоЕсрр из листинга 6.1. Текст данного файла нужно отредактировать так, чтобы он соответствовал листингу 7.1. Листинг 7.1. Проект О1дОето1 ////////////////////////////////////////////////////////////////////// // В1дВето1.срр #1пс1ис1е <\л/1 пс1о\л/8. И> #1пс1ис1е <81зс11 о. Н> #1пс1ис1е "ШпО" #1пс1ис1е "гезоыгсе.Н" #с1еТ1 пе 1л1 200 // ширина фигуры #с1еТ1 пе Н 140 // высота фигуры епит ЗНареЗые { МАХ, М1И }; УуребеТ з^гис^ { 1 ггО 1с1_811аре; // идентификатор фигуры В001_ ТВеб; // компонент красного цвета В001_ Тбгееп; // компонент зеленого цвета В001_ ТВ1ие; // компонент синего цвета 1пР 1с1_Ьг1д1тМ // идентификатор яркости цвета } ЗНареОа^а; В001_ САИВАСК АЬоиШдРгосШШВ, 1ИМТ, ПРАВАМ, ОРАВАМ); 1ВЕ51ЛТ САИВАСК ШбРгос(НШВ, ЮТ, АРАРАМ, ОРАВАМ); //========================================== 1пР ШИАР1 ШпМа1п(Ю5ТАМСЕ ЮзТапсе, Н1М5ТАМСЕ Г1Ргеу1пзТапсе, ОРЗТВ 1 рСтсМ пе, 1 ггО пСгткОом) { М56 тзд; КМпб та1п1л1пс1("В1дВетоГ, ЮзТапсе, пСггкОом, 1л1пс1Ргос, МАКЕ1МТВЕ500ВСЕ( 1ВВ_МЕ1\11Ш, 100, 100, 400, 300); \л/М1е (6еТМеззаде(&тзд, МШ_, 0, 0)) { Тгапз1аТеМеззаде(&тзд);
01 зраРсИМеззадеС&тзд); } геРигп тзд.мРагат; } //========================================== 0КЕ300Т САЕЕВАСК 1л1пс1Ргос(ШМ0 Шпс1, 01ИТ иМзд, ОРАВАМ мРагат, ЕРАЕАМ 1Рагат) { зРаР1с Н1МЗТАМСЕ ЫпзР; /* Объявления остальных локальных переменных - из листинга 6.1 */ з\л/1РсИ (иМзд) { сазе ОМ_СВЕАТЕ: ЫпзР = Зе^Моби!еНапсИ е(МОЕЕ); /* Остальной код обработки сообщения ОМ_СВЕАТЕ - из листинга 6.1 */ Ьгеак; сазе 0М_С0ММАМ0: зт’РсЬ (ЕОООВО(мРагат)) { /* код обработки для команд ЮМ_ОРЕМ, ЮМ_СЕ03Е, ... , ЮМ_ВЕ812Е - из листинга 6.1 */ сазе ЮМ_АВ00Т: // Вызов диалога 01а1одВох(ЫпзР, МАКЕIМТЕЕ300ЕСЕ(Ю0_АВ00Т), Шпс1, АЬоиР01дРгос); Ьгеак; с1еРаи1Р: Ьгеак; } 1пуа11с1аРеВесР(Шпс1, ИОЕЕ, ТВОЕ); Ьгеак; сазе 1л/М_РА1МТ: /* Код обработки сообщения ОМ_РА1МТ из листинга 6.1 */ Ьгеак; сазе 0М_0Е8ТВ0У: РозРОип РМеззаде(0); Ьгеак; 0еСаи1Р: геРигп ОеТШлбомРгос(Шпс1, иМзд, мРагагтг, 1Рагат); } геРигп 0; } //====================- II Диалоговая процедура продолжение
Листинг 7.1 {продолжение} ВООЬ САЕЕВАСК АЬоиШдРгос(НШО ИО1д, 1Л1\1Т иМзд, АРАРАМ мРагат, ЬРАРАМ 1Рагат) { зт’ЬсЬ (иМзд) { сазе Ш-1М1Т01АЮ6: геЬигп ТРОЕ; сазе Ш-СОММАШ: змПсЬ (ЮШРО(мРагат)) { сазе ЮОК: сазе ЮСАМСЕЬ: ЕпсЮ1а!од(Ь01д, 0); геЬигп ТРОЕ; } Ьгеак; } геЬигп ГАЕЗЕ; } ////////////////////////////////////////////////////////////////////// Диалоговая процедура Диалоговая процедура АЬоиЮ1дРгос во многом напоминает оконную процедуру. Она должна иметь спецификатор САПВАСК, поскольку вызывается операционной системой. Имя функции может быть произвольным, однако сложилась традиция завершать это имя суффиксом 01дРгос. Функция АЬонШдРгос принимает такой же набор параметров, что и обычная оконная процедура. Но некоторые различия между диалоговой процедурой и окон- ной процедурой все же есть: □ Оконная процедура возвращает значение типа 1_КЕ51ЛТ, а диалоговая процеду- ра — значение типа ВООЬ. □ Если оконная процедура не обрабатывает какое-то сообщение, то она вызыва- ет ОеЙЛ/1пс1о\л/Ргос. Если диалоговая процедура не обрабатывает какое-то сооб- щение, то она возвращает значение ЕА1_8Е. Если же сообщение обрабатывается диалоговой процедурой, то она возвращает значение ТРОЕ. □ Диалоговая процедура не обрабатывает сообщение ММ_СКЕАТЕ. Вместо это- го она выполняет инициализацию при обработке специального сообщения ММ_1М1Т01А1_06. □ Диалоговая процедура обычно не обрабатывает сообщение ММ_РА1МТ, так как все функции диалогового окна реализуются элементами управления. Сообщение УУМ_1МГГО1АЮ6 является первым сообщением, которое получает диа- логовая процедура. Если после обработки этого сообщения процедура возвращает значение ТРОЕ, то \Упк1о\\ъ помещает фокус ввода на первое дочернее окно элемен- та управления, которое имеет стиль \Л/5_ТАВ5ТОР. В разрабатываемом диалоговом окне первым элементом управления со стилем \Л/5_ТАВ5ТОР является кнопка ОК. В то же время при обработке сообщения ММ_1М1Т01А1_06 диалоговая процедура может ис- пользовать функцию 5е1:Еоси$ для того, чтобы установить фокус на какой-то другой элемент управления. Но тогда она должна вернуть значение ЕАЕ8 Е. Блок обработки сообщения ММ_1М1Т01А1_06 является самым удобным местом для инициализации элементов управления, если в этом есть необходимость.
Основным сообщением, обрабатываемым в диалоговой процедуре, является ММ_С0ММАМ0. Напомним, что если источником сообщения ММ_СОММАМО является элемент управления, то младшее слово параметра до Ра га т содержит идентифика- тор элемента управления, старшее слово доРагат содержит код уведомления, а па- раметр 1Рагат — дескриптор элемента управления. В нашей программе диалоговая процедура должна обрабатывать только два сообщения ММ_СОММАМО. Источником первого сообщения является кнопка ОК с идентификатором ЮОК. Сообщение инициируется, когда пользователь щелкает на кнопке мышью или нажимает клавишу пробела или Еп1ег. Источником второго сообщения является кнопка закрытия диалогового окна, находящаяся в правой части его заголовка и имеющая идентификатор ЮСАМ СЕ I. Это сообщение появляется, когда пользователь щелкает мышью на кнопке с кре- стиком или нажимает сочетание клавиш АЙ+ Е4. Обрабатывая оба эти сообщения, диалоговая процедура вызывает функцию ЕпсЮ1’а1од, после чего возвращает значение ТРОЕ. Для всех остальных сообщений диалоговая процедура возвращает значение ЕА1.5Е. Функция ЕпсЮ1’а1од закрывает модальное диалоговое окно. Она имеет следую- щий прототип: В001 ЕпсЮ1а1од ( НДОИО ИО1д, // дескриптор диалогового окна 1ИТ—РТВ пРезиК // значение, возвращаемое из функции 01а1одВох ); Второй параметр функции ЕпсЮ1’а1од задает значение, которое передается функ- ции 01’а1одВох (описываемой ниже) для использования в качестве кода возврата из функции 01’а1одВох. Чаще всего функции ЕпсЮ1’а1од передается в параметре пВезиИ: значение ТВОЕ при обработке команды ЮОК и ЕАЕ5Е — при обработке команды ЮСАМСЕЕ В программе 01д0ето1 код возврата функции 01’а1одВох игнорируется, но на самом деле он может быть использован для проверки выбора, сделанного пользо- вателем. Вызов диалога Вызов диалогового окна в программе должен происходить после выбора пользо- вателем пункта меню АЬоиЕ... Обработкой сообщений ММ_С0ММАМ 0, которые ини- циируются после выбора команд меню, занимается оконная процедура. В случае выбора пункта меню АЬои!:... младшее слово доРагат сообщения ММ_С0ММАМ0 со- держит идентификатор ЮМ_АВ011Т. Оконная процедура программы Мепи0ето1, обрабатывая это сообщение, вы- зывает функцию Ме$$адеВох. Теперь же оконная процедура вызывает функцию 01’а1одВох: 01а1одВох(Н1п8Ц, МАКЕIМТВЕ50ШСЕ(Ю0_АВ0иТ), Шпс1, АЬоиШдРгос); Для вызова функции необходимо использовать дескриптор экземпляра при- ложения, сохраненный при обработке сообщения ММ_СКЕАТЕ, идентификатор шаб- лона диалога, дескриптор родительского окна для окна диалога и адрес диалого- вой процедуры. Функция 01’а1одВох создает и выводит на экран модальное диалоговое окно, построенное по шаблону Ю0_АВ011Т. Кроме того, 01’а1одВох сообщает АУшскудоз, что диалоговая процедура для обслуживания этого окна имеет адрес АЬоиЮ1дРгос.
В своей реализации функция 01’а1одВох использует вызов Сгеа1:е\Л/1’пс1о\л/Ех для создания диалогового окна. После этого она посылает сообщение ММ_1М1Т01А1_06 диалоговой процедуре, отображает диалоговое окно, блокирует (делает недоступ- ным для ввода) родительское окно и запускает цикл обработки сообщений для менеджера диалогового окна. Функция 01’а1одВох не возвращает управление в \Л/пс1 Ргос до тех пор, пока окно диалога не будет закрыто. Когда диалоговая процедура вызывает функцию ЕпсЮ1’а1од, функция 01’а1одВох уничтожает диалоговое окно, завершает работу цикла обработки сообщений для менеджера диалогового окна, деблокирует родительское окно и возвращает зна- чение параметра пВезиЙ, с которым вызывалась функция ЕпсЮ1’а1од. Тестирование приложения Откомпилируйте и запустите программу. Она должна функционировать так же, как и приложение МепиОето1. Выберите пункт меню АЬои!.... Если все было сделано правильно, то появится модальное диалоговое окно, внешний вид которого показан на рис. 7.14. Рис. 7.14. Модальное диалоговое окно АЬосД О1дОето1 Получилось неплохо. Во всяком случае, гораздо привлекательнее, чем обыч- ное окно сообщений, которое отображается в программе МепиОето! при помощи функции МеззадеВох. Но внутренний голос подсказывает, что дизайн диалогового окна можно сде- лать еще более симпатичным.... Вот если бы можно было написать имя приложе- ния более крупным шрифтом? И не только более крупным шрифтом, но и цвет- ными буквами?.. Для реализации столь смелой мечты продолжим наше путешествие по лаби- ринтам АУш32 АР1. Изменение атрибутов элемента управления Во время работы приложения вы можете изменить некоторые из атрибутов эле- мента управления в диалоговом окне. Так, для изменения статуса элемента управ- ления можно вызвать функцию ЕпаЫе\Л/1’пс1о№, которая имеет следующий прототип: В001 ЕпаЫеШпс1ом( Ш Шпс1, // дескриптор элемента управления ВООЕ ЬЕпаЫе // флаг разрешения или запрещения ввода от клавиатуры и мыши };
Второй параметр функции задает новый статус элемента управления. Если он имеет значение ТРОЕ, то элемент становится разрешенным. Если параметру пере- дается значение ЕА1_8Е, то элемент становится запрещенным. Также вы можете изменить следующие атрибуты элемента управления: □ текст, связанный с элементом управления; □ используемый шрифт; □ цветовые атрибуты (только для элементов Тех! и ЕсНЕ Вох). Текст (атрибут СарНоп) Текст, связанный с элементом управления, можно изменить при помощи функ- ции 5е1М1’пс1одоТех1:. Первому параметру функции передается дескриптор элемента управления, второму параметру — указатель на символьную строку или строко- вая константа. Напомним, что дескриптор элемента управления Н\Л/пдСопЕгоI в диалоговом окне Н0[д можно получить по его идентификатору Ю, вызвав функцию ОеЮфИет: ШпбСопбго! = 6еШ д Кет( И01 д, 10); Используемый шрифт В элементах управления используется системный шрифт по умолчанию. Обра- зец такого шрифта представлен на рис. 7.14. Обычно надписи (51а1:1’с Тех1) применяются в качестве текстовых меток для других элементов управления, и в этом случае системный шрифт хорошо сочета- ется с другими элементами диалогового окна. Но иногда все-таки желательно из- менить шрифт элемента управления. Например, когда статический текст исполь- зуется как информационная надпись. Для изменения шрифта элемента управления необходимо послать сообщение ММ_$ЕТЕОМТ окну этого элемента. Как известно, приложение может посылать со- общения любому окну при помощи функции 5епс1Ме$$аде. Интерпретация пара- метров функции 5епс1Ме$$аде зависит от кода сообщения. В данном случае функ- ция вызывается со следующими параметрами: ЗепбМеззаде ( (Н1л1М0) Шпб, // дескриптор окна-получателя сообщения Ш_8ЕТЕ0МТ, // код сообщения (ЫРАЕАМ) мРагат; // дескриптор назначаемого шрифта (ЬРАЕАМ) 1Рагат; // опция перерисовки элемента управления ); Если параметр доРагат равен МОП, то элемент управления будет использовать системный шрифт по умолчанию. Значение ТРОЕ для параметра [Рагат заставляет элемент управления немедленно перерисовать себя. Цветовые атрибуты Элементы управления 51а1:1’с Тех1 и Есй! Вох, используемые в режиме «только для чтения» (геас!-оп1у), посылают своему родительскому окну сообщение ММ_СТ1_СО1_ОК5ТАТ1С перед тем, как они должны быть перерисованы. В этом сообщении параметр доРагат содержит дескриптор контекста устрой- ства элемента управления, а параметр [Рагат — дескриптор самого элемента уп- равления. Для использования этих дескрипторов в программе значение доРагат следует преобразовать к типу НОС, а значение [Рагат — к типу Н\Л/М0.
Обрабатывая сообщение ММ_СТ1СО1ОК5ТАТ1С, родительское окно может исполь- зовать полученный дескриптор контекста устройства, чтобы изменить цветовые атрибуты элемента управления. К таким атрибутам относятся цвет текста, цвет фона графических элементов и режим смешивания фона. Если приложение обрабатывает сообщение ММ_СТ1СО1ОК5ТАТ1С, то возвращае- мым значением должен быть дескриптор кисти типа НВК115Н, который система ис- пользует для заполнения фона окна элемента управления. Пример модификации атрибутов элемента управления Применим новые знания на практике. Доработаем приложение 01д0ето1, чтобы улучшить дизайн модального диалогового окна. Для этого в листинге 7.1 нужно заменить определение функции АЬоиЮфРгос следующим кодом: /======================= II Диалоговая процедура В001_ САЕЕВАСК АЬоыШдРгосШШВ Ю1д, 1Л1\1Т иМзд, ПРАВАМ мРагат, ЕРАВАМ 1Рагат) { зЬаЫс НШО Ь8ЬаЬ1с1; 8'Ьа'Ыс СОСТОИТ 1Г; // обязательно збаЫ с для инициализации полей нулями! НЕОЕ1Т НГоггЫ; 8\л/ПсИ (иМзд) { сазе ШДМ1Т01АЮ6: ЬЗЬаЫс! = СеЬ01д1Ьет(Ь01д, ЮС_8ТАТ1С_1); // Создание логического шрифта \/егс1апа 1 Г. 1 ГНеп дГД = 28; 1збгсру( (1РЗТВ)&1Г.1СЕасеИате, ’Уегбапа" ); ИЕопН = СгеаЬеЕопЫпс11гесЬ(&1 Г); // Модификация шрифта для элемента Ь8ЬаЬ1с1 ЗепбМеззадеСЬЗЬаЫс!, Ш_8ЕТЕ0ИТ, (ПРАВАМ)НЕопШ, ТРОЕ ); гебигп ТРОЕ; сазе 1л/М_СТЕС0Е0Е8ТАТ1 С: // Модификация цветовых атрибутов элемента И8ЬаЫс1 1Г ((НШ0)1 Рагат == Ь8ЬаЬ1с1) { 8еЬТехЬСо1ог((НОС)мРагат, Р6ВС16О, 0, 0)); ЗеЦВкМобе((НОС)мРапат, ТРАИЗРАРЕИТ); гебигп (01лЮЕВ)6ебЗузСо1 огВгызб (С0Е0Е_ЗВЕАСЕ); } Ьгеак; сазе 1л!М_СОММАИО: з\л/1ТсИ (ЬОШЕВЫРагат)) { сазе ЮОК: сазе ЮСАИСЕЕ: Ве1ебеОЬцесЬ(ЬЕопб!); ЕпсЮпа!од(601д, 0): гебыгп ТВОЕ; } Ьгеак; } гебыгп ЕАЕЗЕ; } //////////////////////////////////IIIIIIIIIIIIIIIIIIIIIIIIIIIIII//////
Модификация шрифта для элемента управления ЮС_5ТАТ1С_1 осуществляется в блоке обработки сообщения ММ_1М1Т01А1_06, а модификация цветовых атрибу- тов — в блоке обработки сообщения ММ_СТ1СО1_ОК5ТАТ1С. Если при обработке сообщения ММ_СТ1СО1ОК5ТАТ1С выясняется, что оно посту- пило от элемента управления с дескриптором ЬЗ^ай с1, то программа устанавлива- ет темно-красный цвет для вывода текста и возвращает дескриптор системной кисти, имеющей системный цвет С01_0В_30ЕАСЕ. Возвращаемое значение позволя- ет сохранить обычный фон элемента управления З^айс Тех!, сливающийся с фо- ном диалогового окна. Если бы было необходимо сделать иным фон окна эле- мента управления, то нужно было бы создать кисть (НВК115Н) желаемого цвета и использовать для возврата ее дескриптор. Модифицированное приложение 01д0ето1 будет выводить диалоговое окно АЬои! 01д0ето1, показанное на рис. 7.15. Рис. 7.15. Модальное диалоговое окно приложения О1дОето1 Использование других элементов управления Приложение 01д0ето1 демонстрирует использование в диалоговом окне элемен- тов управления Р1с1:иге и 51:а1:1’с Тех1. Эти элементы реализуются в системе как до- черние окна предопределенного оконного класса 5ТАТ1С. Следующая группа элементов управления, которая будет рассматриваться в этой главе, включает в себя кнопку (Вийоп), флажок (СНеск Ьох), переключатель (Васйо Ьийоп) и групповую рамку (бгоир Ьох). Все эти элементы реализуются как дочерние окна предопределенного оконного класса В11ТТОМ. Кнопки Кнопка (Вийоп), называемая иногда «нажимаемой кнопкой» (ри$Н ЪиЫоп), пред- ставляет собой прямоугольник, внутри которого обычно располагается некото- рый текст. Щелчок мышью на кнопке заставляет ее перерисовать себя, используя стиль ЗВ с тенью, чтобы выглядеть «нажатой». Отпускание кнопки мыши восста- навливает начальный облик нажимаемой кнопки, а родительскому окну посыла- ется сообщение ММ_С0ММАМ0 с кодом уведомления ВМ_С1_1СКЕО. \Упк1о\\ъ заботится о надлежащем поведении и внешнем облике кнопок. Впро- чем, то же самое можно сказать о флажках и переключателях, которые тоже явля- ются особыми разновидностями кнопок. Если кнопка имеет фокус ввода, то текст
обводится штриховой линией, а нажатие и отпускание клавиши пробела имеет тот же эффект, что и щелчок мышью. Так утверждается в справочных материалах М8ВК К сожалению, выделение текста штриховой линией для кнопки с фоку- сом ввода четко срабатывает только при использовании клавиатурного интерфей- са, когда перевод фокуса ввода осуществляется при помощи клавиши ТаЬ. Когда же приложение отслеживает положение курсора мыши самостоятельно и вызы- вает функцию 5е1Еоси$ для перевода фокуса ввода на соответствующий элемент, то никакого выделения текста почему-то не происходит. В приложении 01д0ето2, рассматриваемом ниже, показан вариант решения этой проблемы с помощью вы- зова функции ОгадоЕосизВес!:. Кнопки ВиНоп используются в основном для немедленного выполнения дей- ствия, без сохранения какой-либо индикации положения кнопки «включено/вык- лючено». В приложении 01д0ето1 уже использовалась кнопка ОК, которая находилась в исходной форме диалога, предложенной редактором диалоговых окон. Но вы можете добавлять на форму диалога и другие кнопки, выполняющие те или иные функции пользовательского интерфейса. Элемент управления «кнопка» размещается на форме диалога так же, как и другие элементы управления, — с помощью мыши, с предварительным выделе- нием элемента ВиНоп на панели инструментов Соп1го1з. Затем надо вызвать окно свойств РизН ВиНоп РгореФ’ез и на вкладке 6епега1 (см. рис. 7.13) в текстовом поле Ю задать идентификатор кнопки, а в поле СарВоп указать текст, который будет отображаться на кнопке. СОВЕТ------------------------------------------------------------------------- Редактор диалоговых окон предлагает в качестве идентификаторов для кнопок Вийоп имена 1ОС_В1ЛТОМ1, ЮС_В11ТТОЫ2 и им подобные. Рекомендуется заменять их именами, отражающими функциональное назначение кнопок. Этот совет относится и к определению идентификаторов других элементов управления. На вкладке 51у1е$ (рис. 7.16) можно задать дополнительные свойства кнопки. Рис. 7.16. Вкладка 81у1ез окна свойств РизН Вийоп Ргорегйез □ Флажок ОеГа и И ЬиНоп назначает кнопке атрибут «применяемая по умолчанию». □ Флажок Омпег сйам применяется для создания кнопок, за прорисовку которых отвечает окно-владелец. Иногда кнопки этого стиля называют кнопками, опре- деляемыми программистом. □ Флажки 1соп или Вйтар позволяют указать, что вместо текста будет отобра- жаться пиктограмма или растровый образ.
□ Флажок М и Ипе используется, если текст слишком длинный, чтобы уместиться на кнопке в одну строку. □ Флажок Е1а1 создает плоскую кнопку. □ Опции Нопноп^а!. аИдптеп! и УеФ’са1 аИдптеп! позволяют выбрать вариант вы- равнивания текста внутри ограничивающего прямоугольника. □ Чаще всего значения свойств на вкладке 81у 1е$ оставляют по умолчанию, то есть в том состоянии, которое показано на рис. 7.16. Наиболее сложны в реализации кнопки, определяемые программистом (Омпег сйам), поэтому этот вопрос стоит рассмотреть отдельно. Кнопка О\л/пег с!галлл Если при задании свойств кнопки был установлен флажок Омпег с1 гам, то в опреде- лении кнопки в файле описания ресурсов появится стиль В$_ОММЕКОКАМ. Кнопка стиля В5_0МИЕК0КАМ отличается от обычной кнопки тем, что при необходимости перерисовки она посылает своему родительскому окну сообщение ММ_0КАМ1ТЕМ. Это происходит при первоначальном создании кнопки, при ее нажатии или от- пускании, при получении или потере фокуса ввода и во всех других случаях, ког- да требуется перерисовка элемента управления. Когда приложение получает сообщение ММ_0КАМ1ТЕМ, параметр доРагат этого сообщения содержит идентификатор элемента управления, а параметр [Рагат яв- ляется указателем на структуру типа 0КАМПЕМ5ТК11СТ. Сообщение ММ_ОКАМПЕМ может поступать не только от кнопок со стилем Омпег дгахл/, но и от элементов уп- равления СогпЬо Ьох, Из! Ьох и Из! У1’ем, если они имеют аналогичный стиль. Структура 0КАМПЕМ5ТК11СТ определена следующим образом: УуребеГ { 1ЛМТ СШуре; 1ЛМТ СШО; 1ЛМТ ПетЮ; ЩИТ ПетАШоп; ЩИТ 1 РепМа^е; НШО ЫпсПРет; НОС НОС; ОЕСТ гсТРет; 0Е0М6_РТ0 ПетОаРа; } 0РАШТЕМ8ТР0СТ; Поле СИТуре позволяет указывать тип элемента управления. Возможные зна- чения этого поля приведены в табл. 7.2. Таблица 7.2. Возможные значения поля СИТуре Значение поля СНТуре Тип элемента управления ООТ-ВСГГТОЫ ООТ-СОМВОВОХ ООТ-Ы8ТВОХ ООТ_1_18ТУ1Е\Л/ ООТ_МЕ1\111 СЮТ_8ТАТ1С СЮТТАВ Кнопка со стилем О\л/пег с!га\л/ СотЬо Ьох со стилем О\л/пег с!га\л/ □51: Ьох со стилем О\л/пег с!га\л/ 1151: соп1:го1 со стилем О\л/пег с!га\л/ Пункт меню со стилем О\л/пег с!га\л/ 51айс соп1:го1 со стилем О\л/пег с!га\л/ ТаЬ соп1:го1 со стилем О\л/пег с!га\л/
В поле СИЮ передается идентификатор элемента управления. Оно не исполь- зуется, если источником сообщения является пункт меню. В поле ИетЮ передается идентификатор пункта меню или индекс строки в элементе управления СогпЬо Ьох или Из! Ьох. Поле йетАсИоп определяет тип действия, связанного с элементом управления. Его значением может быть одна из констант, указанных в табл. 7.3. Таблица 7.3. Возможные значения поля кетАсйоп Значение кегпАсбоп Интерпретация ООА_ОНА\Л/ЕЫТ1кЕ ООА-РОСС18 Элемент управления требует перерисовки Элемент управления получил или потерял фокус ввода. Текущее состояние можно определить, проверяя флаг ОО8_ЕОС118 в поле |1ет81:а1:е ООА_8Е1_ЕСТ Элемент управления получил или потерял статус выбранного (нажатого) элемента. Текущее состояние можно определить, проверяя флаг ОО8_8Е1_ЕСТЕО в поле |1ет81:а1:е Поле НетЗ'Ьа'Ье определяет текущее визуальное состояние элемента управления в виде комбинации битовых флагов. Применение флагов 005_Е0С115 и 005_5Е1_ЕСТЕ0 поясняется в табл. 7.3. Информацию о других флагах можно найти в справочных материалах М8ВК Поле НОС содержит дескриптор контекста устройства для дочернего окна эле- мента управления. Этот дескриптор нужно использовать во всех функциях рисо- вания. Поле гсИет содержит размеры прямоугольника, ограничивающего элемент управления. Прямоугольник определен в контексте устройства с дескриптором НОС. Обрабатывая сообщение ММ_ О КАМНЕМ, приложение должно вызвать функции рисования в контексте устройства НОС, чтобы обеспечить требуемый вид кнопки. Чаще всего для этого используются растровые образы или просто выводится не- обходимый текст. Пример использования кнопки Ошпег с1гаш Технику применения кнопки со стилем Омпег дгахл/ покажем на примере модифи- кации приложения 01д0ето1. В новой версии программы, названной 01д0ето2, диалоговое окно АЬои1... должно стать еще более привлекательным за счет совме- щения картинки и кнопки ОК. Создайте новый проект с именем 01д0ето2. Скопируйте из папки проекта 01д0ето1 (см. листинг 7.1) в папку проекта 01д0ето2 файлы с расширениями .срр, .К и .гс, скорректировав их имена заменой 01д0ето1 на 01д0ето2. Добавьте эти фай- лы в состав проекта. В файле 01д0ето2.срр все вхождения подстроки 01д0ето1 за- мените подстрокой 01д0ето2. Скопируйте также в папку проекта файл ВиНегйу.Ьтр, заменив его имя на В^пАсВ’уе.Ьтр. Этот растровый образ будет использоваться для изображения ак- тивной кнопки, то есть кнопки, имеющей фокус ввода. Создайте при помощи ка- кого-либо графического редактора еще два варианта растрового изображения. Файл В^пМоАсВ’уе.Ьтр должен содержать изображение пассивной кнопки в серой гамме цветов, а файл В1пРге$$.Ьтр будет хранить изображение нажатой кнопки
с более темным фоном1. Кнопка становится пассивной, когда она теряет фокус ввода. В нажатое состояние кнопка переходит либо при щелчке мышью, либо, если кнопка имеет фокус ввода, при нажатии клавиши пробела. Доработку программы начнем с модификации шаблона диалога Ю0_АВ011Т. Для этого надо перейти на вкладку Резо и гее \/1е\л/ в окне Могкзрасе и дважды щелкнуть мышью на элементе Ю0_АВ011Т. В результате будет вызван редактор диалоговых окон с шаблоном диалога, внешний вид которого показан на рис. 7.12. Откройте окно свойств диалогового окна Ю0_АВ011Т и замените текст АЬои! 01д0ето1 строкой АЬои! 01д0ето2. Затем откройте окно свойств элемента ЮС_5ТАТ1С_1 и замените текст 01д0ето1 строкой 01д0ето2. Удалите кнопку ОК и рисунок с бабочкой. Выделите группу надписей, как по- казано на рис. 7.12, и, используя клавиши-стрелки, сместите эту группу немного правее. Увеличьте высоту элемента 01д0ето2, а остальные две надписи передвиньте к нижней границе окна. На месте удаленного рисунка поместите элемент управления ВиНоп и задайте его размеры, как показано на рис. 7.17. Теперь вызовите окно свойств кнопки и укажите идентификатор ЮОК, а в поле СарВоп удалите весь текст. Затем перей- дите на вкладку 51у1е$ и установите флажок Одопег дгахл/. Ниже кнопки ЮОК поместите еще одну кнопку стандартного размера. В окне свойств кнопки задайте идентификатор Ю_АВО11Т_НЕ1_Р, а в поле СарВоп введите строку Не1р. После всех указанных манипуляций шаблон диалога примет вид, по- казанный на рис. 7.17. Рис. 7.17. Модифицированный шаблон диалога Теперь обеспечим необходимые растровые ресурсы для программы. Перейди- те опять на вкладку Кезоигсе \йем в окне Могкзрасе. Откройте папку Вйтар и уда- лите элемент ЮВ_В11ТТЕВЕ1У. Добавьте к проекту три растровых ресурса в режиме 1трог1, назначив им следующие идентификаторы: Идентификатор Файл ЮВ_Р1СТ_0 ВЩМоАсйуе.Ьтр ЮВ_Р1СТ_1 ВЩАсйуе.Ьтр ЮВ_Р1СТ_2 ВЩРгезз.Ьтр 1 Вы можете взять готовые файлы с этими изображениями, если скачаете файлы к данной книге с сайта издательства ННр://уууууу.рйег.сот.
Осталось внести необходимые изменения в код программы. Нет нужды при- водить полный текст файла 01д0ето2.срр, поскольку изменения касаются только функции АЬоиЮфРгос. В листинге 7.2 содержится ее новая редакция. Листинг 7.2. Диалоговая процедура АЬоиЮ)1дРгос в файле О1дОето2.срр /============================= В001_ САЕЕВАСК АбоыШдРгосШШО НО1д, 1Л1\1Т иМзд, ПРАВАМ мРагат, ЕРАВАМ 1Рагат) { Н1М5ТАМСЕ Н1П8Ц; збаЮс НШО НЗТаТ1с1; 8'Ьа'Ыс Е06Е0МТ 15; // обязательно збаб1 с для инициализации полей нулями! збаб1 с НЕОК1Т НЕогД1, НЕопб2; 8-ЕаШс НВ1ТМАР НРЮТО; // изображение пассивной кнопки збаб1с НВ1ТМАР 6Р1сТ1; // изображение активной кнопки 8-ЕаЦпс НВ1ТМАР НР1сб2; // изображение нажатой кнопки збаЮс НВВОЗН НВгизНО; // фон пассивной кнопки збаб1с НВВОЗН НВгизН!; // фон активной кнопки збаб1с НВВОЗН НВгизН2; // фон нажатой кнопки збаб1с НВВОЗН НСигВгизН; // текущий фон збаб1с НОС НМетОС; // контекст устройства в памяти збаЮс В1ТМАР Ьт; // параметры растра ЕР0ВАИ1ТЕМЗТВ0СТ рсИз; 8баЮс НШО НВбпОк, НВбпНе1р; збаб!с ВЕСТ гсВбпНе1р, гесб; Ш хО, уО = 0; 8\л/ПсИ (иМзд) { сазе ИМ_Ю1Т01АЕ06: Н1П8Ц = 6еЦМоби1 еНапсИ е(ШЕЕ); НЗ-ЕаШс! = 6еШд1бет(Н01д, IВС_ЗТАТ1 С_1); НВЦпОк = СМ01дШепп(601 д, ЮОК); ЖпНе1р = СМ01дЮет(Н01д, Ю_АВООТ_НЕЕР); СМИ1пбомВесШВбпНе1р, йгсВбпНе! р); // Создание логического шрифта \/егбапа 1Т.1ТНепдГД = 28; 1збгсру( (ЕРЗТВ)&1 Т. 1 ТЕасеИате, "\/егс1апа" ); НЕопН = СгеабеЕогД1пс11гесб(&1 Г); 1Т.1ТНепдГД = 20; НЕопТ2 = СгеабеЕогД1пс11гесб(&1 Г); // Модификация шрифта для элемента НЗбаЮс! ЗепбМеззадеЮЗШЮ!, ИМ_ЗЕТЕ0МТ, (ПРАВАМ)6ВопН, ТВОЕ ); // Инициализация растровых изображений кнопки ЮОК НРЮТО = ЕоабВп Татар(ВIпзЦ, МАКЕIИТВЕ300ВСЕ(IОВ_РIСТ_0)) НРпсШ = ЕоабВпТатар(ВIпзЦ, МАКЕIИТВЕ300ВСЕ(IОВ_РIСТ_1)) НР1сб2 = ЕоабВпТатар(ВIпзЦ, МАКЕIИТВЕ300ВСЕ(ЮВ_Р1СТ_2)) НВгизНО = СгеабеЗо!1бВгизН(В0В(214, 214, 214)); НВгизЮ = СгеабеЗо!пбВсизН(ВЗВ(149, 242, 242)); НВгизН2 = СгеабеЗо!10ВгизН(ВбВ(83, 52, 254)); СМОЬцесб(НР1 сТО, зЮеоТ(бт), (ЕРЗТВ)&Ьт); ЗеТЕоси8(1эВТпНе1р);
гебигп ГА15Е; // чтобы фокус ввода остался на ЬВЬпНе!р сазе 1л/М_СТЕС0Е0РЗТАТ1С: // Модификация цветовых атрибутов элемента И8ЬаЬ1с1 1Г ((НШО) 1 Рагат == Ь8ЬаЫс1) { 8еЬТехЬ0о1ог((НОС)мРагат, РОВ(160, 0, 0)); 8еЬВкМос1е( (НОС)мРагат, ТЕАИЗРАРЕМТ); гебигп (0ШВ0)беЬ8у8Со1огВгизЬ(С0Е0В_30ЕАСЕ); } Ьгеак; сазе 1л1М_ВРАШТЕМ: // сообщение, используемое для // перерисовки кнопки Омпег Огам рсИз = (ЕРОРАО1ТЕМ8ТЕ0СТ)1 Рагагп; ЬМетОС = СгеаЬеСотраЬ1ЫеВС(рсНз->ЬВС); // Проверяем, какие события произошли змПсЬ (рЫз->1ЬетАсЫоп) { сазе ОВА-ЕОСЬЗ: 1Г (рсПз->Пет8Ьабе & 005_Е0СЫ5) { 8е1есЬ0ЬцесШМет0С, ИР1сЫ); ЬСигВгизЬ = ЬВгизЫ; } е!зе { 8е1есЬ0ЬцесШМет0С, ЬР1сЬ0); ЬСигВгизЬ = ЬВгизЬО; } Ьгеак; сазе 0ВА-5ЕЕЕСТ: 1Г (рсПз->Пет8Ше & 0ВЗ_ЗЕЕЕСТЕВ) { 8е1есЬ0ЬцесШМет0С, ЬР1сЬ2); ЬСигВгизЬ = ЬВгизЬ2; } Ьгеак; ЬеГаиИ: 8е1есЬ0ЬцесШМет0С, ИР1сЬ0); ЬСигВгизЬ = ЬВгизЬО; } // епб оГ 8\л/1ЬсИ // Вывод изображения кнопки с учетом произошедших событий Е111 РесЬ(рс118->Ь0С, &рс118->гс1Ьет, ЬСыгВгизЮ; хО = ((рсПз->гс1Ьет.г1дЬЬ - рсПз->гс1Ьет. 1еЮ - Ьт.ЬтШсЕб) / // Картинка ... ВПВИ(рЫз->ЬОС, хО, уО, Ьт.Ьт1л11бЫэ, Ьт.ЬтНе1дИЬ, ЬМетОС, 0, 0, ЗЕССОРУ); // а теперь текст 3е1 есШэцесб(рс11 з->ЬВС, ЬЕопЬ2); 8еШМос1е(рсПз->ЬОС, ТЕАИЗРАРЕИТ); 8еГГехЬСо1ог(рсИз->Ь0С, Р6В(255, 255, 255)); ТехЬСШ(рс11з->ЬВС, 32, 74, "ОК", 2); 8еГГехЬСо1ог(рсИз->Ь0С, Р6В(100, 100, 100)); ТехЬ0иЬ(рЫз->Ь0С, 30, 72, "ОК", 2); // рамка вокруг кнопки ЕгатеРесЬ(рс11 з->ЬОС, &рс11 з->гс1Ьет, (НВР08Н)ОеШоскОЬ0есб(ВЕАСК_ВРОЗН)); 0е1еСеОС(ИМетОС); // !!! не забудьте удалить ЬМетОС 2; продолжение
Листинг 7.2 {продолжение} гебыгп ТЕСЕ; сазе Ш_8ЕТСЬЕ80Е: // отслеживание фокуса ввода для курсора мыши 1Г ((НЮ Магат == ЬВЬпОк) 1Г (беЬЕосызО != ЬВЬпОк) 8еЬЕосиз(ЬВЬп0к); 1Г ((НЮМагат == ЖпНе1р) { 1Г (ЗеЬЕосизО != ЖпНе1р) { 8еЬЕосиз(ЬВЬпНе1р); // Рисуем пунктирную рамку вызовом ВгамЕосызВесЬ 8еЬКесЬ(&гесЬ, 3, 3, гсВЬпНе1р. г1дИЬ - гсВЬпНе1р. 1еГЬ - 3, гсВЬпНе! р. боШип - гсВЬпНе1р.Ьор - 3); ВгамЕосизРесШеШ(ЖпНе1р), &гесб); } } гебигп ГАЕ8Е; сазе Ш-СОММАМВ: змЮсб (ЬОМЖВ Магат)) { сазе Ю-АВОВТ-НЕЕР: МеззадеВохСШд, "В этом диалоговом окне работает кнопка 'Ошег бгам'", "НЕЕР", МВ_ОК); гебигп ТРЕЕ; сазе ЮОК: сазе ЮСАМСЕЬ: Ве1ебеОЬдесЬ(ЬЕопЬ!); Ве1ебеОЬдесШЕопЬ2); ЕпсЮпа!од(ЬВ1д, 0): гебигп ТРОЕ; Ьгеак; гебигп ЕАЕЗЕ; } ////////////////////////////////////////////////////////////////////// Обратите внимание на обработку сообщений ММ_0КАМТЕМ и ММ_$ЕТС11К50К. При обработке сообщения ММ_0КАМТЕМ на поверхности кнопки ЮОК отобра- жается растровое изображение, соответствующее текущему состоянию кнопки. Для этого в контекст устройства в памяти Н Мет ОС выбирается один из дескрипто- ров растров, НРкЮ, НР1с1:1 или НР1с1:2. Отображение осуществляется позже вызо- вом функции ВЙВЙ. Но сначала поверхность кнопки заполняется цветом фона при помощи функции ННКес! с использованием текущей кисти НСитВтизН. Цвет кисти должен совпадать с цветом фона используемой картинки. В этом случае картинка плавно вписывается в поверхность кнопки, и пользователь не заметит, что рису- нок меньше, чем кнопка. После отображения растра выводится текст ОК с приме- нением более красивого шрифта, чем системный шрифт. В приложении приходится решать еще одну проблему. Дело в том, что кнопка ЮОК должна изменять свой облик в зависимости от того, имеет она фокус ввода или нет. Если пользователь общается с приложением через клавиатурный интер- фейс, то никаких проблем с управлением фокусом ввода не будет. При нажатии клавиши ТаЬ фокус автоматически переходит от одного элемента управления со стилем М8_ТАВ8Т0Р к другому элементу, имеющему такой же стиль.
Хотелось бы сделать так, чтобы при использовании мыши фокус ввода также переходил автоматически с одной кнопки на другую, как только курсор мыши наводится на эту кнопку. К сожалению, \У1пс1о\\ъ не обеспечивает такого мыши- ного интерфейса, поэтому в программе эта проблема решается при помощи обра- ботки сообщения ММ_$ЕТС11К5ОК. Сообщение ДОМ_$ЕТСиК5ОК посылается активному окну приложения при лю- бом перемещении курсора мыши. В нашей программе положение курсора мыши отслеживается только при появлении на экране диалогового окна Ю0_АВ011Т. Ког- да функция АЬоиЮфРгос получает сообщение ММ_$ЕТС11К5ОК, его параметр доРагат содержит дескриптор того окна, в котором в данный момент находится курсор. Так как дескрипторы кнопок НВ1пОк и НВ1пНеф известны, то не составляет труда зафиксировать момент попадания курсора на ту или иную кнопку и вызвать фун- кцию 5е1Еоси$, если кнопка в этот момент не имеет фокуса ввода. Для обычных кнопок, не имеющих стиля В$_ОММЕКОКАМ, при получении фоку- са ввода вызывается функция ОгамЕосизКес!:, чтобы нарисовать штриховую рамку вокруг текста кнопки. На рис. 7.18 показан вид диалогового окна АЬои! 01д0ето2 с пассивным состо- янием кнопки ОК. Рис. 7.18. Диалоговое окно АЬоЩ О1дОето2 Если вы хотите увидеть кнопку ОК во всех трех состояниях, да еще и в цветном исполнении, вам придется откомпилировать проект 01д0ето2 и запустить програм- му на вашем компьютере. Флажки Флажок (СНеск Ьох) представляет собой маленькое квадратное окно с сопроводи- тельным текстом, который обычно размещается справа от этого окна. Флажок действует как двухпозиционный переключатель. Один щелчок вызывает появле- ние контрольной отметки (галочки), а другой щелчок приводит к ее исчезнове- нию. Соответствующие состояния элемента управления определяют также с по- мощью терминов «флажок установлен» и «флажок сброшен». Этот элемент управления уже не раз использовался при работе со средой Ухзпа! 81псйо. Например, свойства элементов управления в соответствующих окнах за- давались как раз с помощью флажков. Сам флажок тоже обладает набором свойств, и для их определения вызыва- ется окно свойств СНеск Вох РгореФ’ез. На рис. 7.19 показана вкладка 51у1е$ в этом окне.
Рис. 7.19. Вкладка 81у1ез в окне свойств флажка Флажок Аи1о установлен по умолчанию. Это очень важное свойство. Оно зас- тавляет элемент управления отслеживать все щелчки мышью, и при этом элемент управления сам включает или выключает контрольную отметку. Поэтому вы мо- жете об этом не беспокоиться. Если же выключить свойство Аи1:о, то программа должна при обработке сообщения ММ_С0ММАМ0, поступающего от флажка, вызы- вать функцию СНескОфВиНоп для установки или снятия отметки. Флажок 1_еТ± ±ех1: ставится, если вы хотите поместить сопровождающий текст слева от квадратного окошка. Свойство Тп-$1:а1:е используется для создания флажка, имеющего три состоя- ния. Кроме состояний «установлен» и «сброшен» добавляется «неопределенное состояние», в котором флажок отображен в серой гамме. Серый цвет показывает пользователю, что выбор флажка не определен или не имеет отношения к теку- щей операции. Свойство РизН-Пке изменяет внешний вид флажка так, что он выглядит как нажимаемая кнопка. Вместо установки галочки эта кнопка переходит в нажатое состояние и остается в нем до следующего щелчка мышью. Флажок На! применяется, если нужно использовать плоское изображение квад- ратного окошка. Флажок 1соп или Вйтар используется, чтобы вместо текста рядом с окошком поместить какой-нибудь рисунок. Но так делают очень редко, поэтому здесь эти параметры не будут рассматриваться детально. Несмотря на то что с включенным свойством АиЕо флажок сам управляет уста- новкой и снятием отметки, все же при первом появлении диалогового окна на экране иногда требуется программная инициализация состояния элемента управ- ления. Для этого используется функция СНескОфВиНоп: В001 СИеск01дВиНоп ( НШО Шд, // дескриптор диалогового окна 1гтк пЮВиНоп, // идентификатор кнопки ЩИТ ыСНеск // состояние отметки ); Параметр иСНеск может принимать одно из значений, указанных в табл. 7.4. Таблица 7.4. Возможные значения параметра иСИеск Константа Шестнадцатеричное значение Интерпретация В8Т_С11\1СНЕСКЕО В8Т.СНЕСКЕО В8Т_1ЫОЕТЕНМ1ЫАТЕ 0x0000 Снять отметку 0x0001 Установить отметку 0x0002 Установить неопределенное состоя- ние (з1а1е 1о дгауес!). Используется только для кнопок, имеющих свойство Тп-51а1е
В окнах диалога элементы управления «флажок» используются обычно для выбора независимых параметров. Переключатели Переключатель (КасИо Ьийоп) похож на флажок, но имеет круглую форму, а не квад- ратную. Жирная точка внутри кружка показывает, что переключатель включен. В диалоговых окнах группы переключателей, как правило, используются для выбора взаимоисключающих опций. В отличие от флажков, если повторно щелк- нуть на переключателе, его состояние не изменится. Устанавливая свойства переключателя в окне свойств Касйо ВиНоп РгорегВез, проследите, чтобы остался установленным флажок Аи1о, обеспечивающий авто- матическую установку и снятие отметки для переключателя. Если флажок АиЕо снят, то программа должна при обработке сообщения ММ_С0ММАМ0, поступающе- го от переключателя, вызвать функцию СНескКасйоВиШэп для установки или сня- тия отметки. Кроме того, для первого переключателя в группе связанных взаимоисключаю- щих переключателей нужно обязательно установить флажок бгоир на вкладке бепега1. Все последующие переключатели (в файле описания ресурсов) со сбро- шенным флажком бгоир считаются принадлежащими к этой группе. Если в пос- ледовательности описаний элементов управления встречается переключатель с установленным флажком бгоир, считается, что он начинает новую группу эле- ментов Васйо ЬиНоп. Для группировки флажков или переключателей важен порядок их описания в файле описания ресурсов. Не забывайте, что определения элементов заносятся в файл описания ресурсов по мере размещения их на форме диалога. Впрочем, среда Ухзпа! Збпсйо позволяет легко изменить этот порядок (этот вопрос рассмат- ривается ниже в разделе «Клавиатурный интерфейс и порядок обхода элементов управления»). Хотя с установленным флажком АиЕо переключатель сам управляет установ- кой и снятием отметки, при первом появлении диалогового окна может потребо- ваться программная инициализация состояния переключателя с помощью функ- ции СНескКасйоВиШэп: ВООЬ СбескВасНоВиНоп ( НШВ ВВ1д, // дескриптор диалогового окна 1пб п1ВГ1гзбВиббоп, // идентификатор первой кнопки в группе 1пб пШазбВиббоп, // идентификатор последней кнопки в группе Ш пЮСбескВиббоп // идентификатор выбранной кнопки Групповая рамка Групповая рамка (бгоир Ьох) — это прямоугольная рамка с надписью, внутри ко- торой помещается некоторый набор элементов управления. Групповая рамка является исключением среди элементов класса В11ТТОМ (см. табл. 7.1). Она не обрабатывает ни сообщения от клавиатуры, ни сообщения от мыши. Она не посылает своему родительскому окну сообщение ММ_С0ММАМ0. В ос- новном групповая рамка применяется как элемент оформления интерфейса, благо- даря которому он становится более удобным и дружественным для пользователя.
Часто групповая рамка используется для объединения в одну группу несколь- ких флажков или переключателей. Причем в первом случае состояния флажков в группе не зависят друг от друга. Состояния переключателей, объединенных в груп- пу, взаимозависимы, и только один из них может иметь включенное состояние. Обращаем ваше внимание на то, что одно лишь размещение нескольких пере- ключателей в групповой рамке не обеспечит их надлежащее поведение. Рамка является сугубо визуальным элементом. Чтобы переключатели составляли груп- пу, первый из них должен иметь установленными флажки бгоир и ТаЬ $1:ор (на вклад- ке 6епега1 окна свойств элемента управления), а у всех остальных переключате- лей в группе эти опции должны быть сброшены. Пример использования групповой рамки, флажков и переключателей Рассмотрим применение групповой рамки, флажков и переключателей на приме- ре разработки приложения 01д0етоЗ, являющегося модификацией приложения 01д0ето1. Цель модификации — сделать более удобным интерфейс пользователя в этой программе. Дело в том, что для выбора вида фигуры и задания ее цвета пользова- тель приложения 01д0ето1 должен трижды выбирать пункт меню ОгамЗНаре. Пер- вый раз — с командой 5Наре для выбора вида фигуры, второй раз — с командой Со1ог для установки флажков Кед, Бгееп, В1ие и третий раз — с командой Со1ог для выбора одного из переключателей, Оагк, МесНит, ИдНС Было бы, наверное, удоб- нее, если бы по команде ОгамЗНаре вызывалось диалоговое окно, содержащее груп- пы опций для установки всех атрибутов рисования. Сказано — сделано. Создайте новый проект с именем 01д0етоЗ. Скопируйте из папки проекта 01д0ето1 в папку проекта 01д0етоЗ файлы с расширениями .срр, .Н и .гс, скорректировав их имена заменой 01д0ето1 на 01д0етоЗ. Включите эти фай- лы в состав проекта. Скопируйте также файл ВиКегйу.Ьтр. Модификация меню в приложении О1дОетоЗ В окне Могкзрасе перейдите на вкладку Ке$оигсе\/1’ем, откройте в иерархическом списке папку Мепи и щелчком на элементе ЮК_МЕМ111 вызовите редактор меню. Откройте окно свойств Мепи Кет РгореПп’ез для пункта Ога\л/ зНаре. В этом окне нужно сбросить флажок Рор-ир. После щелчка мышью на нем будет отображено окно с предупреждением, что это действие уничтожит все пункты подменю Огам зНаре (рис. 7.20). Рис. 7.20. Предупреждение редактора меню при попытке снять флажок Рор-ир Подтвердите серьезность ваших намерений, нажав кнопку ОК. Окно исчезнет, а текстовое поле Ю станет доступным для ввода информации. Введите в него иден- тификатор ЮМ_ОКАМ_$НАРЕ. После этого закройте окно свойств пункта Огам 5Наре.
Разработка шаблона диалога Вы уже умеете делать это. Создайте заготовку шаблона нового диалогового окна. Для этого выполните команду IпзегЬ ► Кезоигсе, укажите тип ресурса 01 а 1од и нажмите кнопку Мем. Затем вызовите окно свойств диалога, дважды щелкнув мышью на форме диалога. В текстовом поле Ю укажите идентификатор ЮО_$НАРЕ_РАКАМ. Перейдите на вкладку 81у1е8 и отключите отметку флажка ТШе Ьаг, после чего закройте окно свойств диалога. Увеличьте примерно вдвое размер формы по вертикали. Выделите группу из двух элементов, щелкнув на кнопках ОК и Сапсе1. Применив команду 1_ауои1: ► Аггапде ВиНопз ► ВоНот, переместите эти кнопки на нижнюю границу формы. Теперь разместите на форме три групповые рамки. Внутри первой рамки нуж- но расположить три переключателя, внутри второй рамки — три флажка, внутри третьей рамки — снова три переключателя, как показано на рис. 7.21. ”5 Наре........................... Г- Нес1апд1е Г- НИотЬ Г- ЕШрзе Со1ог сотропеп1з I- Нес1 I- Бгееп I- В1ие Со1ог 1п1епз|1у Г- Оагк Г- МесИит Г- ИдЫ | ОК | Сапсе! | Рис. 7.21. Размещение элементов управления на форме диалога ЮО_8НАРЕ_РАИАМ В процессе размещения задайте свойства элементов управления в соответствии с табл. 7.5. Рекомендации по определению состояния флажков ТаЬ 81ор и Сгоир приводятся ниже в разделе «Клавиатурный интерфейс и порядок обхода элемен- тов управления». Таблица 7.5. Свойства элементов управления на форме диалога ЮО_5НАРЕ_РАРАМ Тип элемента ГО Сарйоп Флажок ТаЬ $1ор Флажок Сгоир Сгоир Ьох ЮС_5ТАТ1С 8Иаре Сброшен Сброшен Рас1ю Ьийоп Ю_кЕСТА1\1С1_Е Рес1апд1е Установлен Установлен Рас1ю Ьийоп Ю.кНОМВ РИотЬ Сброшен Сброшен Рас1ю Ьийоп Ю_Е1_1_1Р5Е ЕШрзе Сброшен Сброшен Сгоир Ьох ЮС_8ТАТ1С Со1ог сотропеп^з Сброшен Сброшен СИеск Ьох Ю_кЕО кеб Установлен Сброшен СИеск Ьох Ю.СкЕЕМ Сгееп Сброшен Сброшен СИеск Ьох Ю_ВШЕ В1ие Сброшен Сброшен Сгоир Ьох ЮС_8ТАТ1С Со1ог 1п1еп511у Сброшен Сброшен
Таблица 7.5 {продолжение} Тип элемента Ю СарВоп Флажок ТаЬ $1ор Флажок бгоир Нас1ю Ьийоп Ю.ОАкК Оагк Установлен Установлен Нас1ю Ьийоп Ю_МЕОШМ МесКит Сброшен Сброшен Нас1ю Ьийоп ю.ыснт идГ|1 Сброшен Сброшен Сохраните подготовленный шаблон в файле описания ресурсов при помощи кнопки 5ауе на панели инструментов Ухзпа! Збпсйо. Коррекция шаблона диалога ЮО_АВООТ В окне Могкзрасе перейдите на вкладку Ке$оигсе\йем и откройте шаблон диалога Ю0_АВ011Т. После этого откройте диалоговое окно О1’а1од РгореПп’ез и в поле СарВоп введите текст АЬои! 01д0етоЗ. Затем нужно открыть окно свойств Тех! РгорегВез для элемента управления ЮС_5ТАТ1С_1 и заменить текст 01д0ето1 на строку 01д0етоЗ. Модификация исходного кода Отредактируйте код файла 01д0ето3.срр так, чтобы он соответствовал листингу 7.3. Листинг 7.3. Проект О1дОетоЗ ////////////////////////////////////////////////////////////////////// // 01д0ето3.срр #1пс1ыс1е <\л/1 пс1о\л/8. И> #1пс1ис1е <81зс11 о. Н> #1пс1ыс1е "К1л/пс1.ГГ #1пс1ыс1е "гезоигсе.б" #с1еТ1 пе 1л1 200 // ширина фигуры #с1еТ1 пе Н 140 // высота фигуры спит ЗИареЗые { МАХ, М1И }; УуребеГ збгисб { Ш 1б_збаре; // идентификатор фигуры ВООЕ ГРес1; // компонент красного цвета ВООЕ ГОгееп; // компонент зеленого цвета ВООЕ ГВ1ие; // компонент синего цвета Ш 1с1_Ьг1д1тЕ; // идентификатор яркости цвета } ЗбареОаба; ЗбареОаба збареОаба; // Глобальная структура для обмена данными // между 50ареРагат01дРгос и 1л1пс1Ргос В001_ САЕЕВАСК 5бареРагат01дРгос(НШ0, 01ИТ, АРАРАМ, ГРАФАМ); В001_ САЕЕВАСК Абоь01дРгос(НШО, 1ИМТ, АРАРАМ, 1РАРАМ); 1РЕ51ЛТ САЕЕВАСК ШбРгос(НШ0, 01ИТ, АРАРАМ, 1РАРАМ); //========================================== Ш ШИАР1 Ш пМа п п (НIМ5ТА1Х1СЕ Щпзбапсе, Н1М5ТАМСЕ КРгеу1пз0апсе, ЕРЗТР ТрСтбПпе, Ш пСтбЗИом) { М56 тзд; Шпс1 тапп1л/пс1 ("01 дОетоЗ", К1пзбапсе, пСтбЗИом, МпбРгос, МАКЕ IК1ТРЕ500РСЕ (ЮР_МЕМ01), 100, 100, 400, 300); \л/М1е (6ебМеззаде(&тзд, МОЕЕ, 0, 0)) {
ТгапзШеМеззадеС&тзд); ОтзраТсЬМеззадеС&тзд); } геТигп тзд.мРагат; } //========================================== ЬВЕЗОЕТ САЕЕВАСК ОпОРгосСНШО Шпс1, 1ИМТ иМзд, ОРАВАМ мРагат, ЕРАВАМ 1Рагат) { 8-Ьа-Ыс Н1МЗТАМСЕ ЫпзТ; НОС НОС; РА1МТЗТВЫСТ рз; ВЕСТ гесТ; зТаЫс НМЕИО НМепи; // дескриптор главного меню 1пТ хО, уО, х1, у1, х2, у2; РО1МТ рТ[4]; зТаЫ с ЗИареЗые зИареЗые = М1М; зТаТ1с ВООЕ ЬЗЬом = ТВОЕ; зТаЫс НВВОЗН НВгизЬ, ЬОЫВгизЬ; сЬаг* 1ТетВез1хеМате[2] = { "Оесгеазе!", Ыпсгеазе!"}; 1пТ 1пТепзПу[3] = { 85, 170, 255 }; // интенсивность ВОВ-компонентов цвета 1пТ ЬЫдИТпезз; 8\л/1ТсИ (иМзд) { сазе ОМ_СВЕАТЕ: ЫпзТ = ОеТМос1и1еНапс11е(МОЕЕ); НМепы = ОеТМепи(Шпб); 8еТМепи0еТаи1Т1Тет(0еТЗиЬМепи(ЬМепи, 0), 10М_ОРЕМ, ГАЕЗЕ); СИескМепиВасНо1Тет(0еТ8иЬМепи(ИМепи, 1), ЮМ_ЗН00_ЗНАРЕ, ЮМ-НЮЕ-ЗНАРЕ, ЮМ-ЗНОО-ЗНАРЕ, МЕ_ВУСОММАМО); 8Наре0аТа.Ю_8Наре = I О_ВЕСТАМОЕЕ; 8Наре0аТа.1б_Ьг1дНТ = 1О_ОАВК; Ьгеак; сасе 1л/М_СОММАМО: 8тТсЬ (ЕОШВОЫРагат)) { /* код обработки для команд 10М_ОРЕМ, ЮМ_СЕ03Е, ... , ЮМ_ВЕ312Е - из листинга 6.1 */ сазе ЮМ-АВООТ: 01а1одВох(Ь1п8Т, МАКЕ1МТВЕ300ВСЕ(Ю0_АВ00Т), ШпО, АЬоыШдРгос); Ьгеак; сазе 10М_0ВА0_ЗНАРЕ: 01а1одВох(Ь1пзТ, МАКЕ1МТВЕ300ВСЕ(Ю0_ЗНАРЕ_РАВАМ), Шпс1, ЗЬареРагат01дРгос); Ьгеак; ОеТаиЮ: Ьгеак; }
Листинг 7.3 {продолжение} 1пуа11 ОаТеВесТ(Шпс1, М1Л_1_, ТРОЕ); Ьгеак; сазе Ш-РА1МТ: /* Код обработки сообщения Ш_РА1МТ из листинга 6.1 */ Ьгеак; сазе Ш_0Е8ТК0У: РозТ0и1ТМеззаде(0); Ьгеак; с1еТаи1Т: геТигп ОеТЮ пбомРгос(Шпс1, иМзд, мРагат, 1Рагат); } геТигп 0; } /============================= ВООЕ САЕЕВАСК АЬоиТО!дРгос(НШ0 Ы1д, ЫМТ иМзд, ФРАКАМ мРагат, ЕРАВАМ 1Рагат) { /* Текст функции - из листинга 7.1 (модифицированный вариант) */ } //========================^ ВООЕ САЫВАСК ЗНареРагатВ!дРгос(НШ0 Ы1д, ЫМТ иМзд, АРАРАМ мРагат, ЕРАВАМ 1Рагат) { зТаТ1с ЗЬареОаТа збареОаТаМем; зЫТЫ (иМзд) { сазе Ш_1М1Т01АЬ06: СНескВасИ оВиТТоп(001д, IО_ВЕСТАИОЕЕ, 10_ЕЫ1Р8Е, збареОаТа. Ы_зЬаре); ЫескРас11оВиТТоп(Ы1д, 1В_ВАВК, 10_Ы6НТ, збареОаТа. Ы_Ьг1дЬТ); СЬескО!дВиТТоп(601д, Ю_ВЕ0, збареОаТа.ТВеб); СЬескО! дВиНоп(601 д, Ю_0ВЕЕМ, збареОаТа ЫСгееп); СЬескО!дВиТТоп(601д, 10_ВЕЫЕ, збареОаТа.ТВ1ие); зЫреОаТаМем = зЬареОаТа; геТигп ТВОЕ; сазе Ш_С0ММАМ0: зЫТЫ (ЕОШРОЫРагат)) { сазе Ю-ВЕСТАИОЕЕ: сазе Ю-ВНОМВ: сазе 10-ЕЕЕ1Р8Е: зЫреОаТаМем. Ы_зЬаре = ЕОШРОЫРагат); геТигп ТВОЕ; сазе Ю_ВЕ0: зЫреОаТаМем.ТКеб = -зЫреОаТаМем. ТКеЫ геТигп ТВОЕ; сазе Ю-СВЕЕИ: зЫреОаТаМем. ТСгееп = -збареОаТаМем.ТСгееп;
геЬигп ТРОЕ; сазе Ю-ВЕСЕ: зЬаре0аЬаМем.ГВ1ие = ~зЬаре0аЬаМем.ГВ1ие: геЬигп ТВОЕ: сазе Ю_0АРК: сазе Ю_МЕ01иМ: сазе Ю-Е16НТ: зЬареОаЬаМем. 1 с1_Ьг1 дЬЬ = ЕОШВО(\л/Рагат); геЬигп ТРОЕ: сазе ЮОК: зЬареОаЬа = зЬареОаЬаМем: Епс101а1од(Ь01д, ТРОЕ); геЬигп ТРОЕ: сазе ЮСАМСЕЬ: Епс101а1од(Ь01д, ВАЕЗЕ); геЬигп ТРОЕ: } Ьгеак; } геЬигп ВАЕЗЕ: } ////////////////////////////////////////////////////////////////////// Конечно, следует обратить внимание на изменения, появившиеся в коде про- граммы. Так, структурная переменная зЬареОа^а теперь стала глобальной, хотя раньше она объявлялась в теле функции \Л/пс1 Ргос. Это необходимо для обмена дан- ными между \Л/пс1Ргос и диалоговой процедурой 5НареРагат01дРгос. Поля перемен- ной зНареОа^а хранят текущие параметры рисуемой фигуры. В момент запуска про- граммы поля глобальной структуры инициализируются нулями. Также появилась новая функция 5НареРагат01дРгос, которая является диалого- вой процедурой для окна ЮО_$НАРЕ_РАВАМ. Обрабатывая сообщение ММ_1М1Т01А1_06, эта функция инициализирует элементы управления диалогового окна. Ранее ука- зывалось, что если для флажка или переключателя установлено свойство АиЕо, то эти элементы сами управляют установкой и снятием своих меток, реагируя на щелч- ки пользователя. Но при первом появлении диалогового окна на экране программа должна позаботиться об установке меток в соответствии с текущими параметрами фигуры, сохраняемыми в переменной $НареОа1:а. Для этого вызываются функции СНескКасйоВийоп и СНескОфВийоп. Завершив инициализацию, функция 5НареРагат01дРгос копирует значение пе- ременной $НареОа1:а в локальную переменную $НареОа1:аМе№. В полях переменной $НареОа1:аМе№ записываются все изменения состояний флажков и переключателей в блоке обработки сообщения ММ_СОММАМО. Если диалоговое окно закрывается щелчком на кнопке ОК с идентификатором ЮОК, то перед его закрытием перемен- ной зЬареОаЕа присваивается значение зЬареОаЕаМехл/. Если диалоговое окно закры- вается щелчком на кнопке Сапсе1 с идентификатором ЮСАМСЕЬ, то переменная з1эаре ОаЕа сохраняет то значение, которое она имела до вызова диалогового окна. В оконной процедуре \Л/пс1Ргос обработка сообщения ММ_СОММАМО расширена новой ветвью: сазе ЮМ_0РА1л1_ЗНАРЕ: 01 а1одВох(ИIп8Ц, МАКЕIИТРЕЗООРСЕ(100_3НАРЕ_РАРАМ), 1п1л/пс1, 8ЬареРагат01дРгос): Ьгеак:
В этом блоке кода при помощи функции 01’а1одВох вызывается диалоговое окно Ю0_5НАРЕ_РАКАМ. Откомпилировав этот проект, проверьте работу приложения 01д0етоЗ. Клавиатурный интерфейс и порядок обхода элементов управления Система АУтйомга обеспечивает удобный клавиатурный интерфейс для доступа к элементам управления в диалоговом окне. Перемещение между элементами уп- равления осуществляется при помощи клавиши ТаЬ либо при помощи клавиш- стрелок. Нажатие клавиши ТаЬ перемещает фокус ввода на следующий элемент управления, который имеет стиль М$_ТАВ$ТОР (или для которого установлено свой- ство ТаЬ р), в соответствии с установленным порядком обхода. Порядок обхода элементов управления соответствует последовательности определений этих эле- ментов в файле описания ресурсов. При большом количестве элементов управления в диалоговом окне они обыч- но разбиваются на группы элементов по какому-нибудь функциональному при- знаку. Каждая группа элементов помещается в свою групповую рамку. Пользова- телю удобно переходить от одной группы элементов к другой при помощи клавиши ТаЬ, а внутри группы переходить от элемента к элементу с помощью клавиш-стре- лок. Если элемент управления имеет фокус ввода, то нажатие клавиши пробела производит тот же эффект, что и щелчок мышью. Чтобы обеспечить привычный для пользователя клавиатурный интерфейс, следует придерживаться несложных правил. Первому элементу управления в груп- пе нужно назначать свойство ТаЬ 81ор, а если это группа переключателей, то еще и свойство бгоир. У остальных элементов в группе оба свойства, ТаЬ зЕор и Сгоир, должны быть выключенными. Рис. 7.22. Порядок обхода элементов управления в окне диалога ЮО_8НАРЕ_РАКАМ
После окончания проектирования шаблона диалога порядок обхода элемен- тов управления не всегда получается удобным для пользователя. Среда Ухзпа! 81пс1ю позволяет легко изменить эту последовательность. Работая с редактором диалоговых окон, вызовите подменю 1_ауои1:, а в нем вы- берите команду ТаЬ оМег. Выполняя эту команду, редактор диалоговых окон раз- мещает номера на элементах управления, показывая порядок обхода, первоначаль- но соответствующий порядку добавления элементов управления. Для изменения порядка обхода последовательно щелкните на каждом эле- менте управления в задаваемом вами порядке. Например, вид шаблона диалога Ю0_5НАРЕ_РАКАМ из приложения 01д0етоЗ после выполнения указанной проце- дуры показан на рис. 7.22. Перейдем теперь к рассмотрению элементов управления ЕсН! Ьох и Из! Ьох. Окно редактирования Окно редактирования (ЕсН! Ьох), которое также часто называют текстовым полем ввода, представляет собой прямоугольное окно, в котором пользователь может вводить текст с клавиатуры. Элемент управления ЕсН! Ьох реализован на базе пре- допределенного оконного класса ЕО1Т. На практике используются различные окна редактирования в самом широком спектре: от небольшого однострочного поля ввода до многострочного элемента управления с автоматическим переносом строк, как в программе МхсгозоЙ Но1ерас1. Когда окно редактирования имеет фокус ввода, пользователь может наби- рать текст, перемещать курсор, удалять символы, выбирать группы символов, используя либо мышь, либо клавишу 8Н1П: и клавиши-стрелки, удалять или ко- пировать выбранный текст в буфер обмена АУшбом^, вставлять текст из буфера обмена. Элемент управления ЕсШ: Ьох размещается на форме диалога так же, как и дру- гие элементы управления. Затем нужно вызвать окно свойств ЕсШ: РгореПпез и на вкладке 6епега1 в поле Ю указать идентификатор элемента управления. На вклад- ке 81у1ез, показанной на рис. 7.23, можно задать дополнительные свойства окна редактирования. Рис. 7.23. Вкладка 81у1ез в окне свойств окна редактирования По умолчанию окно редактирования является однострочным, с автоматичес- кой горизонтальной прокруткой (свойство А1Й0 Н5сгоН), с объемной рамкой (свой- ство Вогдег) и выравниванием текста по левой границе окна (Ее!!). Открыв комби- нированный список АНдп !ех!, можно выбрать другой режим выравнивания текста (Сегйегес! или К1’дЫ:).
Если установить флажок И и НаНпе, то окно редактирования будет работать в многострочном режиме. При этом станут доступными для использования оп- ции Нол2оп1:а1 зсгоЩ Х/ег±1са1 зсгоИ, АиЕо УЗсгоИ. Установленный флажок МитЬег разрешает вводить в окно редактирования толь- ко цифры. Если же создаваемое окно редактирования предназначено для ввода пароля, то надо установить флажок Ра88\л/огс1, и тогда вместо вводимых символов в поле будут отображаться символы звездочки. Флажок Мо Мс1е зекесИоп заставляет элемент управления всегда показывать выделенную подстроку, даже если окно редактирования теряет фокус. Флажок ОЕМ сопуеП: задает режим работы, при котором вводимый текст преобразуется из набора символов \Упк1о\\ъ в набор символов ОЕМ, а затем — обратно. Это обеспечивает пра- вильное преобразование символов при использовании функции СНагТоОет. Флажок \Л/ап1: геРигп используется для многострочного окна редактирования. Если он установлен, то нажатие клавиши ЕпЕег приводит к вводу символа возвра- та каретки. Если флажок сброшен, то нажатие клавиши ЕпЕег воспринимается как щелчок на кнопке, которая отмечена в диалоговом окне в качестве выбранной по умолчанию. В этом случае для ввода символа возврата каретки применяется ком- бинация клавиш С'ЬгЕн-ЕгтЬег. Взаимоисключающие флажки Оррегсазе и 1_отегса$е преобразуют вводимые символы. В первом случае происходит конвертирование в символы верхнего ре- гистра, а во втором — в символы нижнего регистра. Флажок Кеас1-оп1у создает окно редактирования, которое предназначено только для чтения и не дает возможности пользователю вводить или редактировать текст. Для каждого элемента управления ЕсИ’1: Ьох система резервирует в области па- мяти приложения буфер размером 32 Кбайт для хранения введенного текста. Посылка сообщений окну редактирования Для выполнения различных операций по редактированию текста приложение может отправлять сообщения элементу Н\л/пс1Ес11± с помощью вызова следующей функции: Зепс1Ме88аде(Ыпс1Ес1П, код_сообщения, мРагат, 1 Рагат); В параметре Н\л/пдЕд11: указывается дескриптор элемента управления. АУш32 АР1 содержит специальную функцию ЗепсЮфКетМеззаде для отправки сообщений элементам управления, обращение к которой выглядит так: ЗегсЮ!дШетМе88аде(ГЮ!д, пЮО, код_сообщения, мРагат, 1 Рагат); где Н01д — дескриптор родительского диалогового окна, пЮО — идентификатор элемента управления. Иногда проще будет использовать эту функцию, а не 5епс1Ме$$аде, так как в данном случае не требуется получать дескриптор элемента управления. По результатам выполнения обе функции равнозначны. В табл. 7.6 описаны некоторые коды сообщений для работы с окном редакти- рования. Таблица 7.6. Операции с окном редактирования через коды сообщений Код сообщения шРагат 1Рагат Описание ЕМ_8ЕТ8Е1_ 181а Ц 1Епс1 Выделить текст, начиная с позиции |81аН: и заканчивая позицией 1Епс1 ЕМ_6ЕТ8Е1_ 8и81аН: 8иЕпс1 Получить начальное и конечное положения текущего выделения \Л/М_С1_ЕАК 0 0 Удалить выделенный текст
Код сообщения ал/Рагат 1Рагат Описание \Л/М_С1ГГ 0 0 Удалить выделенный текст и поместить его в буфер обмена \Л/1пс1о\л/5 \Л/М_СОРУ 0 0 Скопировать выделенный текст в буфер обмена \Л/1пс1о\л/5 \Л/М_РА8ТЕ 0 0 Вставить текст из буфера обмена в месте, соответствующем позиции курсора \Л/М_СЕТТЕХТ пМах згВиГГег Скопировать текст (не более пМах символов) из элемента управления в буфер згВиГГег ЕМ_6ЕТ1_1ЫЕСО111\1Т 0 0 Функция возвращает число строк для многострочного окна редактирования ЕМ_1_1ЫЕ1_Е1\16ТН Ите 0 Функция возвращает длину строки ||_1пе ЕМ_СЕТЫЫЕ Ите згВиГГег Скопировать содержание строки ||_1пе в буфер згВиГГег Получение сообщений от окна редактирования Элементы управления Ес11± Ьох посылают оконной процедуре родительского окна сообщения ММ_С0ММАМ0. Эти сообщения содержат в младшем слове параметра мРагат идентификатор элемента управления, в старшем слове параметра доРагат — код уведомления, а в параметре [Рагат — дескриптор элемента управления. Некоторые из кодов уведомления от окна редактирования приведены в табл. 7.7. Таблица 7.7. Коды уведомления от элемента управления типа ЕсН Ьох Код уведомления Интерпретация ЕЫ_8ЕТРОС118 Окно получило фокус ввода ЕМ_К11_1_РОС115 Окно потеряло фокус ввода Е1\1_11РОАТЕ Содержимое окна будет меняться Е1\1_СНА1\1СЕ Содержимое окна изменилось Е1\1_Екк8РАСЕ Произошло переполнение буфера редактирования Кроме того, перед своей перерисовкой обычное окно редактирования посыла- ет своему родительскому окну сообщение ММ_СТ1_СО1_ОКЕО1Т, а окно редактирова- ния с атрибутом «только для чтения» посылает сообщение ММ_СТ1_СО1_ОК5ТАТ1С. Об- рабатывая эти сообщения, родительское окно может изменить цветовые атрибуты окна редактирования аналогично тому, как это было реализовано в приложении Ь1д0ето1 для элемента управления типа 51а1:1с Тех!. Функции чтения/записи текста для элементов управления АУш32 АР1 содержит функции беЮфКетТех!:, ЗеЮфКетТех!: и беЮфКетТп!:, кото- рые используются для работы с текстовой информацией, ассоциированной с не- которым элементом управления. Эти функции могут применяться не только к окну редактирования, но и к над- писям, кнопкам, флажкам, переключателям и рамкам. Функция беЮфКетТех!: имеет следующий прототип: ЩИТ 6еШдШетТехЦ(НШО И01 д, Ш сШЮ, 1РТ8ТК 1р8^г1пд, Ш пМахСоигД); Она позволяет извлечь текст, ассоциированный с элементом управления, иден- тификатор которого передается в параметре с1г11 Ь, и записать его в буфер с адре-
сом 1р5!т1 пд. Параметр пМахСоип! задает максимальное количество символов, ко- пируемых в буфер 1р5±ппд. При успешном завершении функция возвращает чис- ло символов, прочитанных в буфер, без учета завершающего нуль-символа. При возникновении ошибки функция возвращает нулевое значение. Функция 5еЮ1д11:етТехЕ имеющая такие же параметры, осуществляет обрат- ную операцию записи текстовой строки, на которую указывает 1р51лпд, в элемент управления с1г1Ю. Функция беЮфИетТп!: с прототипом ЩИТ 6еШ дШет!гД(НШО ИО1д, Ш сШЮ, ВООЬ* 1 рТгапз!аЦес!, В001_ Ь81дпес1); предназначена для извлечения текстового изображения десятичного числа из эле- мента управления С1г110 и преобразования его к целочисленному значению. Если изображению числа предшествуют пробелы, то они отбрасываются. В процессе преобразования функция сканирует текстовую строку символ за символом и, если очередной символ оказывается не цифрой, то прекращает преобразование. Параметру 1рТгап$1а1:ес1 обычно передается адрес переменной типа ВООЕ, в кото- рую записывается значение, свидетельствующее об успешности завершения функ- ции. Значение ТВОЕ используется в случае успешного преобразования. Если вас эта информация не интересует, то можно передать данному параметру значение МОП. Если параметр Ь51’дпес1 равен ТВОЕ, то функция будет учитывать наличие знака «минус» перед числом и возвращать значение типа 1П1:, несмотря на определение в прототипе функции возвращаемого типа 1ИМТ. Пробел между минусом и первой цифрой числа не допускается. Функция СеЮфИетТп!: возвращает нулевое значение, если транслируемое значение превышает 1МТ_МАХ (2147483647) для знакового числа или 1ПМТ_МАХ (4294967295) для беззнакового числа. Пример использования функций СеЮфИетТех!: и 5еЮ1д11:етТех1: приведен в ли- стинге 7.4. Список Список (Из! Ьох) представляет собой прямоугольное окно, в котором отображает- ся набор элементов, из которых пользователь может делать выбор. Элементы списка могут быть представлены строками, растровыми образами или комбинацией текста и изображения. Если размеры окна не позволяют пока- зать все элементы списка, то Из! Ьох создает полосу прокрутки. Типичный пример использования списка — список файлов в диалоговом окне Ореп, вызываемом по команде ЕПе ► Ореп в среде Ухзпа! 81исйо. Различают списки с единичным выбором, в которых пользователь может выб- рать только один пункт списка, и списки с множественным выбором, допускаю- щие выбор более одного пункта списка. АУшбом^ показывает выбранный элемент в списке, инвертируя цвет текста и цвет фона для символов. В списке с единичным выбором пользователь может выбрать пункт списка щелчком мыши или нажатием клавиши пробела после получения списком фоку- са ввода. Клавиши управления курсором перемещают как курсор, так и текущую выборку. В списке с множественным выбором первый щелчок на пункте выбира- ет его, а следующий щелчок отменяет предыдущую операцию. Программа может добавлять или удалять элементы в списке, отсылая сообще- ния оконной процедуре списка. Когда пользователь выбирает или отменяет вы-
бор пункта в списке, система посылает нотификационное сообщение родительс- кому окну. Родительское окно может определить, какой пункт списка был выбран пользователем. Элемент управления Из! Ьох помещается на форму диалога так же, как и другие элементы управления. Затем необходимо вызвать окно свойств Из! Вох РгореФ’ез и на вкладке 6епега1 в поле Ю указать идентификатор элемента управления. На вкладке 81у1ез, показанной на рис. 7.24, можно задать дополнительные свойства списка. Рис. 7.24. Вкладка 81у1ез в окне свойств списка По умолчанию создается список с единичным выбором (51 пд1е), имеющий рам- ку (Вогдег), выполняющий автоматическую сортировку элементов списка (Бог!) и обеспечивающий появление вертикальной полосы прокрутки в случае необхо- димости (УегЫса!. зсгоН). Флажок Мо 1п!едга1 Неп’дЫ: указывает системе, что высота окна списка Из! Ьох должна быть в точности такой, какой она указана на форме диалога. Если этот флажок сбросить, то АУтйомга может уменьшить высоту окна, чтобы исключить неполное изображение элемента списка. В окне 5е1ес!1оп можно выбрать другой стиль списка. Кроме значения 51’пд1е здесь доступны следующие значения: Стиль списка Описание Ми1йр1е Список с множественным выбором. Разрешено выбирать несколько элементов, щелкая на каждом элементе для выбора или отмены выбора Ех1епс1ес1 Список с множественным выбором. Разрешено выбирать несколько элементов только при нажатой клавише 8ЫП: Ыопе Список содержит элементы, которые можно просматривать, но не выбирать В окне Омпег Огам по умолчанию используется опция Мо. Если необходимо создать список, изображаемый окном-владельцем, то нужно выбрать соответству- ющий параметр: Опция Описание Пхеб Создать список со стилем Омпег бгам. Родительское окно получает сообщение \Л/М_МЕА81ЖЕ1ТЕМ при создании списка и сообщение \Л/М_ОКА\Л/1ТЕМ, когда требуется отобразить элемент УапаЫе То же, что и Нхес1, за исключением того, что размер каждого элемента в списке можно определять отдельно. Для любого элемента в списке перед вызовом сообщения \Л/М_ОРА\Л/1ТЕМ вызывается сообщение \Л/М_МЕА81ЖЕ1ТЕМ Флажок Наз зФ’пд можно использовать только при выборе стиля Омпег с1гам. Если он установлен, то это означает, что в список должны быть введены строки.
Затем для получения конкретного элемента списка в приложении можно исполь- зовать сообщение 1_В_6ЕТТЕХТ. Флажок Ми1±1-со1итп позволяет создавать многостолбцовый список, который может прокручиваться по горизонтали. Но для этого должен быть установлен флажок Нол2оп1:а1 зсгоН. Для установки ширины столбцов используется сообще- ние ЕВ_5ЕТС01_11ММУУ10ТН. Флажок Мо гедгахл/ определяет список, окно которого не получает сообщений ММ_РА1МТ при внесении изменений. Этот стиль может быть в любой момент отме- нен посылкой сообщения ММ_$ЕТКЕОКАМ. Флажок О1’$аЫе по зсгоИ заставляет отображать в списке вертикальную полосу прокрутки, даже если список не содержит необходимого количества элементов. При этом полоса прокрутки недоступна до тех пор, пока не появится достаточное количество элементов. По умолчанию полоса прокрутки скрыта, если список со- держит столько элементов, что все они отображаются в окне. Посылка сообщений списку Для выполнения различных операций со списком приложение может отправлять сообщения элементу Нхл/псШб!:, вызывая функцию Зепс1Ме88аде(Ыпс1Ы8^, код_сообщения, мРагат, 1Рагат); или равнозначную ей функцию 8епсЮ1 д1^етМе88аде(И01 д, ЮС_1_18Т, код_сообщения, мРагат, 1Рагат); где ЮС_П5Т — идентификатор элемента управления. Результаты выполнения этих функций для некоторых кодов сообщений опи- сываются в табл. 7.8. Таблица 7.8. Операции со списком через коды сообщений Код сообщения шРагат 1Рагат Описание 1_В_А008ТкИ\16 0 5281ппд Добавляет в список строку 5281ппд. Если свойство 8ог1 у списка выключено, то строка будет добавлена в конец списка. Если это свойство включено, то после добавления строки производится сортировка списка 1_В_0Е1_ЕТЕ8ТкИ\16 Ипбех 0 Удаляет строку с индексом Ипбех 1_В_Р1Ш8Тк1М6 181а Ц 5281ппд Ищет строку, начинающуюся префиксом 5281ппд. Поиск начинается с элемента с индек- сом |81аг1 + 1. Если \л/Рагат равен -1, то поиск начинается с начала списка. Функция возвра- щает индекс найденной строки или 1_В_Екк, если поиск завершился неуспешно 1_В_6ЕТСО11МТ 0 0 Возвращает количество элементов в списке 1_В_6ЕТС1Ж8Е1_ 0 0 Возвращает индекс текущего выбранного элемента или 1_В_Екк, если такого элемента нет. Код можно использовать только для списков с единичным выбором 1_В_6ЕТ8Е1_СО11МТ 0 0 Возвращает количество выбранных элементов (для списка с множественным выбором) 1_В_6ЕТ8Е1_1ТЕМ8 пМах рВиГ Заполняет буфер с адресом рВиГ массивом индексов выделенных элементов для списка с множественным выбором. пМах задает мак- симальное количество таких элементов
Код сообщения ал/Рагат 1Рагат Описание 1_В_6ЕТТЕХТ |1пс1ех 528Ц|пд Копирует строку с индексом Ппс1ех в буфер знЗЫпд. Размер буфера должен быть достаточ- ным для размещения строки. В случае выделения динамической памяти требуемый размер мож- но узнать, послав сообщение 1_В_6ЕТТЕХТ1_Е1\1. 1_В_СЕТТЕХТ1_ЕМ Ипбех 0 Возвращает длину строки с индексом Ипбех 1_В_11\18ЕкТ8ТК11\16 |1пс1ех 528Ц|пд Вставляет строку зн&ппд после строки с индек- сом Ипбех. Применяется для списка с выклю- ченным свойством Бог! 1_В_кЕ8ЕТСО1\1ТЕ1\1Т 0 0 Удаляет все элементы из списка 1_В_8Е1_ЕСТ8ТкИ\16 151:аг1: 528Ц|пд Выполняется так же, как для сообщения 1_В_Р1Ы08ТИ1М6, но дополнительно выделяет найденную строку 1_В_8ЕТС1Ж8Е1_ |1пс1ех 0 Выбирает элемент с индексом Ипбех (в списке с единичным выбором) 1_В_8ЕТ8Е1_ \л/Рагат |1пс1ех Выбирает элемент с индексом Ипбех (в списке с множественным выбором). Если \л/Рагат равен ТН11Е, элемент выбирается и выделяется, если ЕА18Е — выбор отменяется. Если 1 Рагат равен -1, то операция применяется ко всем элементам списка Ссылка на строки текста в списке обычно осуществляется через индекс 11пдех, который начинается с нуля, что соответствует самому верхнему элементу списка. Функция 5епс1Ме$$аде после отправки некоторых сообщений, не использую- щих код возврата для передачи результата, может вернуть один из специфичных кодов. Код 1_В_ОК (0) возвращается при нормальном завершении функции, код 1_В_ЕКК$РАСЕ (-2) — в случае нехватки памяти для сохранения содержимого спис- ка, 1_В_ЕКК (-1) — при возникновении любой другой ошибки. Получение сообщений от списка Когда пользователь щелкает мышью в окне списка, оно получает фокус ввода и посылает оконной процедуре родительского окна сообщение ММ_С0ММАМ0, в ко- тором старшее слово параметра доРагат содержит код уведомления. Наиболее распространенные коды уведомления приведены в табл. 7.9. Таблица 7.9. Коды уведомления от элемента управления типа 1181: Ьох Код уведомления Интерпретация 1_В1\1_8ЕТЕОС118 1_В1\1_К11_1_ЕОС118 1_ВМ_8Е1_СНА1\16Е 1_В1\1_ОВ1_С1_К Окно получило фокус ввода Окно потеряло фокус ввода Текущий выбор был изменен (список должен иметь свойство МойГу) На данном пункте списка был двойной щелчок мыши (список должен иметь свойство 1\1ой1у) 1_В1\1_ЕКК8РАСЕ Превышен размер памяти, отведенный для списка Пример использования элементов управления ЕсН( Ьох и Ьох Различные вариации на тему проекта 01д0ето..., вам, наверное, уже надоели. Да- вайте создадим что-нибудь новенькое. Например, приложение «Электронная
записная книжка» для хранения сведений о ваших друзьях и знакомых. Эти све- дения могут включать: □ имя (фамилия и.о.), содержащее не более 20 символов; □ телефон (максимум 20 символов); □ дата рождения (максимум 20 символов); □ адрес (максимум 80 символов). Реальное хранение информации будет обеспечиваться с помощью «базы дан- ных», реализованной в файле по1еЬоо к.да!. Каждая запись в базе данных, или строка файла, имеет формат, показанный в табл. 7.10. Таблица 7.10. Формат записи для базы данных и для списка 1181 Ьох Поле: Имя Телефон День рождения Адрес Байты: 0 1 ... 20 21 22 ... 41 42 43 ... 62 63 64 ... 143 Заметим, что каждое поле имеет длину на единицу большую, чем того требует спецификация программы. Дополнительный байт в конце каждого поля учитыва- ет размещение нуля при записи значения в виде С-строки. Для простоты реализации приложение будет иметь меню с единственным пунк- том Просмотр, при выборе которого будет вызываться диалоговое окно, содержа- щее окно списка Из! Ьох. Диалоговое окно должно содержать кнопки, при помощи которых пользователь может добавить новую запись в список или удалить выб- ранную запись из списка. Примем решение, что для удобства пользователя окно списка должно отобра- жать только первые два поля каждой записи (мы считаем их основными). Допол- нительная информация, показывающая день рождения и адрес, должна отображать- ся в отдельных окнах редактирования (ЕФТ Ьох) только после выбора какого-либо элемента в окне списка. Создайте новый проект типа \Л/1 пЗ2 АррПсаВоп с именем МуМо1:еЬоок1. Добавьте к приложению ресурс меню ЮК_МЕМ1П с одним пунктом. Пункт меню должен иметь имя Просмотр и идентификатор ЮМ_\/1ЕМ. Включите в приложение ресурс диалогового окна Ю0_\/1ЕМ. В свойствах диа- логового окна снимите флажки Т1Т1е Ьаг и 5у81ет теп и. Теперь увеличьте ширину
первоначальной формы диалога примерно вдвое и разместите на ней элементы управления в соответствии с рис. 7.25. Ширину окна списка, возможно, придется изменить. Это выяснится на этапе тестирования программы. В окне списка должны быть полностью видны первые два поля каждой записи, но не должно попадать начало третьего поля. Позже, при разработке приложения МуМо1:еЬоок2, мы покажем, как можно ре- гулировать программным способом размеры и размещение элементов управле- ния в зависимости от метрик используемого шрифта. Для элементов управления должны быть установлены следующие атрибуты: Элемент ГО Сарйоп Свойства Сгоир Ьох ЮС_СкО11Р_0 По умолчанию 81аЬс 1ех1: ЮС_5ТАТ1С Имя По умолчанию 81аЬс 1ех1: ЮС_5ТАТ1С_РНОМЕ Телефон По умолчанию 1151: Ьох ЮС_1_15Т1 — По умолчанию Вийоп ЮС_кЕС_АОО Добавить По умолчанию Вийоп ЮС_кЕС_ОЕ1_ЕТЕ Удалить По умолчанию Сгоир Ьох ЮС_СкО11Р_1 Дополнительная информация По умолчанию Зййс 1ех1: ЮС_8ТАТ1С_АООк Адрес По умолчанию ЕсИ1: Ьох ЮС_11\1ЕО_АООк — Установить дополнительно флажки Ми1Ь1|пе, Нопнопй! зсгоН, Неас1-оп1у 51айс 1ех1: ЮС_5ТАТ1С_ВОАУ День рождения По умолчанию ЕсИ1: Ьох ЮС_11\1ЕО_ВОАУ — Установить дополнительно флажок Неас1-оп1у Включите в приложение ресурс диалогового окна Ю0_А00_КЕС с заголовком Ввод записи. Свойства диалогового окна оставьте по умолчанию. Поместите на фор- му диалога Ю0_А00_ВЕС четыре надписи и четыре окна редактирования. Разме- щение элементов управления должно соответствовать рис. 7.26. Рис. 7.26. Форма диалога ЮО_АОО_кЕС Для элементов управления установите следующие атрибуты: Элемент ГО Сарйоп Свойства 51айс 1ех1: ЮС_5ТАТ1С Имя По умолчанию 51айс 1ех1: ЮС_5ТАТ1С Телефон По умолчанию 51айс 1ех1: ЮС_5ТАТ1С Адрес По умолчанию
Элемент ГО Сарйоп Свойства 81айс ЕехЕ ЮС_5ТАТ1С_ВОАУ День рождения По умолчанию ЕЬк Ьох ЮС_1ЫР1ГГЦ\1АМЕ — По умолчанию Ес№ Ьох ЮС_1ЫР1ГГ_РНОЫЕ — По умолчанию Ес№ Ьох ЮС-1ЫР11Т-АООИ — По умолчанию Ес№ Ьох ЮС_ЖР11Т_ВОАУ — По умолчанию Осталось написать код. Добавьте к проекту файл МуМо1еЬоок1 .срр, текст кото- рого приведен в листинге 7.4. Листинг 7.4. Проект Му1\1обеЬоок1 ////////////////////////////////////////////////////////////////////// // МуМобебоок1.срр #1пс1ибе <\л/1 пс1о\л/8. Н> #1пс1ибе <б8бгеат> из1пд патезрасе збб; #1пс1ибе "КМпскГГ #1пс1ибе "гезоигсе.М" #с1еТ1 пе М1 21 #беб1пе М2 81 #беб1пе Т0ТА1__517Е (3*И1 + М2) бзбгеат ббаб; збгисб 8иЬ8бг { скаг пате[М1]; скаг рИопе[М1]; скаг ЫгШау[М1]; скаг ас1с1ге88 [М2]; }; ит’оп Оаба { 8иЬ8бг 88; скаг 11пе[Т0ТАЕ_812Е]; }; с1а88 Е1зб1бет { скаг ЬиГ[Т0ТА1_812Е]; риЫ 1с: Е1зШет() { С1еаг(); } // Очистка данных (заполнение строки 11пе пробелами) уо16 С1еаг() { тетзеб(баб.11пе, ' ', Т0ТАЕ_317Е); } // Замена в строке 11 пе всех нулевых байтов, кроме последнего, // символом 'пробел' уо16 0о0пе8бг1пд() { 1пб соипб = 0: Гог (пгтЬ 1 = Т0ТАЕ-517Е - 1: 1 > 0: --1) 1Г (!баб. 11пе[1 ]) 1Г (!соипб) соипб++; е1зе баб.11пе[1] = ' ': } сопзб сбаг* 6ебВ1гбИбау() { тетсруСЬиб, баб.зз.ЫгШау, М1);
ЬиГ[М1-1] = 0; геЕигп ЬиГ; } сопзЕ сЬаг* беШИгеззО { тетсру(ЬиГ, (Ш.зз.асИгезз, М2); ЬиГ[М2-1] = 0; геЕигп ЬиГ; } ОаЪа с1а1; }: ЫзЫГет Пет; ВООЬ САЕЕВАСК \/1еО1дРгес(ШМ0, 1ИИТ, 1л1РАРАМ, ВРАКАМ); ВООЬ САЕЕВАСК Ас1с1Рес01аРгос(ШМ0, 1ИИТ, АРАРАМ, 1РАРАМ); ВРЕЗиВТ САЕЕВАСК Шс1Ргос(ШМ0, 1ИМТ, В/РАРАМ, ВРАРАМ); //============================================ 1пГ ШМАР1 ШпМа1п(НIИЗТАИСЕ ЫпзГапсе, Н1МЗТАМСЕ ЬРгеу1пзГапсе, ЕРЗТР ТрСтЬЫпе, 1пГ пСгткОом) { М86 тзд; Шпс1 та1 п1л/пс1 ("МуМо1зе1эоок1", ЫпзГапсе, пСтсЕЬом, 1л1пс1Ргос, МАКЕ1МТРЕ80иРСЕ(10Р-МЕМВ1), 100, 100, 400, 300); \л/М1е (6еГМеззаде(&тзд, МОЕВ, 0, 0)) { ТгапзШеМеззадеС&тзд); 01зраГсЬМеззаде(&тзд); } геГигп тзд.мРагат; } /============================= ЕРЕЗВЕТ САЕЕВАСК ШЬРгос(НШО Шпс1, 1ИМТ иМзд, АРАРАМ мРагат, ЕРАРАМ 1Рагат) { Н1МЗТАМСЕ Н1ПЗ-Е; зтТсЬ (иМзд) { сазе Ш_С0ММАМ0: змПсМ (В01лЮР0(\л/Рагат)) { сазе 10М_У1ЕВ: НТпз-Е = Зе1зМоби1 еНапсИ е(МЕ1ЕЕ); 01 а!одВохШпзЕ, МАКЕIМТРЕ300РСЕ(100_УIЕЮ , 01л1пс1, 71ем01дРгос); Ьгеак; } Ьгеак; сазе Ш_0Е8ТР0У: РозЩпЬМеззадеСО); Ьгеак; ЬеГаиИ: геЕигп ОеГШпсЫРгос(Шпс1, иМзд, мРагат, 1Рагат); } геЕигп 0; }
Листинг 7.4 {продолжение} В001_ САЕЕВАСК 71ем01дРгос(НИМ0 601д, 1ЛМТ иМзд, ПРАВАМ мРагат, ЕРАВАМ 1Рагат) { зТаЫс НИНО ЬЫзТВох; зТаТ1 с НШВ ЬЕЫЫпТоАббг, ЬЕЫЫпТоВбау; ШИВ Нйг 1 ; 8-Ьа-Ыс ЬООЕОМТ 1Т; НЕОИТ НГоп-Ы; СОЬОВВЕЕ еб1Т1пТоСо!ог; зТаЫс НВРЫЗН бВгизб; 1пТ п1Тетз; // количество элементов в списке 1пТ ЮигТТет; // текущий выбранный элемент списка ВООЕ сИдУез; 1 пТ 1 ; з\л/1Тсб (иМзд) { сазе ИМ_1М1Т01АЕ06: НЕпз-ЬВох = 6еТ01д1Тет(601д, ЮС_Е13Т1); бЕЫЫпТоАббг = 6еТ01 д 1Тет(601 д, ЮС_1МЕ0_А00В); бЕЫЫпТоВбау = 6еТ01 д 1Тет(601 д, ЮС_1МЕ0_В0АУ); // Модификация шрифта для элемента бЫзТВох 1Г.1ТНепдбТ = 16; 1зТгсру( (ЕРЗТР)&1 Г. 1 ТЕасебате, "Сот’ег" ); бЕопЫ = СгеаТеЕопТ1пб1гесТ(&1Т); ЗепбМеззадеСбЫзТВох, ИМ_8ЕТЕ0бТ, (ПРАВАМ) бЕопЫ, ТРОЕ ); // Создание кисти для фона элементов управления еб1ЫпТоСо1ог = В6В(190, 255, 255); бВгизб = СгеаТеЗо!1бВгизб(еб1Т1пГоСо1ог); // Чтение "базы данных" из файла и инициализация списка ТбаТ.ореп("поТеЬоок.баТ", 1 оз::1п); 1Т (!ТбаТ) { ТбаТ.с1еап(); геТигп ТРОЕ; } е!зе \л/611е (1) { ТбаТ.деТ11 пе(1 бет.баб. 11 пе, зыеоПОаТа)); 1Г (ТбаТ.еоТО) { ТбаТ.с1еап(); Ьгеак; } ЗепбМеззадеШЫзТВох, ЕВ_А003ТР1ИО, О, (ЕРАРАМ)1Тет.баТ.11пе); } ТбаТ.с!озе(); геТигп ТРОЕ; сазе 1л/М_СТЕС0Е0РЗТАТ1С: // Модификация цветовых атрибутов окон редактирования 6СТг1 = (НИ6О)1 Рапагп; 1Т ((6СТг1 == 6Е61Т1пТоАббп) || (6СТп1 == 6Еб1Т1пТоВбау)) { ЗеТВкСо!ог((НОС)мРапат, еб1ТIпТоСо!оп); ЗеТВкМобе((НОС)мРапат, ТРАИЗРАРЕМТ); геТигп (СПОРО)бВгизб; } Ьгеак; сазе ИМ_СОММА6О: зш’Тсб (ЕОИОРО(мРапат)) {
сазе 1ОС_ВЕС_АОО: сПдХез = 01 а!одВох( (НIИ8ТАМСЕ)6е1аМос1и1 еНапс11 е(М1Л1), МАКЕ1МТВЕ801ЖСЕ( 100_А00_ВЕС), 001 д, АббВесО!аРгос); 1Т (сПдУез) ЗепОМеззадеПЕНТВох, ЕВ_А008ТК1И6, О, (ЕРАВАМ)Пет.с1а1с. 11 пе); Ьгеак; сазе ЮС-ВЕС-ОЕЕЕТЕ: 1Сиг1Тет = ЗепбМеззадеСНЕ1зТВох, ЕВ_0ЕТСЫК8ЕЕ, 0, 0); И (ЮигИет != ЬВ_ЕВВ) { ЗепОМеззадеПЫ зТВох, ЕВ_0ЕЕЕТЕЗТК1И6, ЮигИет, 0); ЗеТ01д1ТетТехТ(Ь01д, I0С_1МЕО_АООВ, ""); ЗеТ01д1ТетТехТ(Ь01д, I0С_1ИГО_ВОАУ, ""); } е!зе { МеззадеВох(Ь01д, "Сначала надо выделить элемент списка", "Ошибка", МВ_ОК); Ьгеак; } Ьгеак; сазе 10С-Ы8Т1: змПсЬ (НШОВОЫРагат)) { сазе ЕВМ-ЗЕЕСНАВЮЕ: // Показываем дополнительную информацию ЮигИет = ЗепОМеззадеСЬИзТВох, ЕВ_0ЕТС0КЗЕЕ, 0, 0); ЗепОМеззадеСЬИзТВох, ЬВ_ОЕТТЕХТ, ЮигИет, (ЕРАВАМ)Пет.баб. 11 пе); ЗеТ01дИетТехТ(Ь01д, 10С_1 ИЕО_АООВ, ПетЮеТАсЮгеззС)); ЗеТ01дИетТехТ(Ь01д, 10С_1 МЕО_ВОАХ, ПетЮеТВ!гТ0с1ау()); геТигп ТВОЕ; } Ьгеак; сазе ЮОК: Т0аТ.ореп("поТеЬоок.с1аТ", 1оз::оиТ | 1оз::Тгипс); И (!ТОаТ) { МеззадеВох(Ь01д, "Не могу открыть файл 'поТеЬоок.ОаТ' для записи!", "Ошибка", МВ—ОК); геТигп ЕАЬЗЕ; } пИетз = Зепс1Меззаде(НЕ1 зТВох, ЕВ_ЗЕТС00МТ, 0, 0); Тог (1 =0; 1 < пИетз; ++1) { ЗепбМеззаде(НЕ1зТВох, ЬВ_ОЕТТЕХТ, 1, (ЕРАВАМ)Пет.ОаТ.11пе); ТОаТ « Пет.ОаТ. 11 пе « епс11; } Тс1аТ.с1озе(); ЕпсЮ1а1од(Ь01д, ТВОЕ); геТигп ТВОЕ; сазе ЮСАМСЕЬ: ЕпсЮ1 а! од(Н01д, ЕАЬЗЕ);
Листинг 7.4 {продолжение} геЬигп ТЕМЕ; } Ьгеак: } геЬигп ГАЕЗЕ; } /============================= ВООЬ САЕЕВАСК АсШесЮ аРгос(НШВ Ю1д, ЮТ иМзд, ПРАВАМ мРагат, ЬРАРАМ 1Рагат) { зЮск (иМзд) { сазе Ш_1М1Ю1АЕ0б: 8еТЕосиз(беТ01д1Тет(Ь01д, IВС_1МРЬТ_МАМЕ)); геТигп ЕАЕЗЕ: сазе Ш-СОММАМВ: зт’ТсЬ (ЕОШРВЫРагат)) { сазе ЮОК: 1Тет.С1еаг(): беТО1д 1ТетТехТ(ЬВ1 д, ЮС_ЮРОТ_МАМЕ, 1Тет.с1аТ.зз.пате, М1); беТО1дЮетТехТ(К01 д, ЮС_ЮР0Т_РН0МЕ, 1Тет.с1аТ.зз.рЬопе, М1); беТО!дЮетТехТ(К01 д, ЮС_ЮР0Т_В0АУ, 1Тет.с1аТ.зз.ЫгТЬс1ау, М1); беТО!д 1ТетТехТ(К01 д, ЮС_ЮР0Т_А00В, 1 Тет.с1аТ.зз.асМгезз, М2): 1ТетЮо0пе$Тг1пд(); ЕпсЮ1а1од(Ь01д, ТВОЕ); геТигп ТРОЕ: сазе ЮСАМСЕЬ: ЕпсЮ1а1од(Ю1д, ЕАЕЗЕ); геТигп ТВОЕ: } Ьгеак: } геТигп ЕАЕЗЕ; } ////////////////////////////////////////////////////////////////////// Информация, которую необходимо хранить в списке и записывать в файл для длительного хранения, представляет собой последовательность записей длиной 144 байта. В этих операциях запись должна интерпретироваться как одна С-строка с завершающим нулевым байтом. В то же время, когда формируется новая запись с помощью диалогового окна Ввод записи, отдельные ее поля получают значения С-строк в результате вызова функции 6еТ01д1ТетТехТ. Поэтому появляется проблема «склеивания» полей для получения одной С-строки. В другой ситуации, когда требу- ется отобразить дополнительную информацию для выбранной в списке записи, воз- никает обратная проблема: извлечь С-подстроку из общей С-строки. Чтобы упростить эти операции, в программе определен класс И$Шет. Для хра- нения записи он содержит член да!, имеющий тип объединения (итоп) 0а1а. С помощью объединения мы можем интерпретировать один и тот же участок па- мяти и как одну строку Ппе, и как структуру 88, содержащую четыре поля, пате, рНопе, Ы1±Нс1ау и ас1с1ге88.
В классе также определен метод 0о0пе51:лпд, склеивающий четыре подстроки в одну строку. Эта подзадача решается путем сканирования массива да!.Ипе от его конца к началу, с заменой всех нулевых байтов, кроме ближайшего к концу масси- ва, символом пробела. В классе также определены метод беЮННдау, выполняю- щий извлечение в виде С-строки значения поля даЕзз.ЫйНдау, и метод Се1:Ас1с1ге88, делающий то же самое для поля с1а1:.$$.ас1с1ге$$. Переменная йет, которая является объектом класса ЕйШегп, объявлена в гло- бальной области видимости программы. Эта переменная используется и для внут- ренних нужд диалоговой процедуры \йем01дРгос, и для обмена данными между диалоговыми процедурами Ас1сШес01’аРгос и \йедоО1дРгос. Диалоговая процедура \/1’едо01дРгос, обслуживающая диалог Ю0_У1ЕМ, выпол- няет самую большую часть работы данного приложения. Блок обработки сообщения ММ_1М1Т01А1_06 содержит следующую функцио- нальность: □ Создается специальный шрифт с дескриптором НЕопИ для использования в окне списка. В программе используется моноширинный шрифт Соппег, что- бы обеспечить правильное расположение границы второго столбца, в котором выводится информация о телефоне. Модификация шрифта в окне НИзЮох ре- ализуется отправкой сообщения ММ_$ЕТЕОМТ. □ Создается кисть Н ВгизН для изменения фона элементов управления НЕс1й1пГо- Ас1с1г и НЕсйНпГоВдау. Эта кисть применяется в блоке обработки сообщения УУМ_СТ1_СО1_ОК5ТАТ1С. □ Инициализируется список Н1_15±Вох. Для этого из файла псйеЬоок.с1а1: считыва- ется информация, и каждая прочитанная строка добавляется в список с помо- щью посылки сообщения 1_В_А00$ТВ1М6. В блоке обработки сообщения ММ_С0ММАМ0 решаются следующие подзадачи: □ Если сообщение поступило от кнопки ЮС_КЕС_А00, то при помощи функции 01’а1одВох вызывается модальный диалог Ю0_А00_КЕС. Работая с этим диало- гом, пользователь вводит информацию для очередной записи. Если ввод за- вершен нажатием кнопки ОК, то функция 01’а1одВох вернет значение ТВОЕ и будет вызвана функция 5епс1Ме$$аде для отправки сообщения 1_В_А00$ТВ1М6. □ Если сообщение поступило от кнопки ЮС_ВЕС_ОЕ1_ЕТЕ, мы определяем, какой пункт списка выделен пользователем. Если такой пункт есть, то отправляем окну списка сообщение 1_В_0Е1_ЕТЕ$ТВ1М6, а также очищаем окна редактирова- ния ЮС_1МЕ0_А00В и ЮС_1МЕО_ВОАУ при помощи функции 5еЮ1д11:етТех1: (в них может отображаться информация для выбранного пункта списка). □ Если сообщение поступило от окна списка ЮС_И5Т1 и оно содержит код уве- домления 1_ВМ_$Е1_СНАМ6Е, то надо получить данные для выделенного пункта 1СигКет. Для этого отправляется сообщение 1_В_6ЕТТЕХТ. Затем поля записи, со- храненной в объекте йет, с информацией об адресе и дне рождения выводятся с помощью функции 5еЮ1д11:етТех1: в окна редактирования ЮС_1МЕ0_А00В и ЮС_1МЕО_ВОАУ. □ Если сообщение поступило от кнопки ЮОК, то приложение записывает содер- жимое списка в файл поЕеЬоок.даЕ.
Диалоговая процедура Ас1с1 Кес01а Ргос очень проста и не требует дополнительных пояснений. Но все же стоит обратить внимание на вызов метода йет.ОоОпеЗЫпд, выполняющего объединение четырех подстрок в одну С-строку. На рис. 7.27 показан внешний вид работающей программы Му 1Мо1еЬоок1. МуЫо1еЬоок1 Рис. 7.27. Просмотр содержимого списка в приложении Му1\1о1еЬоок1 Комбинированный список Комбинированный список (СотЬо Ьох), называемый также комбинированным ок- ном, состоит из окна редактирования и окна списка. В классическом варианте ком- бинированный список выглядит как окно редактирования со стрелкой справа. Если на ней щелкнуть мышью, то появится выпадающий список. Комбинированный список помещается на форму диалога при помощи мыши с предварительным выделением элемента СотЬо Ьох на панели инструментов СопЕго18. Сразу после щелчка на форме диалога элемент СотЬо Ьох выглядит при- мерно так, как показано на рис. 7.28. СотЬо Вох Рис. 7.28. Размещение комбинированного списка на форме диалога Обратите внимание на специальную «редакторскую» рамку вокруг окна эле- мента СотЬо Ьох. Она говорит о том, что элемент управления сейчас находится в выделенном состоянии. Мы уже имели дело с выделением элементов управле- ния, занимаясь выравниванием их размещения на форме диалога с помощью под- меню 1_ауои1:. Но сейчас речь пойдет совсем о другом. Щелкните на стрелке справа, чтобы открыть окно выпадающего списка. Ре- дактор диалога покажет размеры этого окна, растянув прямоугольник рамки вниз. А теперь — внимание! Если размеры окна списка окажутся слишком маленькими и их не будет хватать для отображения хотя бы одного-двух элементов списка, то элемент управления СотЬо Ьох работать не будет! Поэтому растяните мышью ог-
раничивающую рамку окна списка до того размера, который вы хотите увидеть в работающем приложении (рис. 7.29). Рис. 7.29. Установка размеров для выпадающего списка К сожалению, ни в справочной помощи М8В?<, ни в других источниках ин- формации об этой технологической «мелочи» ничего не говорится. Я знаю про- граммистов, которые потеряли много часов, выясняя, почему у них не работает СогпЬо Ьох, пока они не находили ответ случайно в процессе экспериментов либо благодаря подсказке более опытных коллег. После размещения элемента управления СотЬо Ьох на форме диалога вы опре- деляете его атрибуты, вызывая окно свойств СотЬо Ьох РгореПп’ез. На вкладке 6епега1 в поле Ю введите необходимый идентификатор элемента управления. На вкладке 81у1е5 (рис. 7.30) установите дополнительные свойства комбинированного списка. Рис. 7.30. Вкладка 81у1ез в окне СотЬо Вох РгорегЬез Многие свойства СотЬо Ьох аналогичны свойствам элемента управления 1_151 Ьох. Но есть и некоторые различия. Стиль списка выбирается в окне Туре из сле- дующих значений: Стиль Описание 81тр1е Комбинированный список отображается окном редактирования и раскрытым окном списка. Содержание окна редактирования можно изменять Огорс1о\л/п Комбинированный список отображается в свернутом состоянии со стрелкой справа. Содержание окна редактирования можно изменять □гор Ы51: Комбинированный список отображается в свернутом состоянии со стрелкой справа. Содержание окна редактирования изменять нельзя В окне свойств появилась новая вкладка Оа1:а. Разработчики, создающие при- ложения на базе библиотеки классов МЕС, пользуются вкладкой ОаЕа для ввода строк, которыми инициализируется СотЬо Ьох. Редактор диалоговых окон сохра- няет введенные строки в файле описания ресурсов в виде определения ресурса 01С1М1Т. К сожалению, работая с АУш32 АР1, вы не можете использовать подоб- ный способ инициализации списка, так как в этом случае отсутствует поддержка
интерпретации ресурса типа 0Ю1М1Т. Для инициализации комбинированного спис- ка применяется посылка сообщений СВ_А005ТК1М6. Обмен сообщениями с комбинированным списком Обмен сообщениями с комбинированным списком во многом похож на обмен со- общениями с элементами ЕсШ: Ьох и Из! Ьох. При этом идентификаторы сообщений, отправляемых элементу СотЬо Ьох, начинаются с префикса СВ_, а идентификаторы уведомительных сообщений, посылаемых от СотЬо Ьох своему родительскому окну, имеют префикс СВМ_. В табл. 7.11 приведено сопоставление некоторых кодов сообщений, посылае- мых элементу управления СотЬо Ьох, с аналогичными сообщениями, посылаемы- ми элементам управления ЕсН! Ьох и Из! Ьох. Таблица 7.11. Однотипные сообщения для элементов управления ЕсП1 Ьох, 1181 Ьох и СотЬо Ьох Код сообщения для ЕсН( Ьох или 1_1$1 Ьох Код сообщения для СотЬо Ьох ЕМ_СЕТ8Е1_ ЕМ_8ЕТ8Е1_ 1_В_АОО8ТкИ\1С 1_В_ОЕ1_ЕТЕ8ТкИ\1С 1_В_СЕТСО11МТ 1_В_СЕТС1Ж8Е1_ 1_В_СЕТТЕХТ 1_В_8ЕТС1Ж8Е1_ СВ_СЕТЕО1Т8Е1_ СВ_8ЕТЕО1Т8Е1_ СВ_АОО8Тк!1\1С СВ_ОЕ1_ЕТЕ8ТкИ\1С СВ_СЕТСО111\1Т СВ_СЕТС1Ж8Е1_ СВ_СЕТ1_ВТЕХТ СВ_8ЕТС1Ж8Е1_ Полный список сообщений, посылаемых элементу СотЬо Ьох, можно найти в справочных материалах М8ВК Всего существует более шестидесяти подобных сообщений. В табл. 7.12 приведено аналогичное сопоставление некоторых кодов уведоми- тельных сообщений. Таблица 7.12. Однотипные уведомительные сообщения от ЕсН1 Ьох, 1181 Ьох и СотЬо Ьох Код сообщения от ЕсН( Ьох или 1_1$1 Ьох Код сообщения от СотЬо Ьох Е1\1_8ЕТРОС118, 1_ВМ_5ЕТРОС115 ЕМ_К11_1_РОС118, 1_В1\1_К11_1_РОС118 Е1\1_11РОАТЕ Е1\1_СНА1\1СЕ Е1\1_ЕКК8РАСЕ, 1_В1\1_ЕКК8РАСЕ 1_В1\1_8Е1_СНА1\1СЕ СВ1\1_8ЕТРОС118 СВ1\1_К11_1_РОС118 СВМ_ЕО1Т11РОАТЕ СВ1\1_ЕОГГСНА1\1СЕ СВ1\1_ЕКК8РАСЕ СВ1\1_8Е1_СНА1\1СЕ Пример использования элемента управления СотЬо Ьох Сделаем небольшую доработку предыдущего приложения. Добавим возможность для пользователя выбирать размер шрифта в окне списка нашей электронной за- писной книжки. Нам будут очень признательны те пользователи, у которых есть проблемы со зрением и которым трудно читать мелкий шрифт.
Для выбора размера шрифта используем элемент управления СотЬо Ьох. В но- вой программе кроме изменения шрифта, используемого в окне списка ЮС_1_15Т1, необходимо изменять ширину этого окна так, чтобы в нем помещались первые два поля каждой записи. Кроме того, должны регулироваться размеры диалого- вого окна и позиции других элементов управления в зависимости от используе- мого шрифта. Проблему изменения размеров и позиции окна приходится решать в самых разных приложениях. Поэтому мы разработаем функцию ЗЫ’ШЛй пс1о\л/ общего при- менения, код которой будет размещен в одном файле с реализацией класса К\Л/пс1. Прежде всего создайте новый проект с именем МуМо1:еЬоок2. Скопируйте из папки проекта МуМо1:еЬоок1 (см. листинг 7.4) в папку проекта МуМо1:еЬоок2 файлы с расширениями .срр, .К и .гс, скорректировав их имена заменой подстроки МуМо1:еЬоок1 на МуМо1:еЬоок2. Измените имена файлов КМпсЬЬ, К\Л/пс1.срр, содержа- щих интерфейс и реализацию класса К\Л/пс1, на имена К\Л/пс1 Р1и1.К, К\Л/пс1 РШКсрр1. Добавьте все перечисленные файлы в состав проекта. Откройте шаблон диалога Ю0_\/1ЕМ двойным щелчком мыши на соответствую- щем значке на вкладке Кезоигсе \/1’е\л/ в окне Могкзрасе. Добавьте на форму диалога (рис. 7.31) элементы управления 51а1:1’с Тех! (с текстом Шрифт:) и СотЬо Ьох. 8!а!ю Тех! СотЬо Вох Рис. 7.31. Размещение на форме диалога новых элементов управления В окне свойств элемента СотЬо Ьох укажите идентификатор ЮС_ЕОМТ. На вклад- ке 51у1е$ выберите стиль списка Огор Из! и сбросьте флажок 5ог1. Измените текст в файлах К\Л/пс1 Р1и1.К, К\Л/пс1 Рйй.срр и МуМо1:еЬоок2.срр так, чтобы он соответствовал листингу 7.5. Листинг 7.5. Проект Му1\1о1еЬоок2 ////////////////////////////////////////////////////////////////////// // К1л1ПС1Р1иТ.И #1пс1ис1е <\л/1 пс1ом8. И> с1аз8 К1л/пс1 { /* Текст из листинга 1.2 */ }; 1 Р1и! — р1из ЫлШТез.
Листинг 7.5 {продолжение} II Утилиты общего применения //========================================== // Сдвиг окна верхнего уровня с модификацией размеров уо1с1 861Ш1 пс1ом(И 6шс1, 1пб с1Х = 0, 1пб (IV = О, 1пб с11л/ = 0, 1пб с1Н = 0); // Сдвиг дочернего окна с модификацией размеров уо1с1 861Ш\11 пс1о\л/(Ш 6С6116, НШО бРагепб, 1пб с1Х = О, 1пб (IV = 0, 1пб с11л/ = 0, 1пб с1Н = 0); // Сдвиг окна элемента управления с модификацией размеров уо1с1 861Ш\11пс1ом(1пб сбгПО, НШО бРагепб, 1пб с1Х = О, 1пб (IV = 0, 1пб с11л/ = 0, 1пб 6Н = 0); ////////////////////////////////////////////////////////////////////// // К1л1пс1Р1иб.срр #1пс1ыс1е "К1л1пс1Р1иб.6" К1л1пс1: :К1л1пс1(1РСТ8ТР \л/1 псЫМате, Н1М8ТА1Х1СЕ 61п8б, 1пб стс186ом, ЕРЕ8СЕТ (ШИАР1 *рШс1Ргос) (НШО, СIИТ,МРАЕАМ, 1РАЕАМ), ЕРСТ8ТР тепиМате, 1пб х, 1пб у, 1пб \л/1666, 1п6 6е1д6б, Ы1МТ С1а888бу1е, 0ШР0 \л/1 пс1ом8бу 1 е, НШО бРагепб) { /* Текст из листинга 1.2 */ } 11====================^^ II Утилиты общего применения //========================================== // Сдвиг окна верхнего уровня с модификацией размеров уо1с1 861Ш1пс1ом(ШМ0 6мпс1, Ш 6Х, Ш (IV, Ш с11л/, Ш сШ) { РЕСТ гесб; СеШ пбомРесб (6мпс1, &гесб); 1п6 хО = гесб.1еТб + 6Х; Ш уО = гесб.бор + (IV; 1п6 \л/Ш6 = гес6.г1д66 - гесб.1еТб + с11л/; 1п6 6е1д6б = гесб.боббот - гесб.бор + 6Н; МоуеШпс1о\л/(6\л/пс1, хО, уО, \л/Ш6, 6е1д6б, ТРЕЕ); } // Сдвиг дочернего окна с модификацией размеров уо1с1 861Ш1пс1ом(ШМ0 6С6116, НШО бРагепб, Ш 6Х, Ш (IV, Ш с11л/, Ш сШ) { РЕСТ гесб; Р01ИТ рО; СеШ пс1омРесб( 6С6116, &гесб); 1п6 \л/1666 = гес6.г1д66 - гесТ.1еГ6 + с11л1; Ш 6е1д6б = гесб.боШит] - гесТЛор + 6Н; рО.х = гесб.1еТ6 + 6Х; рО .у = гесТЛор + (IV; 8сгеепТоС11еп6(6Рагеп6, &р0); Моуе1л11 пс1о\л/(6С6116, рО.х, рО.у, мШ6, 6е1д6б, ТРЕЕ); } // Сдвиг окна элемента управления с модификацией размеров уо16 861 Т61л11 пбо\л/( 1 пб сбгПО, НШО бРагепб, Ш 6Х, Ш (IV, Ш 61л1, Ш сШ) { РЕСТ гесб; НШО 6Сбг1 = СеШд 1бет(6Рагепб, сбгПО);
РО1МТ рО; СеШпсЫВесШСТг! , &гесТ) ; 1пТ \л/1 сИзН = гесТ.г1д1П - гесТЛеН + Ш; 1пТ Не1д1п1з = гесТ.ЬоНот - гесТ.Тор + 6Н; рО.х = гесТЛеН + 6Х; рО.у = гесТ.Тор + с1У; ЗсгеепТоСИепШРагепТ, &р0); $Ио\л/1л11 пс1о\л/( ЬСТг!, 81л/_НЮЕ); Моуе1лНпс!о\л/(НС1зг 1, рО.х, рО.у, \л/1 сТЕИ, Ие1д1тЕ, ТВОЕ); ЗЕю\лДл/1 пс1о\л/( ИСТг1 , 31л/_ЗНОЮ ; } ////////////////////////////////////////////////////////////////////// // МуМоТеЬоок2.срр #1пс1ис1е <\л/1 пс1о\л/8. Ь> #1пс1ис1е <ТзТгеат> изапд патезрасе зТс1; #1пс1ис1е " К1л/пс1Р1ыТ. И" #1пс1ис1е "гезоигсе.Ь" #с1еПпе М1 21 #с!еТ1 пе М2 81 #с1еГ1пе Т0ТАЕ_317Е (3*И1 + М2) ТзТгеат НаТ; зТгисТ 8иЬ8Тг { /* Текст из листинга 7.4 */ }; ит’оп ВаТа { /* Текст из листинга 7.4 */ }: с!азз ЫзШет { /* Текст из листинга 7.4 */ }: ЫзШет Пет; 1 пТ ТопТНе1дНШ = { 16, 20, 24, 28, 30 }; 1пТ 1ЕопТ = 0; // текущий шрифт для окна ЫзТ Ьох ВООЕ САЕЕВАСК VIем01 дРгос(ШМО, ШМТ, ПРАВАМ, ЕРАВАМ); ВООЕ САЕЕВАСК А(ШесВ1 аРгос(НШВ, ШМТ, ПРАВАМ, ЕРАВАМ); уо1с1 Ас1дизШа!од81 хе(ШМВ ЬВ1д); ЕВЕЗОЕТ САЕЕВАСК ШЬРгос(НШВ, ЮТ, ПРАВАМ, ЕРАВАМ); //============================= 1пТ ШМАР1 1лМпМа1п(Н1МЗТАМСЕ ЫпзТапсе, Н1МЗТАМСЕ ЬРгеу1пзТапсе, ЕРЗТВ ОрСтбЫпе, 1 пТ пСтс13Ио\л/) { МЗО тзд; К1л1пс1 та1п1л1псК"МуМоТеЬоок2", ЫпзТапсе, пСтс13Ьом, 1л1пс1Ргос, МАКЕ1МТВЕ300ВСЕ(ЮВ-МЕМЫ), 100, 100, 400, 300); \л/М1е (СеТМеззаде(&тзд, МЫЕЕ, 0, 0)) { Тгапз1аТеМеззаде(&тзд);
Листинг 7.5 {продолжение} 018рабсНМе88аде(&т8д); } гебыгл глад. \л/Рагат; } /======================= 0РЕ300Т САЕЕВАСК ОлбРгос(НОМО Шпс1, 1ИМТ иМзд, ОРАВАМ мРагат, ЕРАРАМ 1 Рагат) { /* Текст из листинга 7.4 */ } //========================================== // Настройка размеров диалога и размещение элементов управления // по метрикам шрифта, установленного в окне списка уоЮ Абци8б01 а!одЗпхе(Н0И0 Н01д) { 1лб лСбг11с1[] = { ЮС_6В00Р_1, ЮС_8ТАТ1С_А00В, ЮС_8ТАТ1С_В0АУ, 10С_1МЕ0_А00В, 10С_1 ИЕО_ВОАУ, ЮОК, ЮСАМСЕЕ, ЮС_ВЕС_А00, 10С_ВЕС_0ЕЕЕТЕ }; НШО НЕЮТВох = 6ебО1дЮет(НО1д, ЮС_ЕЮТ1); 8баб1с ЕОЗЕОИТ 1Т; 8баб1с НЕОМТ НЕолТ1; НОС НОС; ТЕХТМЕТР1С Ш 1лТ зНомТехТОШН; // длина текста (в пикселах), показываемого // в окне списка ВЕСТ гсЕВ; // первоначальное размещение окна списка 1лТ 8Н1ТТ; // сдвиг правой границы окна списка для // отображения текста длиной зНомТехТОШН // Модификация шрифта для элемента ПЕ1зТВох 1Т (НЕопШ ОеШеОЬцесШЕопШ; 1Т.1ТНе1дНТ = ТолТНе1дНТ[1Еолб]; 18бгсру( (ЕРЗТЮ&! Т.! ТЕасеИагпе, "Соыг1ег" ); НЕолб! = СгеабеЕолТ1пс11гесб(&1Т); Зелс1Ме88аде(НЕ18бВох, 0М_8ЕТЕ0ИТ, (ОРАВАМ)НЕолИ, ТВОЕ ); // Модификация формы диалога с учетом размера шрифта, // используемого в окне списка НОС = 6ебОС(НЕ18бВох); 3е1 есбОЬцесб(НОС, НЕопШ; 6ебТехТМебг1с8(Н0С, &Тт); Ве!еазеОССНЕ18бВох, НОС); 6еб01пбомВесб(НЕ18бВох, &гсЕВ); зНомТехТОШН = Тт.ТтАуеСНагОШН * 2 * И1 + 10; 8Н1ТТ = зНомТехТОШН - (гсЕВ.г1дПТ - гсЕВЛеГТ); 8Н1 ТТО1 лс1о\л/(НО!д, 0, 0, 8Н1ТТ); ЗН1ПО1 пс1о\л/( ЮС_Е13Т1, НО!д, 0, 0, 8Н1П); ЗН1ТТО1 пбом( 10С_6ВС0Р_0, НО!д, 0, 0, 8Н1ТТ); Тог (1пб 1 =0; 1 < 81хеоТ(пСбг!Ю) / 81хеоТ(1пб); ++1) ЗН1Ш1пс1ом(пСбг! Ю[1], НО!д, 8Н1ТТ); 8Н1Ш1 пс1о\л/( 10С_ЗТАТ1 С_РНОМЕ, НО!д, 8Н1П / 2); }
//============================ В001_ САЕЕВАСК 71ем01 дРгос(НШ0 601д, 1ЛМТ иМзд, ИРАРАМ мРагат, ЕРАРАМ 1Рагат) { зТаЫс НШО ЬЫзОВох, бСотЬоЕопО; зТаЫс ШИВ 6Еб1ЫпТоАббг, 6Еб1Ып0оВбау; НШВ 6СОг 1 ; СОЬОРРЕЕ еб1Ып0оСо1ог; зТаЫс НВРС13Н бВгизб; 1п0 пЮетз; // количество элементов в списке 1п0 ЮигТОет; // текущий выбранный элемент списка ВООЕ сИдУез; 1 пО 1; сбаг Ьи0[10]; змПсб (иМзд) { сазе ИМ_1б1Т01АЕ06: бЫзОВох = 0е001д 10ет(601 д, ЮС_Ы8Т1); бСотЬоЕопО = 0е001д10ет(601д, ЮС_С0МВ0_Е0бТ); НЕст1пТоАс1с1г = 6еШ д!0ет(601 д, ЮС_1бЕ0_А00Р); 6Еб1Ып0оВбау = 6еШ д!0ет(601 д, ЮС_1бЕ0_В0АУ); // Создание кисти для фона элементов управления еб1Ып0оСо1ог = Р6В(190, 255, 255); бВгизб = Сгеа0е8о1 1бВгизб(еб1Ып0оСо1ог); // Настройка формы диалога Абдиз001 а!од81 ге(601 д); // Инициализация комбинированного списка Со г (1 =0; 1 < 812еоС(СопСНе1д6С) / зпгеоТ( 1 гтб); ++1) { зрг1пСС(ЬиС, ’7б", СопСНе1д6С[1]); 8епбМеззаде(6СотЬоЕопС, СВ_А008ТРЖ, 0, (ЕРАРАМ)ЬиТ); } 8епб01д10етМез8аде(601д, ЮС_С0МВ0_Е0бТ, СВ_8ЕТС0Р8ЕЕ, 1ЕопС, 0); // Чтение "базы данных" из файла и инициализация списка СбаС. ореп ("побеЬоок.баС", 1 оз:: 1 п); 10 (!0баС) { СбаС.с1еаг(); геТигп ТРОЕ; } е!зе м611е (1) { ТбаТ. деЫ1 пе( 1 бет. баб. 11 пе, зЖоССОаСа)); 10 (ОбаТ.еоОО) { СбаС.с1еаг(); Ьгеак; } ЗепбМеззадеСбЫзОВох, ЕВ_А008ТР1 60, 0, (ЕРАРАМ Шет. баб. 11 пе); } ОбаО.с!озе(); геТигп ТРОЕ; сазе 1л/М_СТЕС0Е0Р8ТАТ1С: /* Текст из листинга 7.4 */ Ьгеак; сазе Ш-С0ММА60: змЮсб (ЕОШРО(мРагат)) { сазе ЮС_РЕС_А00: /* Текст из листинга 7.4 */
Листинг 7.5 {продолжение} Ьгеак; сазе ЮС_ЕЕС_ОЕЬЕТЕ: /* Текст из листинга 7.4 */ Ьгеак; сазе ЮС_Ы8Т1: /* Текст из листинга 7.4 */ Ьгеак; сазе ЮОК: /* Текст из листинга 7.4 */ геТигп ТЕОЕ; сазе ЮСАМСЕЬ: Епс101а1од(Ь01д, ГАЕЗЕ); геТигп ТЕОЕ; сазе ЮС-СОМВО-ЕОМТ: з\л/1ТсЬ (НШОЮЫРагат)) { сазе СВМ-ЗЕЕСНАМЗЕ: // Выбираем используемый шрифт 1ЕопТ = 8епс1Меззаде(ЬСотЬоЕопТ, СВ_ЗЕТСЕ1К8ЕЕ, 0, 0); // Настройка формы диалога АбдизТВ1а!одЗпге(ЬВ1д); геТигп ТВОЕ; } Ьгеак; } Ьгеак; } геТигп ЕАЕЗЕ; } //========================= ВООЕ САЕЕВАСК АсШесЮ аРгос(НШ0 Ь01д, ОЮТ иМзд, ОРАВАМ мРагагл, ЕРАВАМ 1 Рагат) { /* Текст из листинга 7.4 */ } ////////////////////////////////////////////////////////////////////// Обрабатывая уведомительное сообщение СВМ_$Е1_СНАМ6Е, мы посылаем комби- нированному списку сообщение СВ_6ЕТС11В5Е1_, чтобы узнать, какой элемент спис- ка был выбран пользователем. Индекс выбранного шрифта сохраняется в глобаль- ной переменной 1’ЕопТ. После этого вызывается новая функция Аф’и$Т01’а1од512е, которая не использовалась в предыдущем приложении. Она выполняет всю ру- тинную работу по настройке формы диалога. Во-первых, функция устанавливает выбранный шрифт в окне списка НИзТВох. Во-вторых, она изменяет размеры диа- логового окна и окна списка НИзТВох с учетом метрик выбранного шрифта так, чтобы в окне списка отображались только первые два поля каждой записи. В-тре- тьих, она изменяет размещение по горизонтали некоторых элементов управле- ния, чтобы их позиция согласовывалась с шириной окна списка. Для выполнения описанной работы функция Аф изЮтаЕодЗтге пользуется услу- гами новой перегруженной функции пс1о\л/. Прототипы функции $Н1’ГВЛ/1’пс1о№
объявлены в файле К\Л/п с1Р1и1:. Н, а реализация перегруженных версий функции оп- ределена в файле К\Л/пс1 Р1и1.срр. Первая версия функции 5ЫГ1:\Л/1’пс1одо с пятью параметрами предназначена для перемещения и изменения размеров окна верхнего уровня ({ор-1еъе1 иу1п(1оиу}. Деск- риптор окна Ндоп с1 передается в качестве первого параметра. В нашей программе эта версия функции вызывается для изменения размеров диалогового окна Н 01д. Вторая версия функции пдодо с шестью параметрами предназначена для перемещения и изменения размеров дочернего окна. Первый параметр этой функ- ции имеет тип Н\Л/М 0. Он принимает дескриптор дочернего окна. Второй параметр позволяет задавать дескриптор родительского окна. В реализации функции учи- тывается, что функция 6еВЛ/1’пПодоКес1 определяет позицию окна, выраженную в эк- ранных координатах. В то же время функция Мсл/еМ1’пс1одо работает с экранными координатами, если применяется к окну верхнего уровня, и с клиентскими ко- ординатами, если применяется к дочернему окну. Необходимое преобразование экранных координат к клиентским координатам достигается вызовом функции ЗсгеепТоСПеп!. Данная версия функции в этом приложении не используется, но может быть применена, например, для немодальных диалоговых окон. Третья версия функции ЗЫШЛй’пдодо предназначена для перемещения и изме- нения размеров дочерних окон элементов управления. Она тоже принимает шесть параметров, но первый параметр имеет тип 1П1. Первый параметр этой функции позволяет указывать идентификатор элемента управления, а второй параметр — дескриптор родительского окна. На рис. 7.32 показан внешний вид работающей программы МуМо1:еЬоок2, когда в окне СотЬо Ьох выбран шрифт размером 24 логические единицы. Рис. 7.32. Просмотр списка в приложении Му1\1о1еЬоок2. Установлен размер шрифта 24 Обратите внимание на то, что файлы К\Л/пс1Р1и1:.Ь, К\Л/пс!Рйй.срр вы можете вклю- чать и в другие проекты. В этом случае помимо класса К\Л/пс1 в вашем распоряже- нии оказывается также перегруженная функция $Ь1’Г1:\Л/1’пс1одо. До сих пор мы изучали применение элементов управления, размещая их в мо- дальном диалоговом окне. В принципе, подобным же образом они могут работать и в составе немодального диалогового окна. Однако создание немодального диа- логового окна и работа с ним несколько отличаются от создания и обработки мо- дального диалога.
Немодальный диалог Немодальные диалоговые окна позволяют пользователю переключаться между диалоговым окном и окном, в котором оно было создано. Окна этого типа больше напоминают обычные всплывающие окна, которые могут создаваться программой. Немодальные диалоговые окна лучше использовать, когда пользователь работает с ними и с основным окном одновременно. Пожалуй, самые распространенные не- модальные окна — это окна инструментов поиска Ипс1 и Кер1асе, отображаемые про- граммами обработки текстов. Пользователю может потребоваться оставить такой диалог в активном состоянии, если нужно выполнить несколько операций поиска и замены. При отображении этого окна можно также редактировать документ, в котором выполняется поиск или замена. Иногда немодальное диалоговое окно создается в момент старта приложения и может оставаться на экране до окончания работы приложения. Различия между модальными и немодальными окнами диалога Мы уже знаем, что модальные диалоговые окна создаются при помощи функции 01’а1одВох. Эта функция возвращает управление только после закрытия диалогово- го окна. Немодальные диалоговые окна создаются с помощью функции Сгеа1е01 а 1од. Она принимает такие же параметры, что и функция 01’а1одВох: ШдМос1е1е88 = СгеаШ1а1од(И1п8^ МАКЕ1МТЕЕ801ЖЕ(100_0Е0), Шпс1, 01дРгос); то есть функции передаются дескриптор экземпляра приложения, идентифика- тор шаблона диалога, дескриптор родительского окна и адрес диалоговой проце- дуры. Различие состоит в том, что функция Сгеа1е01а1од сразу возвращает дескриптор диалогового окна. Как правило, этот дескриптор хранится в глобальной переменной. Определяя свойства шаблона диалогового окна, обязательно установите фла- жок У1$1Ые на вкладке Ноге 81у 1е$ окна 01 а1од РгореПп’ез. Если этот флажок сброшен, то для появления окна на экране потребуется после вызова функции Сгеа1:е01’а1од дополнительно вызвать функцию $Ном\Л/1’пс1о№: 8Иой\П пс1о\л/( И01 дМобе! е88, 81л1_8Н0Ю ; При сброшенном флажке У1$1Ые и отсутствии указанного вызова функции 8Но\л/\ЛЛпс1о\л/ немодальный диалог вообще не появится на экране. Модальное диа- логовое окно менее притязательно: оно появляется на экране и при сброшенном флажке У1$1Ые. В отличие от сообщений для модальных диалоговых окон, направляемых сис- темой непосредственно менеджеру диалогового окна, сообщения для немодаль- ного диалогового окна проходят через очередь сообщений программы. Поэтому цикл обработки сообщений в теле функции М1пМа1п должен быть приведен к сле- дующему виду: \л/М1е (6еШэ88аде(&т8д, ШЕЕ, 0, 0)) { 1Т (! 1801а1одМе88аде(ИМос1е1е8801д, &тад)) { ТгапзШеМеззадеС&тзд); 018ра^сИМе88аде(&т8д); } }
Функция 1$01’а1одМе$$аде определяет, относится ли сообщение, сохраненное в переменной тзд, к диалоговому окну КМос1е1е8801д. Если да, то функция обраба- тывает это сообщение, отправляя его диалоговой процедуре и возвращая значе- ние ТРОЕ. В ином случае функция ничего не делает с сообщением и возвращает значение ЕА15Е. Сообщения, переданные на обработку диалоговой процедуре, не должны об- рабатываться функциями Тгап$1а1:еМе$$аде и Оп’зраЕсНМеззаде. Это обеспечивает ус- ловный оператор 1’Г, анализирующий код возврата функции 1$01’а1одМе$$аде. Заме- тим, что если немодальное диалоговое окно еще не создано, то дескриптор НМос1е1е$$01д должен быть равен нулю и в этом случае функция 1$01’а1одМе$$аде тоже возвращает значение ЕА1_8Е. Для закрытия немодального диалогового окна вместо функции ЕпсЮ1’а1од вы- зывается функция ОезЕгоу\Л/1 п до\л/. Если диалог закрывается, а приложение продол- жает работать, то рекомендуется дескриптор ЬМос1е1е$$01д установить в нулевое значение. Если немодальное диалоговое окно имеет заголовок, в котором размещается кнопка закрытия окна, то пользователь по привычке может попытаться закрыть диалог с помощью данной кнопки. Менеджер немодального диалогового окна не обрабатывает сообщение ММ_С1_О5Е. Поэтому реакция приложения будет зависеть от решения программиста. Если вы считаете, что окно можно закрыть, то в диало- говую процедуру нужно добавить следующий код: сазе ЫМ_С1_08Е: ОезЬгоуШ пс1о\л/( И01 д); ИМос1е1е8801д = 0; Ьгеак; Это же сообщение будет обрабатываться в случае выбора команды С1озе (АЙ+Е4) в системном меню диалогового окна. Пример использования немодального окна диалога В приложении Мос1е1е8501д, которое мы сейчас разработаем, используется немо- дальное диалоговое окно, содержащее три полосы прокрутки. При помощи этих полос пользователь может изменять цвет фона в клиентской области основного окна. Создайте новый проект типа \Лйп32 АррПсаВоп с именем Мос1е1е$$01д. Скопируй- те в папку проекта файлы К\Л/пс1 Р1иЕН, К\Л/пс1 РЕиЕ.срр из папки проекта МуМо1:еЬоок2, после чего добавьте их в состав нового проекта. Включите в приложение ресурс диалогового окна с идентификатором Ю0_ М00ЕЕЕ88 и заголовком Цвет фона. На вкладке 8Еу1е8 окна О1’а1од РгорегВез нужно выбрать стиль окна СН11д, а на вкладке Ноге 81у1е5 установить флажок У1$1Ые. Ос- тальные свойства окна оставьте со значениями по умолчанию. Удалите с заготовки диалогового окна кнопки ОК и Сапсе1. Поместите на форму диалога надписи Кес1, Бгееп и В 1ие, а под ними — элементы управления типа УегВса15сго11 Ваг (рис. 7.33). Для полос прокрутки задайте, соответственно, идентификаторы ЮС_КЕ0, ЮСДЖЕЕИ, ЮС_В1_11Е. В окне свойств каждой полосы прокрутки должны быть ус- тановлены флажки У1$1Ые и ТаЬ 81ор.
Рис. 7.33. Размещение элементов управления на форме диалога ЮО_МООЕ1_Е88 Добавьте к проекту файл Мос1е1е$$01д.срр, текст которого приведен в лис- тинге 7.6. Листинг 7.6. Проект Мос1е1е88О1д ////////////////////////////////////////////////////////////////////// // Мобе!еззО!д.срр #1пс!ибе <\л/1 пс1о\л/8. И> #1пс1ибе " К1л/пбР1 ыб. И" #1пс!ибе "гезоыгсе.Н" опыт ОзегМзд { 0М_СНАМ6Е = ИМ_08ЕВ+1 }; НИМО ИМобе! еззО! д; РЕСТ гсИогк; // прямоугольник рабочей области Ш гдЬЕЗ]; // интенсивность для В-, 6-, В-компонентов цвета ВООЕ САЕЕВАСК Мобе!езз01 дРгос(НИМО, 01ИТ, ПРАВАМ, ЕРАРАМ); уоЮ АОцызШдР1 асе(НИМО, НЮ); ЕРЕЗОЕТ САЕЕВАСК ИпОРгос(НЮ, 01МТ, ПРАВАМ, ПРАВАМ); //============================================ Ш И1МАР1 ШпМа1п(НIМЗТАМСЕ ГПпзбапсе, Н1М8ТАМ1СЕ КРгеуШОапсе, ЕРЗТВ ПрСглбЕппе, Ш пСпкОом) { М86 тзд; КИпО та1пИпсК"Мос1е1е8801д", КТпзбапсе, пСтсОом, ИпОРгос, МОЕЕ, 100, 100, 400, 300); \л/Гп'1е (ОебМеззадеС&тзд, МОЕЕ, 0, 0)) { 1Г (!1з01а!одМеззаде(ИМобе!еззО!д, &тзд)) { ТгапзШеМеззадеС&тзд); ОтзраШМеззадеС&тзд); } } гебигп тзд.мРагат; } //========================= ЕРЕЗОЕТ САЕЕВАСК ИпОРгос(НИИО Шпс1, 01МТ иМзд, ПРАВАМ мРагат, ПРАВАМ 1 Рагат)
{ 8\л/1ТсИ (иМзд) { сазе 1л/М_СкЕАТЕ: ЬМос1е1 еззО!д = Сгеа1;е01 а!од(6е1аМос1и1 еНапсПе(ШЕЕ), МАКЕ1МТЕЕ500ЕСЕ( 100-М00ЕЕЕ55), Шпс1, Мос1е168801 дРгос); Абди81.01 дР1 асе(Шпс1, НМос1е168801 д); Ьгеак; сазе ИМ_312Е: Ас11изТВ1дР1асе(Шпс1, НМобе!езз01 д); Ьгеак; сазе ОМ-СНАМСЕ: // Изменение цвета фона основного окна 8еТС1аззЕопд(Шпс1, ССЕ_НВВВАСКСВОВМВ, (ЬСИС)СгеаТеЗо! 1 ВВгизЬ( ВСВ(гдЬ[0], гдЬ[1], гдЬ[2]))); 1пуа11 с1аТеВесТ(Шпс1, &гсИогк, ТРОЕ); Ьгеак; сазе ИМ_ВЕ8ТВ0У: ВезТгоуЫ пс1ом(ЬМос1е1 езз01 д); РозТ0и1 ТМеззаде(0); Ьгеак; с1еТаи!Т: геТигп ВеТИ1пЬомРгос(Шпс1, иМзд, мРагат, 1Рагат); } геТигп 0; } //========================^ ВООЕ САЕЕВАСК Мобе!езз01 дРгос(НШВ И01д, ЫМТ иМзд, НРАВАМ мРагат, ЕРАВАМ 1Рагат) { НИИВ НСТг!; 1пТ 1СТГ11В, 11пбех; ШИВ ЬВуаЫе = СеТВ! д 1Тет(001 д, 10С_ВЕ0); ШИВ ЬСуаЫе = СеТВ!д1Тет(НВ1 д, 1ВС_СВЕЕИ); ШИВ ЬВуаЫе = СеТВ! д 1Тет(НВ1 д, 1ВС_ВЕ0Е); зтТсЬ (иМзд) { сазе Ш_1М1ТВ1АЕ00: ЗеТЗсго!1Вапде(ЬВуа1 ие, 8В_СТЬ, 0, 255, ЕАЕЗЕ); 8еТ8сго11Роз(ЬВуа1ие, 8В_СТЬ, 0, ЕАЕЗЕ); 8еТ8сго11Вапде(ГО/а1ие, ЗВ_СТЕ, 0, 255, ЕАЕЗЕ); 8еТ8сго11Роз(Н0уа1 ие, ЗВ_СТЕ, 0, ЕАЕЗЕ); ЗеТЗсго!1Вапде(ЬВуа1ие, ЗВ_СТЕ, 0, 255, ЕАЕЗЕ); ЗеТЗсго!1Роз(ЬВуа1ие, ЗВ_СТЕ, 0, ЕАЕЗЕ); геТигп ТВОЕ; сазе 1л/М_73СВ0ЕЕ: ЬСТг! = (ШМВ)1 Рагат; 1СТГ11В = СеТВ1дСТг11В(ЬСТг1); продолжение
Листинг 7.6 {продолжение} Ппс1ех = 1СЬг1Ю - 10С_ВЕ0; зтбсЬ(Е0ШВ0(мРагат)) { сазе 5В_Е1МЕЫР: гдЬ[11пс1ех] = тах(О, гдЬ[11пс1ех] - 1); Ьгеак; сазе 8В-ЫМЕ00Ш: гдЬ[ 11пс1ех] = т1п(255, гдЬ[1 Мех] + 1); Ьгеак; сазе 8В_РА6ЕЬР: гдЬЕ 11пс1ех] -= 15; Ьгеак; сазе 8В_РА6Е00Ш: гдЬ[1 Мех] += 15; Ьгеак; сазе 8В_ТНЬМВТВАСК: гдЬ[1 Мех] = НИлЮРВ(мРагат); Ьгеак; } 8еЬ8сго11 Роз(ЬСЬг1, 5В_СТЕ, гдЬ[11пс1ех], ТРОЕ); // Сообщение родительскому окну об изменении цвета фона 8епс1Меззаде(6еЬРагепШВ1д), ЬМ_СНАМ6Е, О, 0); Ьгеак; } гебыгп ЕАЕ8Е; } //========================= уо1с1 АбцызШдР1 асе(НШВ ЬРагепЬ, ШИВ ЬВ1д) { ВЕСТ гсРагепТ, гсВ1д; // Установка размеров окна ЬВ1д 6еТС11епТВесШРагепТ, йгсРагепТ); СеТШ псЫРесТ(ЬВ1 д, &гсВ1 д); 1 пТ \л/1 с1ТЬ = гсВ1 д. г1 дЬТ - гсВ1 д. 1 еТТ; 1пТ Ье1дЬТ = гсВ1д.ЬоТТот - гсВ1д.Тор; 1 пТ с1Н = гсРагепТ.ЬоТТот - Нел дЬТ; 8ЫШ1пбом(ЬВ1д, ЬРагепТ, 0, 0, 0, сШ); // Установка размеров рабочей области для окна ЬРагепТ гс1л!огк = гсРагепТ; гс1л!огк.1еГЬ += \л/1сИсИ; } ////////////////////////////////////////////////////////////////////// Немодальное диалоговое окно НМос1е1е$$01д с шаблоном Ю0_М00Е1_Е5$ создает- ся в оконной процедуре \Л/пс1Ргос при помощи вызова функции Сгеа1:е01’а1од, кото- рый размещается в блоке обработки сообщения ММ_СКЕАТЕ. После этого вызывается функция АфизЮЕдР1асе, которая регулирует высоту диалогового окна таким образом, чтобы она совпадала с высотой клиентской области главного окна. Для решения этой подзадачи вызывается вторая версия перегруженной функции 5МШ/У1 пдо\л/. Кроме того, функция АфизЮЕдР1асе вычис- ляет прямоугольник рабочей области главного окна, к которой относится вся клиентская область, за исключением участка, закрытого диалоговым окном. Этот
прямоугольник гс\Л/огк используется оконной процедурой \Л/пс1Ргос при вызове функции 1пуа11’с1аЕеКесЕ, чтобы ограничить обновляемый регион только видимой частью клиентской области. Диалоговая процедура Мос1е1е5501дРгос осуществляет обработку сообщений от полос прокрутки ЮС_КЕ0, ЮС_6КЕЕМ и ЮС_ВШЕ. Элементы управления типа \/еПасаI 5сгоII Ваг посылают родительскому окну такие же сообщения, как и вертикальная полоса прокрутки главного окна приложения. Пример обработки этих сообще- ний мы уже рассматривали в приложении ТехЕ\йемег (см. листинг 2.2). В результате обработки сообщений от полос прокрутки изменяются значе- ния глобального массива гдЬ, в котором хранятся текущие величины для КСВ- компонентов цвета. Если изменилось хотя бы одно из значений, то диалоговая процедура при помощи функции 5епс1Ме$$аде посылает пользовательское сооб- щение 11М_СНАМ6Е, адресуя его оконной процедуре родительского окна. Константа 11М_СНАМ6Е является значением перечисляемого типа ОзегМзд, который опреде- лен оператором епит. Оконная процедура \Л/пс1Ргос содержит код обработки сообщения 11М_СНАМ6Е. В этом блоке вызывается функция ЗеЕСЕазз 1_оп д для изменения цвета фона главно- го окна приложения. Чтобы окно перерисовалось, нужно также вызвать функцию 1пуа11’с1аЕеКесЕ. Обратите внимание на то, что если в качестве второго аргумента функции 1пуа11’с1аЕеКесЕ передать МОП, то при перерисовке возникнет неприятное мерцание фона диалогового окна. Уничтожение диалогового окна происходит в блоке обработки сообщения ММ_ОЕ$ТКОУ. Последнее замечание: если для диалогового окна Ю0_М00Е1_Е5$ на вкладке ЗЕу 1ез окна О1’а1од РгорегВез задать стиль Рорир, то в теле функции Аф изЕ01дР 1асе для уста- новки позиции окна следует вызывать первую версию перегруженной функции $Н1’Ш/\йпс1о№: $М Ш\11 пс1о\л/( И01 д, 0, 0, 0, с1Н); Окно работающего приложения МодеЕеззОЕд показано на рис. 7.34. Рис. 7.34. Приложение Мос1е1е55О1д К сожалению, черно-белый рисунок не может передать всей красоты нежней- шего светло-салатового цвета в клиентской области окна, который установлен в настоящий момент элементами управления диалогового окна Цвет фона.
Окно сообщений Окно сообщений, которое вызывается функцией МеззадеВох, является простей- шим типом диалогового окна. В предыдущих проектах эта функция уже неод- нократно применялась, но при этом были использованы далеко не все ее воз- можности. Функция МеззадеВох позволяет создавать, отображать и выполнять различные действия с окном сообщения. Окно сообщения содержит текст, определяемый приложением, и заголовок, а также любое сочетание предопределенных пикто- грамм и кнопок. Функция МеззадеВох чаще всего используется для сообщений об ошибках и предупреждающих сообщений. Иногда ее применяют для отладочной печати данных в качестве замены оператора рпп1Г в консольных приложениях. Также окна сообщения могут использоваться в качестве функций-заглушек при разработке программы. Например, окна сообщений могут вызываться для пунктов меню, ко- торые еще не реализованы. Функция имеет следующий прототип: Ж МеззадеВохСНШО Шпс1, 1РСТ5ТК 1рТех^, 1РСТЗТР ПрСарШоп, 1Л1Х1Т иТуре); Параметр Н\Л/п с! принимает дескриптор родительского окна. Если этот параметр равен ИII 1_1_, то окно сообщения не имеет окна-владельца. Параметр 1рТех1 является указателем на С-строку, содержащую текст, который должен быть отображен в окне. Вы можете использовать в составе строки управ- ляющие символы \п, если хотите сделать текст многострочным. Параметр фСарВоп является указателем на С-строку, которая отображается в заголовке диалогового окна. Если этот параметр равен МОП, то применяется за- данный по умолчанию заголовок Еггог. Параметр иТуре определяет содержимое и правила поведения диалогового окна. Его значением может быть комбинация флагов из групп флагов, которые приве- дены в табл. 7.13-7.15. Для указания состава отображаемых кнопок используйте одно из значений, приведенных в табл. 7.13. Таблица 7.13. Флаги, определяющие состав кнопок Флаг Описание МВ_СА1\1СЕЕГкУСО1\1Т11\1С1Е Окно сообщений содержит кнопки Сапсе1, Тгу Адат, Сопйпие МВ_НЕ1_Р Добавляет кнопку Не1р в окно сообщений. Когда пользователь нажимает эту кнопку, система посылает сообщение У\/М_НЕ1_Р окну-владельцу МВ_ОК Окно сообщений содержит единственную кнопку ОК. Это значение применяется по умолчанию МВ_ОКСА1\1СЕ1_ Окно сообщений содержит кнопки ОК и Сапсе! МВ_кЕТкУСА1\1СЕ1_ Окно сообщений содержит кнопки РеНу и Сапсе! МВ_УЕ51\Ю Окно сообщений содержит кнопки Уез и 1\1о МВ_УЕ51\ЮСАМСЕ1_ Окно сообщений содержит кнопки ОК, 1\1о и Сапсе! Чтобы указать отображаемую пиктограмму, нужно выбрать одно из значений, приведенных в табл. 7.14.
Таблица 7.14. Флаги, определяющие отображаемую пиктограмму Флаг Описание МВ_1СОПЕХС1_АМАТЮ1\1, Пиктограмма с изображением восклицательного знака МВ_1СО1\1\Л/Ак1\111\1С МВ_1СОМ1МРОкМАТЮМ, МВ_1СОЫА5ТЕк15К МВ_1СОЫ(211Е8Т1ОЫ МВ_1СОЫ8ТОР, МВ_1СОЫЕккОк, МВ_1СО1\1НАШ Пиктограмма с изображением буквы «1» Пиктограмма с изображением вопросительного знака Пиктограмма с изображением знака «Стоп» Чтобы указать «вид модальности», используйте одно из значений, приведен- ных в табл. 7.15. Таблица 7.15. Флаги, определяющие вид модальности окна сообщений Флаг Описание МВ_АРР1_МООА1_ Пользователь должен щелкнуть на одной из кнопок в окне сообщения, прежде чем сможет продолжить работу с окном, указанным в пара- метре Н\Л/пс1. Пока окно сообщений не закрыто, окно ИХЛ/пс! и все его дочерние окна недоступны. Однако окна стиля «рорир», открытые ранее в данном приложении, доступны для работы с ними. Пользователь также может переключиться в окна других приложений и работать с ними МВ_5У5ТЕММООА1_ Поведение окна сообщений такое же, как и с флагом МВ_АРР1_МООА1_, за исключением того, что окно сообщений имеет стиль \Л/8_ЕХ_ТОРМО8Т, то есть располагается всегда поверх других окон. Рекомендуется использо- вать этот флаг для сообщений о серьезных ошибках или проблемах, требующих немедленной реакции пользователя МВ_ТА8КМСЮА1_ Поведение окна сообщений такое же, как и с флагом МВ_АРР1_МООА1_, за исключением того, что, если параметр И\Л/пс1 равен 1\1111_1_, недоступными будут все окна данного приложения. Этот флаг используется обычно в тех ситуациях, когда дескриптор окна-владельца неизвестен (например, при разработке библиотек) Есть и другие, реже применяемые флаги, которые перечислены в справочных материалах М8ВК Если функция завершается успешно, она возвращает одно из следующих зна- чений: Возвращаемое значение Описание ЮАВОкТ ЮСАМСЕЬ ЮСОМТ1М11Е Ю161\ЮкЕ Ю1\Ю ЮОК ЮкЕТкУ ЮТкУАОАЮ ЮУЕ8 Была выбрана кнопка АЬоЦ Была выбрана кнопка АЬоЦ или нажата клавиша Езс Была выбрана кнопка Сопйпие Была выбрана кнопка 1дпоге Была выбрана кнопка Ио Была выбрана кнопка ОК Была выбрана кнопка кеЦу Была выбрана кнопка Тгу Ада1п Была выбрана кнопка Уез Если произошла какая-то ошибка, то функция возвращает нулевое значение. Технику применения функции МеззадеВох довольно легко понять, поэтому спе- циальные примеры в этой главе не приводятся.
Диалоговые окна общего пользования Одна из важнейших целей, поставленных перед разработчиками системы АУтскпуз, заключалась в том, чтобы способствовать разработке приложений со стандартизи- рованным интерфейсом пользователя. Для многих общепринятых пунктов меню это было сделано довольно быстро. Почти все разработчики программного обеспе- чения стали использовать последовательность команд Ейе ► Ореп (Файл ► Открыть) для вызова диалогового окна, при помощи которого пользователь мог открыть файл. Однако сами окна диалога для открытия файла часто были не очень похожи друг на друга. Начиная с версии АУшсЬауз 3.1, решение этой проблемы было реализовано в виде библиотеки диалоговых окон общего пользования (Соттоп Бга1о§ВохЫЬгагу). Библиотека содержит несколько функций, которые вызывают стандартные окна диалога для открытия и сохранения файлов, поиска и замены, выбора цвета, выбо- ра шрифта и другие стандартные окна. Для каждой функции, создающей и вызывающей стандартное диалоговое окно, в заголовочном файле соттсНд.Н определен соответствующий тип структуры. Что- бы работать со стандартным диалогом, вы должны определить подходящую струк- турную переменную и инициализировать ее. После этого вызывается функция стандартного диалога, которой передается адрес этой структурной переменной. Когда пользователь закрывает окно диалога, вызванная функция возвращает уп- равление программе. В этот момент вы можете получить требуемую информацию из указанной структурной переменной. В демонстрационном приложении СоттопО1’а1од$, которое мы сейчас разрабо- таем, иллюстрируется, как можно использовать стандартные диалоговые окна Ореп (Открыть), 5ауе Аз (Сохранить как), Со1ог (Цвет) и Еоп! (Шрифт). Создайте новый проект с именем СоттопО1’а1од$. Добавьте к приложению ресурс меню с идентификатором ЮК_МЕМ1П. Главное меню должно содержать пункты с атрибутами, указанными в табл. 7.16. Таблица 7.16. Пункты главного меню Имя пункта Тип пункта Идентификатор &Н1е Подменю — &Васкдгоипс1 со1ог Команда ЮМ_ВКСк_СОЮк &Тех1 со1ог Команда ЮМ_ТЕХТ_СО1_ОК йСНоозе Гоп1 Команда ЮМ_СНОО5Е_ЕО1\1Т Подменю Ейе должно содержать пункты с атрибутами, приведенными в табл. 7.17. Таблица 7.17. Пункты подменю Н1е Имя пункта Тип пункта Идентификатор &Ореп... Команда ЮМ_ОРЕЫ 8ауе &Аз... Команда ЮМ_5АУЕ_А5 — 8ЕРАКАТОН — Е&хЛ Команда ЮМ_ЕХ1Т Добавьте в состав проекта файл Соттоп01’а1одз.срр с текстом, приведенным в листинге 7.7.
Листинг 7.7. Проект СоттопО|а1одз ////////////////////////////////////////////////////////////////////// // Соттоп01а1одз.срр #1пс1ис1е <\л/1 пс1о\л/8. Н> #1пс1ис1е <зТс11о.Ь> #1пс1ис1е "КИпО" #1пс1ис1е "гезоигсе.Ь" #0еТ1пе Е5С—0Г "Отказ от выбора или ошибка выполнения функции " 0КЕ800Т САЕЕВАСК ИпОРгос(НШО, 01ИТ, НРАВАМ, ЕРАкАМ); //============================================ 1пТ ШИАР1 Ш пМа 1 п (НIМ8ТАМ1СЕ Шнапсе, Н1М5ТАМСЕ ЬРгеу1пзТапсе, ЕРЗТЕ ПрСтбЫпе, 1 гтЕ пСтсЕбом) { М80 тзд; К1л/пс1 талп1л/пс1 ("СоттопОпа!одз", ЫпзТапсе, пСтсОом, Ипс1Ргос, МАКЕIК1ТВЕ800ВСЕ (10В_МЕ1\101), 100, 100, 400, 300); \л/И11е (ОеТМеззадеС&тзд, МОЕЕ, 0, 0)) { ТгапзШеМеззадеС&тзд); 01зраТсЬМе88аде(&тзд); } геТигп тзд.мРагат; } /============================= ЕЕЕЗОЕТ САЕЕВАСК ИпОРгос(НШ0 Шпс1, 01ИТ иМзд, НРАВАМ мРагат, ЕРАВАМ 1Рагат) { НОС НОС; РА1МТЗТЕ0СТ рз; ВЕСТ гсСИепТ; ВООЕ зиссезз; зТаТ1с СОЕОВВЕЕ ТехТСо1ог; // Переменные для стандартных диалогов "Ореп", "Зауе Аз" зТаТ1с ОРЕМЕЮЕМАМЕ оТп; зТаТ1с сНаг зхЕ11е[МАХ_РАТН]; // Переменные для стандартного диалога "Со1оп" зТаТ1с СН003ЕС0Е0В сс; // сотгтюп сП а! од Ьох зТписТипе зТаЫс СОЕОВВЕЕ аспСизТС! п[16]; // аггау оТ сизТот со!опз // Переменные для стандартного диалога "ЕопТ" зТаЫс СН003ЕЕ0ИТ сЬТ; зТаЫс НЕОМ1Т НЕопТ; зТаЫс Е03Е0Е1Т 1Т; з\л/1ТсИ (иМзд) { сазе Ш_СВЕАТЕ: // Инициализация структуры оТп оСп.18ТписТ81хе = з1геоТ(ОРЕМЕ1ЕЕИАМЕ); оСп. ЫпсЮшег = Шпс1; оТп. 1 рзТпЕ11 е = зхЕПе; оТп.пМахЕПе = з1хеоС(зхЕ11е); // Инициализация структуры сс продолжение
Листинг 7.7 {продолжение} сс.18ЬгисЬ81хе = з12еоГ(СН008ЕС0Е0Р); сс. ЫпсЮмпег = Шпс!; сс.1рСизЬСо1ог8 = (ЬРОШРО) асгСизЮг; сс.Падз = СС_ЕОЫОРЕМ | СС_Р6В1М1Т; // Инициализация структуры сЬГ сЬГ. 18ЬгисЬ81хе = зыеоЖНООВЕЕОШ; сЖЫпсЮмпег = Шпс1; сЬГ. ПрЕодГогтЬ = &1Г; сЬГ.Падз = СЕ_8С^ЕМЕ0МТ8 | СЕ_1 И 1ТТ0Е06Е0МТ8ТР0СТ; сЖпЕопЬТуре = 81МиЕАТЕО_ГОМТТУРЕ; Ьгеак; сазе ИМ_СОММАМО: 8\л/1ЬсИ (ЬОШРОЫРагат)) { сазе ЮМ-ОРЕМ: зЬгсру(зхЕ11е, ""); зиссезз = 6еЬ0репЕ11еМате(&оГп); 1Г (зиссезз) МеззадеВох(Н1л/пс1, оГп. 1 рзЬгП 1 е, "Открывается файл:", МВ_ОК); е!зе МеззадеВох(Ыл1пс1, Е8С_0Е"0еЬ0репЕ11еМате", "Отказ от выбора или ошибка", МВ_1 СОШАВМ Ж); Ьгеак: сазе ЮМ_8А\/Е_А8: зЬгсру(зхЕ11е, зиссезз = 0еЬ8ауеЕ11еМате(&оГп): 1Г (зиссезз) МеззадеВох(Шпс1, оГп. 1 рзЬгП 1 е, "Файл сохраняется с именем:", МВ_ОК); е!зе МеззадеВох(Шпс1, Е8С_0Е"0еЬ8ауеЕ11еМате", "Отказ от выбора или ошибка", МВ_1СОМИАВМ1МО); Ьгеак; сазе ЮМ_ВКОВ_СОЕОВ: 1Г (СЬоозеСо1ог(&сс)) 8еЬС 1 аззЬопд(Шпс1, 6СЬ_НВРВАСКОРООИ0, (Ь0Мб)СгеаЬе8о11ЬВгизЬ(сс.гдЬРезиИ)); Ьгеак; сазе ЮМ-ТЕХТ-СОЕОК: 1Г (СЬоозеСо1ог(&сс)) ЬехЬ0о1ог = сс.гдЬРезиИ; Ьгеак; сазе ЮМ_СН008Е_Е0МТ: ШСИоозеЕопШсШ) ЬЕопЬ = СгеаЬеЕопЫпсНгесШЖ 1рЬодЕопЬ); Ьгеак; сазе 1ОМ-ЕХ1Т: 8епс1Ме88аде(Шпс1, ИМ_0Е8ТР0У, 0, 0); Ьгеак; ЬеГаиИ: Ьгеак; }
1пуа11(ШеКесШШс1, ШЬЕ, ТШЕ); Ьгеак; сазе 1л/М_РА1МТ: НОС = Вед1 пРа1 пЬ(Шпс1, &рз); 8еЬВкМос1е(кОС, ТРАИЗРАРЕМТ); 8еЬТехЬСо1ог(ИВС, ЬехЬСо!ог); 1Т (ИЕопЬ) 0е1еЬеОЬаесЬ(8е1есЬОЬаесЬ(НОС, ИЕопЬ)); СеЬС11 егОесЬ (Шпс1, &гсС 11 епЬ); ОгамТехШОС, "Сотгтюп В1а!одз", -1, &гсС11епЬ, 0Т-51МЗЕЕЕ1МЕ | ОТ_СЕМТЕР | ВТ_УСЕМТЕЮ ; Епс1Ра!пЬ(Шпс1, &рз); Ьгеак; сазе Ш_0Е8ТЕ0У: РозЬОи!ЬМеззаде(0); Ьгеак; с1еТаи!Ь: геЬигп ОеТШпсЫРгос(Шпс1, иМзд, мРагат, 1Рагат); } геЬигп 0; } ////////////////////////////////////////////////////////////////////// В приложении используются предопределенные структуры трех типов. Струк- тура ОРЕМЕИЕМАМЕ применяется для создания стандартных диалогов Ореп и 5ауе Аз. Структура СНОО5ЕСОЮК позволяет вызывать диалоговое окно Со1ог, а струк- тура СНОО5ЕЕОМТ предназначена для работы со стандартным диалоговым окном Еоп1:. Соответствующие структурные переменные оГп, сс и сНГ объявлены со спецификатором $1:а1:1с. Это решает две проблемы: инициализацию нулевым значением тех полей структур, для которых можно использовать значения по умолчанию, и сохранение значений полей между двумя вызовами функции \Л/пс1Ргос. Инициализация переменных оГп, сс и сНГ осуществляется в блоке обработки сообщения ММ_СКЕАТЕ. Рис. 7.35. Приложение СоттопО1а1одз
Стандартные диалоговые окна создаются и вызываются следующими функ- циями: Стандартное диалоговое окно Функция Ореп Зауе Аз Со1ог Роп1 6еЮрепН1еЫате 6е18ауеН1еЫате СНоозеСо1ог СНоозеРоп!: Приложение выводит в центре окна текстовую строку Соттоп О1’а1од$ (рис. 7.35). Пользователь может выбрать любой цвет фона и любой цвет текста, а также любой шрифт из установленных в системе шрифтов, с любым возможным размером и на- чертанием шрифта. На рисунке выбран шрифт Мопо1уре Согыуа с размером 36 логических единиц. Протестируйте приложение, выполняя различные команды, в том числе и для подменю ЕПе. Обратите внимание на то, что удобный, а главное, стандартизиро- ванный интерфейс пользователя в нашей программе был реализован минималь- ными средствами.
8 Элементы управления общего пользования По мере развития операционной системы ее пользовательский интер- фейс непрерывно улучшался. Так, начиная с АУтйомга 95, в системе было опреде- лено несколько новых элементов, названных общими элементами управления. Чтобы сделать эти элементы доступными для разработчиков программного обес- печения, МхсгозоЙ разработала библиотеку элементов управления общего пользо- вания (соттоп сопёго! ИЬгагу). Новые элементы управления дополняют базовые элементы управления, рассмотренные в предыдущей главе, и придают програм- мам более совершенный вид. Библиотека элементов управления общего пользо- вания реализована в виде динамически загружаемой библиотеки сотсй32.с111. Элементы управления общего пользования можно разделить на четыре кате- гории, в которые входят элементы управления главного окна, составные диалого- вые элементы управления, элементы управления АУтйомга Ехр1огег и иные до- полнительные элементы управления. В табл. 8.1-8.4 приведены краткие описания этих элементов. Таблица 8.1. Элементы управления главного окна Элемент управления Описание Тоо1Ьаг (Панель инструментов) Тоо1йр (Окно подсказки) 81аЩз Ьаг (Строка состояния) Состоит из кнопок быстрого доступа Показывает текст во всплывающем окне Информационная строка, обычно размещаемая в нижней части окна приложения Таблица 8.2. Составные диалоговые элементы управления Элемент управления Описание ТаЬ соп1го1 (Закладки) Имитация закладок в записной книжке. При выборе одной из закладок на экране отображается связанное с ней диалоговое окно РгорегЬу 5Иее1 (Набор страниц свойств) Диалоговое окно с набором страниц, аналогичных зак- ладкам, содержащее кроме этого общие для всех страниц кнопки РгорегЬу раде (Страница свойств) Диалоговое окно, используемое как страница в элементе управления РгореНу 5Иее1
Элемент управления Описание Тгее у!е\л/ (Дерево просмотра) Отображает иерархически структурированный список (левая панель окна программы \Л/1пс1о\л/5 Ехр1огег) 1151 у!е\л/ (Список просмотра) Отображает список элементов, идентифицируемых пикто- граммами и текстовыми данными (правая панель окна программы \Л/1пс1о\л/5 Ехр1огег) Таблица 8.4. Другие элементы управления Элемент управления Описание Ап1тайоп (Анимационное изображение) Неабег (Заголовок списка просмотра) 1таде 1151 (Список изображений) Воспроизводит анимационную последовательность для индикации длительной операции Отображает горизонтальные заголовки для столбцов и ис- пользуется совместно с элементом 1151 у!е\л/ Элемент управления для хранения набора растровых изо- бражений, не являющийся отдельным окном Ргодгезз Ьаг (Индикатор процесса) Элемент управления, который отображает динамику дли- тельной операции в виде процентного соотношения выпол- ненной части задачи к1сН ес№ (Усовершенствованный редактор) 811с1ег (Регулятор) Редактор, поддерживающий множество шрифтов и базовые возможности ОЬЕ-контейнера Ползунок, перемещаемый в пределах шкалы (разновидность полосы прокрутки) 8р1п (Счетчик или стрелки) Полоса прокрутки, состоящая из двух кнопок со стрелками, для увеличения или уменьшения на единицу целого числа, находящегося в «приятельском» окне редактирования Основы применения Большинство элементов управления общего пользования реализовано в виде окна соответствующего предопределенного класса. С этой точки зрения элементы уп- равления общего пользования похожи на базовые элементы управления. И те и другие элементы могут быть созданы вызовом функции Сгеа1:е\Л/1’пс1о№, кото- рой передаются конкретные флаги стиля класса. И те и другие элементы управ- ляются специфичными для данного класса сообщениями. Оба типа элементов управления посылают уведомляющие сообщения родительскому окну, информи- руя его обо всех происходящих событиях. Разница между базовыми элементами управления и элементами управления общего пользования состоит в типе посылаемых уведомительных сообщений. Базовые элементы управления посылают сообщения ММ_СОММАМО, а элементы уп- равления общего пользования почти всегда посылают сообщения ММ_М0Т1ЕУ. Инициализация библиотеки Чтобы использовать в приложении какой-либо элемент управления общего пользования, сначала нужно вызвать функцию 1тЧСоттопСоп1:го1$Ех, которая
регистрирует оконные классы элементов управления. Функция имеет следую- щий прототип: В001_ 1тДСоттопСопбго1гЕх(ЕР1М1ТС0ММ0МС0МТЕ0Е8ЕХ 1 р!п1 Шг18); В параметре 1р1ш’1:С1:г1$ передается адрес структурной переменной типа 1МПСОММОМСОМТКО15ЕХ, содержащей информацию о том, какие классы элементов управления должны быть зарегистрированы. Структура 1М1ТСОММОМСОМТКО15ЕХ имеет следующее определение: Цурес1еТ гбгысб Цад1 И1ТС0ММ0МС0МТК0Е8ЕХ { ОШЕО (Махе; // размер структуры в байтах ОШЕО б\л/1СС: // флаги загрузки классов из ОЕЕ } 1М1ТС0ММ0МС01Х1ТВ0Е5ЕХ, *ЕРIИ1ТС0ММ0МС01Х1ТВ0Е5ЕХ; Второй параметр может принимать одно или несколько значений, перечислен- ных в табл. 8.5. Таблица 8.5. Некоторые из возможных флагов для функции 1пП:СоттопСоп1то18Ех Флаг Загружаются оконные классы для элементов управления 1СС_А1\11МАТЕ_С1А55 атташе 1СС_ВАк_С1_А88Е8 1оо1Ьаг, 5ЩЩ5 Ьаг, 511с1ег, 1:оо1Нр 1СС_Ы8ТУ1Е\Л/_С1_А88Е8 1151 У1е\л/, Неас1ег 1СС_РНОСкЕ88_С1_А88 ргодгезз Ьаг 1СС_ТАВ_С1_А88Е8 1а Ь, 1оо1Ьр 1СС_ТкЕЕУ1Е\Л/_С1_А88Е8 Нее У1е\л/, 1оо1Ьр 1СС_11РОО\Л/1\1_С1_А88 ир-Ьсм/п 1СС_\Л/1Ы95_С1_А88Е8 атташе, Иеас1ег, кеу, 1151 У1е\л/, ргодге55 Ьаг, 5ЩЩ5 Ьаг, 1аЬ, 1оо1Ьр, 1оо1Ьаг, 5Пс1ег, Нее мем, ир-Ьсм/п Полный список флагов можно найти в справочных материалах Описание функции ТтТСоттопСогйгоЕЕх вместе с другими описаниями, необ- ходимыми для использования библиотеки, находятся в файле соттсЫ.Н. Этот файл не входит в группу файлов, ссылки на которые помещены в файле \ллпдохл/з.К. Поэтому в начале любого исходного файла, содержащего вызовы функций дан- ной библиотеки, необходимо поместить следующую директиву: #1пс1ыс1е <соттсбг1. б> Помимо этого следует указать компоновщику расположение библиотечного файла сотс1:132.11Ь. Если применяется среда разработки Ухзпа! Збпсйо 6.0, то до- бавьте имя этой библиотеки в текстовое поле 0Ь]’ес1:/11’Ьгагу тос1и1е$ диалогового окна настроек проекта, которое вызывается при помощи команды меню Рго]ес1: ► ЗеФ’пдз... ► Ипк. Если вы забудете это сделать, то компоновщик выведет сообщение следующе- го вида: еггог ЕМК2001: ипгего1уес1 ехбегпа! гутбо! _1тр_1тДСоттопСопбго1г@0 Учтите, что если вы забудете вызвать функцию ТтТСоттопСогйгоЕЕх, то ни ком- пилятор, ни компоновщик этого «не заметят», но ваша программа, возможно, бу- дет вести себя странным образом. Например, диалоговое окно, в котором был раз- мещен элемент управления общего пользования, может вообще не появиться на экране. Так что будьте внимательны!
Элемент управления КкН есШ: из-за его сложности и большого размера распо- лагается в его собственной динамически подключаемой библиотеке псЬес132.с111. Для работы с усовершенствованным редактором нужно загрузить эту библиоте- ку при помощи вызова функции 1_оас11лЬгагу: 1_оас11_1Ьгагу("г1 сНес132.сП 1"); а в начале файлов, использующих функции или константы из данной библиоте- ки, поместить директиву #1пс1ис1е <г1с6ес1Ю> Создание элементов управления общего пользования Наиболее традиционным способом создания элемента управления общего пользо- вания является вызов функции Сгеа1е\Л/1’пс1оУ7 или Сгеа1е\Л/1пс1о\л/Ех. Например, при- веденный вызов создает панель инструментов: НЮ Ыпс1Тоо1Ваг = СгеаШПпсШТООЬВАКСЬАЗЗМАМЕ, 1л/8_СН1Ю | И5_У151В1_Е | И5-В0ЕВЕЕ, 0, 0, 16, 16, Ыпс1РагегД, (НМЕМП)!, ЕНпзЦ, 0); Имя оконного класса ТОО1_ВАКС1_А$$МАМЕ здесь задается без кавычек, посколь- ку это именованная константа, определение которой зависит от набора симво- лов, выбранного при построении программы. Для набора символов АМ81 имя ТОО1_ВАКС1_А$$МАМЕ заменяется строковой константой Тоо1Ьаг\ЛНпдо\л/32, для набо- ра символов НМСОНЕ — строковой константой Е"Тоо1Ьаг\Л/1’пс1о\л/32". Имена дру- гих оконных классов элементов управления общего пользования определяются аналогично. Обычно элементы управления общего пользования создаются как дочерние окна, что определяется флагом М$_СН11_О и передачей дескриптора родительского окна Ндопс1Рагеп1:. Альтернативой вызову функции Сгеа1:е\Л/1’пс1о\л/ является вызов специализиро- ванной функции создания элемента управления, которая в то же время может выполнять некоторую стандартную инициализацию. Например, панель инстру- ментов может быть создана функцией Сгеа1:еТоо1ЬагЕх. Для некоторых элементов управления соответствующие оконные классы не определены. Такие элементы управления могут быть созданы только с помощью специализированных функций. В табл. 8.6 приведены сведения об оконных клас- сах и специализированных функциях создания для элементов управления обще- го пользования. Таблица 8.6. Оконные классы и специализированные функции создания для элементов управления общего пользования Элемент управления Оконный класс Функция создания Тоо 1 Ьаг ТОО1_ВАкС1_А88ЫАМЕ Сгеа1еТоо1ЬагЕх Тоо1Ьр ТОО1Т1Р8_С1_А88 Нет 81аЩ5 Ьаг 8ТАТ118С1А88ЫАМЕ Сгеа^еЗЩЩзМ п с! о\л/ ТаЬ соп1го1 \Л/С_ТАВСО1\1ТкО1_ Нет РгореНу зИее1: Нет Ргорег1у8Иее1:
Элемент управления Оконный класс Функция создания РгореНу раде Нет Сгеа1еРгорег1у8Иее1:Раде Тгее \Л/С_ТкЕЕУ1Е\Л/ Нет Из! У1е\л/ \Л/С_1_15ТУ1Е\Л/ Нет Ап1тайоп А1\ПМАТЕ_С1_А88 Нет Неабег \Л/С_НЕАОЕк Нет 1таде Пз! Нет 1таде1дз1_Сгеа1:е Ргодгезз Ьаг РкОСНЕ88_С1_А88 Нет к1сН ес!П: "К|с1эЕс1|Г' (АЫ51) или 1_"к1сИЕс1П:" (11М1ССЮЕ) Нет 811с1ег ТКАСКВАк_С1_А88 Нет 8р1п 11РОО\Л/1\1_С1_А88 Сгеа1е11 рОо\л/пСопЦо1 Стили элементов управления общего пользования Независимо от способа создания — с помощью Сгеа1е\Л/1 пс1о\л/ или специализиро- ванной функции — при вызове соответствующей функции необходимо задать набор стилей элемента управления. Флаги стилей можно разделить на следующие четыре категории: флаги основ- ного стиля окна (с префиксом флаги расширенного стиля окна (М$_ЕХ_)1 2, флаги основного стиля элемента управления общего пользования (СС5_) и флаги стиля, специфичные для конкретного элемента управления. Библиотека элементов управления общего пользования поддерживает набор значений стиля с префиксом СС5_. Они применяются для панелей инструментов, строк состояния и заголовков списка просмотра. В табл. 8.7 перечислены эти сти- ли с кратким описанием их назначения. Таблица 8.7. Флаги основного стиля элемента управления общего пользования Флаг Эффект СС8_АОЛ18ТАВ1_Е Состав кнопок панели инструментов и их последовательность могут изменяться пользователем СС8_ВОТТОМ Элемент управления располагается внизу родительского окна. Этот стиль установлен по умолчанию для элемента управления ЗЩЩз Ьаг СС8_1\ЮО1УЮЕк Между элементом управления и родительским окном нет раздели- тельной линии СС5_1\ЮМСЛ/ЕУ Элемент управления не может быть растянут или сдвинут по вертикали СС8_1\ЮкЕ812Е Размер и позиция элемента управления фиксированы СС8_ТОР Элемент управления располагается вверху родительского окна. Этот стиль установлен по умолчанию для элемента управления Тоо1 Ьаг Флаги стиля, специфичные для конкретных элементов управления, имеют уникальные префиксы, которые приведены в табл. 8.8. 1 Флаги основного стиля окна приведены в табл. 1.8 (глава 1). 2 Флаги расширенного стиля окна приведены в табл. 1.9 (глава 1).
Таблица 8.8. Префиксы флагов стиля, специфичных для конкретных элементов управления Элемент управления Префикс флага стиля Тоо 1 Ьаг Тоо1Ьр 81аЩ5 Ьаг ТаЬ соп1го1 РгорегЬу 5Иее1 РгореНу раде Тгее У1е\л/ 1151 у!е\л/ Ап1таЬоп НеаЬег 1таде 1151 Ргодгезз Ьаг к1сИ еЬЛ ЗНЬег 8р1п ТВ8ТУ1_Е_ ТТ8_ 8ВАН8_ ТС8_ Нет Нет Т75_ 1Л/8_ АС8_ НО8_ Нет Нет Е8_ ТВ8_ 1Ю8_ Обмен сообщениями После создания элемента управления общего пользования приложение управ- ляет его действиями, посылая необходимые сообщения при помощи функции 5епс1Ме$$аде. Для каждого типа элемента управления существуют свои специ- фические сообщения. Например, для добавления элемента в дерево просмотра (Тгее VIем) с дескрипто- ром НмпсГГУ необходимо послать элементу управления сообщение Т\/М_1М$ЕКТ1ТЕМ. Это можно реализовать следующим вызовом функции 5епс1Ме$$аде: Жет = (НТкЕЕ1ТЕМ)Зепс1Ме88аде(Н\л/пбТУ, ТУМ_1 МЗЕКТ1ТЕМ, О, (ЕРАЕАМ) (ЕРТУ_1МЗЕЕТЗТЕиСТ) ; Альтернативой вызовам функции 5епс1Ме$$аде является использование набора мак- росов, определенных в файле соттсЫ.Н. Например, добавление элемента в дерево можно реализовать, используя макрос Тгее\йем_ ТпзегШет: Жет = Тгее\Ле\л/_1п8егЖет (ЖЖ, Как видно, текст с макросом выглядит значительно проще для чтения. К сожалению, файлы АУш32 содержат определения макросов не для всех эле- ментов управления общего пользования. В файле соттсЫ.Н находятся определе- ния макросов только для элементов управления ТаЬ соп1:го1, Тгее У1’ем, Из! У1’ем, Аш’тайоп и Неадег. Файл ргзЫ.Н содержит определения макросов для элемента уп- равления Ргорег1у зНее!. Как и базовые элементы управления, элементы управления общего пользова- ния посылают своему родительскому окну уведомляющие сообщения, содержа- щие информацию о произошедших событиях. Но если базовые элементы управ- ления используют сообщение ММ_СОММАМО, то элементы управления общего пользования обычно посылают уведомления при помощи сообщений ММ_МОТ1ЕУ.
Однако не все уведомления реализуются подобным образом. Например, па- нель инструментов, использующая сообщение ММ_МОТ1ЕУ для большинства уве- домлений, посылает сообщение ММ_С0ММАМ0, когда пользователь нажимает одну из кнопок. Дело в том, что панель инструментов обычно дублирует команды меню, поэтому имеющийся код обработки сообщений ММ_СОММАМО от команд меню бу- дет одновременно обрабатывать и сообщения от кнопок панели инструментов. Еще одно исключение составляют полосы прокрутки элементов управления 5Пс1ег и 5р1п, которые посылают сообщение ММ_\/$СКО1_1_ или ММ_Н$СКО1_1_. Хотя каждый элемент управления общего пользования имеет свой собствен- ный набор кодов уведомления, существует набор уведомлений, общий для всех элементов. Эти уведомления приведены в табл. 8.9. Таблица 8.9. Общие уведомления Код уведомления Описание ММ.СЫСК ЫМ_ОВ1_С1_К ММ_К11_1_РОС118 ым.оитормЕмокУ ым_нсиск ЫМ_кОВ1_С1_К ЫМ-НЕТ1ЖЫ ЫМ_8ЕТЕОС118 Пользователь сделал щелчок левой кнопкой мыши Пользователь сделал двойной щелчок левой кнопкой мыши Элемент управления потерял фокус ввода Ошибка «Не хватает памяти» Пользователь сделал щелчок правой кнопкой мыши Пользователь сделал двойной щелчок правой кнопкой мыши Пользователь нажал клавишу Еп1:ег Элемент управления получил фокус ввода Не все элементы управления обязательно посылают каждое из этих уведомля- ющих сообщений. Например, набор закладок (ТаЬ соп1:го1) не посылает уведомле- ний об изменении фокуса ввода. Перед использованием каждого элемента управления общего пользования сле- дует разобраться с тем, какие уведомляющие сообщения он посылает. Из-за ограниченного объема книги мы рассмотрим приемы работы только со следующими элементами управления общего пользования: Тоо1Ьаг, ТооШр, 51:а1:и$ Ьаг, Ргодгезз Ьаг, 511с1ег и 5р1 п. Элементы управления главного окна В оформлении главного окна приложения часто применяются следующие эле- менты управления общего пользования: панели инструментов, окна подсказки и строки состояния. На рис. 8.1 показано, как выглядят эти элементы управления в основном окне М8 Ухзпа! 81псйо 6.0. Панель инструментов Панель инструментов — это дочернее окно, обычно расположенное под меню при- ложения и содержащее одну или несколько кнопок. Когда пользователь щелкает мышью на кнопке панели инструментов, она посылает сообщение ММ_С0ММАМ0 своему родительскому окну. Традиционно кнопки панели инструментов соответ- ствуют некоторым пунктам в меню приложения, обеспечивая более удобный
способ доступа пользователя к командам меню. Следует отметить, что в простых приложениях меню может отсутствовать, а все необходимые команды могут быть реализованы с помощью панели инструментов. Панель инструментов Окно подсказки Строка состояния Рис. 8.1. Панель инструментов, строка состояния и окно подсказки Кнопки панели инструментов сами по себе не являются окнами. Они реализо- ваны как графические объекты, нарисованные на поверхности окна панели инст- рументов. Изображения на кнопках поясняют их назначение. Иногда помимо изоб- ражения кнопка может содержать текстовую метку, расположенную или правее, или ниже картинки. Панель инструментов устанавливает одинаковые размеры всех кнопок, а в слу- чае наличия текстовых меток определяет размеры так, чтобы разместить самый длинный текст. Поэтому для текстовых меток следует выбирать короткие строки, иначе кнопки могут получиться слишком большими. В большинстве случаев кноп- ки на панелях инструментов содержат только растровые изображения, а назначе- ние кнопок поясняется с помощью всплывающих окон подсказок. Чтобы сделать интерфейс панели инструментов более удобным для пользовате- ля, кнопки часто объединяют в группы. Кнопки, объединенные в группу, следуют одна за другой, а между группами остается небольшой промежуток. Такие проме- жутки реализуются при помощи кнопок стиля ТВ$ТУ1_Е_$ЕР, называемых кнопками- разделителями . Кроме кнопок, панель инструментов может содержать и другие дочерние окна элементов управления, такие, как, например, комбинированный список (сотЬо Ьох). Встроенные элементы управления создаются при помощи функции Сгеа±е\Л/1 пс!о\л/.
Для добавления к приложению панели инструментов необходимо выполнить следующую последовательность действий: 1. Определить ресурс растрового образа панели инструментов. 2. Объявить и инициализировать массив структур типа ТВВ11ТТОМ, содержащий информацию о кнопках панели инструментов. 3. Вызвать функцию Сгеа1еТоо1ЬагЕх для создания и инициализации панели инст- рументов. Рассмотрим реализацию этих шагов на примере разработки приложения Тоо 1В а г, представляющего собой модифицированную версию программы Мепи0ето1, опи- санной в главе 6. В результате модификации к интерфейсу приложения будет до- бавлена панель инструментов, дублирующая некоторые команды меню. Начало разработки приложения Тоо1Ваг Создайте новый проект с именем Тоо1Ваг. Скопируйте из папки проекта Мепи0ето1 (см. листинг 6.1) в папку проекта Тоо1Ваг файлы с расширениями .срр, .Н и .гс, скор- ректировав их имена заменой Мепи0ето1 на Тоо1Ваг. Добавьте эти файлы в состав проекта. Укажите имя библиотеки сотс1132.11’Ь в настройках компоновщика в проекте. В среде Ухзпа! 81псйо 6.0 эти настройки находятся на вкладке Ип к диалогового окна Рго]'ес1: ЗеЙпдз, которое открывается при помощи команды меню Рго]’ес1: ► ЗеЙпдз. Определение ресурса растрового образа панели инструментов В главном меню Ухзпа! 81исйо выполните команду Тпзег! ► Кезоигсе. В появившем- ся диалоговом окне ТпзеП: Кезоигсе укажите тип ресурса ТооШаг и нажмите кнопку Мем. В результате будет открыто окно редактора панели инструментов с заготов- кой растрового образа панели инструментов, содержащего первую и пока един- ственную кнопку (рис. 8.2). Рис. 8.2. Редактор панелей инструментов с заготовкой растрового образа панели По умолчанию редактор присваивает растровому образу панели инструментов идентификатор ЮК_Т001_ВАК1. Если вы хотите изменить назначенный идентифика- тор, то на вкладке Резо и гее VI е\л/ в окне Могкзрасе сделайте щелчок правой кнопкой мыши на элементе ЮК_Т001_ВАК1 и в появившемся контекстном меню выберите пункт РгорегВез. В открывшемся диалоговом окне ТооШаг РгорегВез введите в окне редакти- рования Ш нужный идентификатор. В нашем проекте можно оставить предложенный редактором идентификатор ресурса ЮК_Т001_ВАК1.
Окно редактора панелей инструментов, показанное на рис. 8.2, разделено на три части. В верхней части окна находится изображение создаваемой панели ин- струментов. В левой нижней части располагается изображение растра для теку- щей кнопки в натуральную величину, а в правой нижней части — увеличенное изображение растра для текущей кнопки. Растр для каждой кнопки имеет разме- ры 16 х 15 пикселов. Для создаваемой кнопки нужно нарисовать картинку и определить идентифи- катор кнопки. Пользуясь инструментами рисования на панели СгарЫсз, создайте нужный ри- сунок на кнопке так же, как это делалось для пиктограмм. После этого сделайте двойной щелчок мышью на изображении кнопки в верхней части окна редактора. В открывшемся диалоговом окне Тоо1Ьаг Вийоп РгорегВез введите нужный иденти- фикатор кнопки. Если кнопка дублирует некоторый пункт меню, то идентифика- тор кнопки должен быть таким же, как и у дублируемого пункта меню. Допустим, что вы нарисовали на кнопке изображение прямоугольника и ука- зали ее идентификатор Ю_КЕСТАМ61_Е. После выполненных операций редактор па- нелей инструментов автоматически создает следующую кнопку, расположенную правее от созданной кнопки (рис. 8.3). Рис. 8.3. Редактор панелей инструментов предлагает заготовку следующей кнопки Щелкнув на новой кнопке мышью, можно перейти в режим редактирования этой кнопки, повторяя описанные выше действия. Если текущая кнопка используется как кнопка-разделитель между группами кнопок, то изображение на ней создавать не нужно. В качестве идентификатора кнопок-разделителей в рассматриваемом примере будет использоваться имено- ванная константа Ю_$ЕР (вы можете использовать любое другое имя). Если нужно изменить порядок размещения уже созданных кнопок, то это мож- но сделать при помощи мыши, ухватившись за перемещаемую кнопку и перета- щив ее в нужную позицию. Для удаления созданной ранее кнопки просто схвати- те ее мышью и переместите за пределы изображения панели инструментов. Вернемся к нашей программе. Используя описанную процедуру, определите растровый образ панели инструментов, как показано на рис. 8.4. | | | |.|.|»1 !!! ||| Рис. 8.4. Растровый образ панели инструментов Растровый образ должен содержать 11 кнопок, причем две из них используют- ся как кнопки-разделители. Изображения для кнопок принято нумеровать слева
направо, начиная с нуля. Подразумеваемые индексы изображений, описания изоб- ражений и идентификаторы кнопок приведены в табл. 8.10. Таблица 8.10. Индексы изображений, их описание и идентификаторы кнопок Индекс изображения Описание изображения Идентификатор кнопки 0 Прямоугольник Ю_кЕСТА1\1С1_Е 1 Ромб Ю.ННОМВ 2 Эллипс Ю_Е1_1_1Р5Е 3 — Ю_5ЕР 4 Красный кружок Ю-кЕО 5 Зеленый кружок Ю-СкЕЕЫ 6 Синий кружок Ю_ВШЕ 7 — Ю_5ЕР 8 Черный квадрат Ю.ОАкК 9 Серый квадрат Ю_МЕ0ШМ 10 Белый квадрат Ю_1_1СНТ Следует отметить, что идентификаторы кнопок, кроме кнопок-разделителей, совпадают с идентификаторами дублируемых пунктов меню. Закончив визуальное проектирование панели инструментов, сохраните создан- ный растровый образ в файле описания ресурсов. Обратите внимание на то, что теперь в составе проекта появился файл 1ооШаг1.Ьтр, содержащий растровый образ панели инструментов. Имя этого фай- ла использует вторую часть идентификатора панели инструментов. Если открыть файл описания ресурсов Тоо1Ваг.гс в текстовом режиме, то в нем можно найти многострочное определение панели инструментов: ЮК_Т001_ВАРа Т001ВАВ 013САВВАВ1Е 16, 15 ВЕСЛИ ВЫТТОИ 1В-ВЕСТАМ31Е виттои 1в_вномв ЕИВ Помимо этого в описании ресурсов есть ссылка на указанный файл: 1ВВ-Т00ЕВАВ1 ВIТМАР ВIЗСАРВАВЕ Е Чоо! ба г 1. Ьтр" Заполнение массива структур типа ТВВ1ЛТОИ Если для создания панели инструментов используется вызов функции Сгеа1е- ТооШагЕх, то один из параметров функции должен получить адрес массива струк- тур типа ТВВ11ТТ0М. Структура ТВВ11ТТ0М определена в файле соттсЮ следую- щим образом: ЦуребеТ збгысб _ТВВЫТТОМ { 1пб ч’ВПтар; // индекс изображения кнопки или // ширина кнопки-разделителя (в пикселах) 1пб 1с1Соттапс1: // идентификатор кнопки ВУТЕ ТзВбабе; // начальное состояние кнопки ВУТЕ 138Уу1е; // стиль кнопки ВШВВ сЮаба; // дополнительные данные 1ИТ—РТР п'ЗТгппд: // индекс (относительно нуля) текстовой метки кнопки } ТВВ1ТГТ0М;
Интерпретация полей структуры частично поясняется в комментариях. Поле Гз51:а1:е может принимать комбинацию из следующих значений: Флаг состояния Описание ТВ8ТАТЕ.СНЕСКЕ0 Кнопка со стилем ТВ8"ГУ1_Е_СНЕСК находится в нажатом состоянии ТВ8ТАТЕ_РНЕ88Е0 Кнопка любого стиля находится в нажатом состоянии ТВ8ТАТЕ_ЕЫАВ1_Е0 Кнопка доступна (может реагировать на действия мышью) ТВ5ТАТЕ_НЮОЕЫ Скрытая кнопка (для пользователя недоступна) ТВ8ТАТЕ_1Ы0ЕТЕНМ1ЫАТЕ Кнопка отображается серым цветом и недоступна для действий пользователя Поле Гз51:у1е может принимать одно из следующих значений: Флаг стиля Описание ТВ8ТУ1_Е_В11ТТ0Ы Стандартная кнопка. Может быть нажата, но не может оставаться в нажатом состоянии ТВ8ТУ1_Е_8ЕР Разделитель для создания промежутка между группами кнопок. Может использоваться для резервирования места для дочерних элементов управления ТВ8ТУ1_Е_СНЕСК Кнопка ведет себя, как флажок (сИеск Ьох). Каждый щелчок мыши изменяет состояние кнопки (нажата / отжата) ТВ8ТУ1_Е_СкО11Р Кнопка является членом группы кнопок типа переключателей (гасНо Ьийоп) ТВ8ТУ1_Е_СНЕСКСкО11Р Объединяет свойства стилей ТВ8ТУ1_Е_СНЕСК и ТВ8ТУ1_Е_СН011Р Поле с!\л/0а1:а можно не использовать. Но при желании в нем можно сохра- нить указатель на дополнительную информацию, специфичную для конкрет- ной кнопки. Разработчик может получить значение этого поля, послав сообще- ние ТВ_6ЕТВ1ЛТ0М. Последнее поле 15Ыпд используется только в тех случаях, когда кроме изобра- жения кнопка имеет текстовую метку. В этом случае программист должен создать список текстовых строк панели инструментов, отправив сообщения ТВ_А005ТК1М6. Таким образом, прежде чем вызывать функцию Сгеа1:еТоо1ЬагЕх, следует объя- вить массив структур типа ТВВ11ТТ0М: ТВВСТТОМ Шэ[МШ_ВиТТ0М8]; и заполнить поля элементов массива подходящими значениями. Вызов функции Сгеа1еТоо1ЬагЕх Функция Сгеа1:еТоо1ЬагЕх, создающая и инициализирующая панель инструмен- тов, имеет следующий прототип: ШИВ СгеабеТоо1ЬагЕх( Ш Ыпс1, // дескриптор родительского окна ОШЕО \л/8, // стили панели инструментов ЩИТ \л/Ю, // идентификатор панели инструментов Ш пВИшарз, // количество изображений кнопок Н1М5ТАМСЕ ИВМТпзб, // дескриптор экземпляра приложения, содержащего // ресурс растрового образа панели инструментов ЩИТ \л/ВМЮ, // идентификатор ресурса растрового образа 1РСТВВЫТТ0М 1рВыббопз, // адрес массива структур типа ТВВЫТТОМ 1пб ЩытВыббопз, // количество кнопок Ш бхВиббоп, // ширина кнопок в пикселах Ш буВыббоп, // высота кнопок в пикселах
Ш бхВйшар, Ш буВйшар, ШТ иЗРгисШ хе // ширина изображения кнопки в пикселах // высота изображения кнопки в пикселах // размер структуры ТВВЫТТОМ Стили панели инструментов определяются параметром доз. Он обязательно дол- жен содержать флаги М$_СН11_0 и Могут также указываться и другие стандартные стили, например М$_ВОКОЕК. Кроме того, есть два специальных стиля, которые предназначены для панелей инструментов. Один из них, ТВ$ТУ1_Е_ТОО1_Т1Р$, задает поддержку всплывающих подсказок для кнопок панели инструментов. Вто- рым стилем является ТВ$ТУ1_Е_МКАРАВЕЕ. Включение этого флага означает, что слиш- ком длинные панели инструментов будут автоматически переходить на другую стро- ку. Дополнительно могут указываться флаги основного стиля элементов управления общего пользования, приведенные в табл. 8.7. Если вы не указываете эти флаги, то по умолчанию применяется стиль СС5_ТОР. Параметру доЮ необходимо передать идентификатор панели инструментов. Вы можете использовать любой целочисленный идентификатор, но нужно следить за тем, чтобы это значение не повторялось среди идентификаторов, определен- ных в файле гезоигсе.Н. Параметры с1х Вийоп и с1у Вийоп задают ширину и высоту самих кнопок в пиксе- лах, а параметры с1хВ1’Етар и дуВйтар — ширину и высоту изображения кнопки в пикселах. Если вы не хотите особых приключений, то передайте нулевое значе- ние для всех четырех параметров. В этом случае используются значения по умол- чанию: дхВийоп=16, дуВийоп=16, с1хВ1’Етар=16, с1уВйтар=15. На самом деле АУшбом^з выводит изображения кнопок, включающие рамки, шириной не менее дхВийоп+7 и высотой не менее с1уВийоп+6 пикселов. Изменение размеров панели инструментов Когда панель инструментов инициализирована, ее размеры устанавливаются со- ответственно текущим размерам родительского окна. Но если размер окна из- меняется, то размер панели инструментов автоматически изменяться не будет. Особых неприятностей это не причиняет, однако при увеличении окна панель инструментов может оказаться слишком маленькой. Для решения этой пробле- мы необходимо посылать сообщение ТВ_А11ТО512Е панели инструментов каждый раз, когда изменяются размеры родительского окна. Проще всего это сделать при обработке сообщения ММ_$12Е: 8епбМеззаде(ЫпбТоо1Ваг, ТВ_АбТ0812Е, 0, 0); Реакция панели инструментов на это сообщение определяется флагами ее стиля (см. табл. 8.7). Например, панель инструментов со стилем СС5_ТОР устанавливает свое местоположение и размеры. С другой стороны, панель инструментов со сти- лем СС5_М0ВЕ$12Е игнорирует это сообщение и требует явной установки ее место- положения и размеров. Поддержка элемента управления «подсказка» Окно подсказки (Тоо1йр) — это маленькое окно, содержащее текст подсказки. Обыч- но подсказка всплывает, когда курсор мыши оказывается в «горячей» (кок) обла- сти, обслуживаемой данным элементом управления, задерживаясь в ней на неко- торое время. Элемент управления ТооШр хранит список «горячих» областей, которые для него ассоциируются с инструментами (&)о1). Каждая «горячая» область опреде-
ляется как некоторая прямоугольная область в обслуживаемом окне или отно- сится к окну целиком. Существует две категории окон подсказки: □ автономные элементы управления ТооЙтр, создаваемые при помощи функции Сгеа1:е\Л/1’пс1о\л/Ех как окна предопределенного оконного класса ТОО1_ПР$_С1_А$$; □ элементы управления ТооК1р, встроенные в панель инструментов. Техника работы с ними различна. Первую категорию мы рассмотрим позже. Сейчас нас интересуют подсказки, встроенные в панель инструментов. Если при создании панели инструментов указан стиль ТВ$ТУ1_Е_ТОО1_Т1Р$, то каж- дая кнопка, добавляемая в панель инструментов, регистрирует инструмент под- сказки. Когда подсказка становится активной, она посылает сообщение ММ_МОТ1ЕУ с кодом уведомления ММ_МЕЕОТЕХТ. Панель инструментов передает это сообще- ние своему родительскому окну, которое должно его обработать, если необходи- мо отобразить на экране текст подсказки. При получении сообщения ММ_МОТ1ЕУ параметр [Рагат содержит адрес струк- туры типа ТОО1Т1РТЕХТ, имеющей следующее определение: 1зурес1еТ збгысб { ИМНОЕ Нбг; // информация об уведомительном сообщении 1РТЗТЕ 1р8хТехб; // указатель на строку или строковый ресурс ЮАЕ 8хТехб[80]; // буфер для текста подсказки Н1М5ТАМСЕ М П8“Е; // дескриптор экземпляра приложения, содержащего // строковый ресурс 1ЛМТ ыГ1адз; // это поле не используется } ТОО1Т1РТЕХТ; Поле Нс1г содержит данные типа МИНОР, сопровождающие любое сообщение ММ_МОТ1ЕУ. Эти данные включают информацию об элементе управления, послав- шем сообщение, и информацию о самом сообщении: 1зурес1еТ збгысб ЦадИМНОЕ { НШО ЫпсЕгот; // дескриптор окна элемента управления ЩИТ 1бЕгот; // идентификатор элемента управления Ы1ИТ собе; // код уведомления } ИМНОР; Обрабатывая сообщение ММ_МОТ1ЕУ, приложение обычно сохраняет значение параметра [Рагат в переменной [рТТТ типа 1_РТОО1_ПРТЕХТ, после чего проверяет зна- чение поля [рТТТ->Нс1г.сос1е. Если оно равно ТТМ_МЕЕОТЕХТ, то это свидетельствует о поступлении запроса на отображение подсказки. Идентификатор кнопки, кото- рая запросила отображение подсказки, хранится в поле [рТТТ->Нс1 гл’с1 Егот. Зная этот идентификатор, приложение может выбрать необходимый текст для передачи окну подсказки. Структура ТООЕТ1РТЕХТ позволяет использовать три способа передачи текста в подсказку: 1. Копирование текста в буфер с адресом [рТТТ->$2Тех!:. 2. Запись адреса буфера, содержащего текст, в поле [рТТТ->[р$2Тех1:. 3. Запись идентификатора строки, хранящейся в ресурсе таблицы строк, в поле [рТТТ->[р$2Тех1: и одновременно запись дескриптора экземпляра приложения, содержащего строковый ресурс, в поле [рПТ->Н1’п8Е
Реализация первых двух способов требует применения оператора 5\л/11:сН, при помощи которого выбирается источник копирования текста в зависимости от зна- чения поля 1рТТТ->Нс1мс1Егот. Третий способ позволяет применить наиболее красивое решение. Оно основа- но на довольно простой идее. Если все строки с текстами подсказок поместить в ресурс таблицы строк1 и присвоить им идентификаторы, совпадающие с иден- тификаторами кнопок панели инструментов, то можно обойтись без оператора змйсН с гирляндой блоков сазе. Обработка сообщения ММ_МОТ1ЕУ в этом случае будет выглядеть следующим образом: сазе 1л/М_МОТ1ЕУ: 1рТТТ = (1РТОО1Т1РТЕХТ)!Рагат; 1Т (1 рТТТ->Ис1г. собе == ТТ1\1_МЕЕОТЕХТ) { 1 рТТТ->М пзР = беТсИосЮ! еНапсП е(1\1Е1ЕЕ); 1рТТТ->1рз2ТехР = МАКЕ1МТРЕ30иРСЕ(1 рТТТ->ббг. 1бЕгот); } Ьгеак; В результате такой обработки панель инструментов отобразит подсказку для кнопки с идентификатором 1рТТТ->Нс1мс1Егот. При этом содержимым подсказки будет строка из таблицы строк точно с таким же идентификатором. Применим новые знания, вернувшись к разработке приложения Тоо1Ваг. Продолжение разработки приложения Тоо1Ваг Добавьте к приложению ресурс таблицы строк. Введите в таблицу следующие стро- ки: Идентификатор Строка Ю_кЕСТА1\1С1_Е Ю.ИНОМВ Ю_Е1_1_1Р5Е Ю-кЕО Ю_СНЕЕЫ Ю_ВШЕ Ю.ОАкК Ю-МЕ0И1М ю.ыснт Прямоугольник Ромб Эллипс Красная составляющая цвета Зеленая составляющая цвета Синяя составляющая цвета Темный цвет Средний цвет Светлый цвет В настоящий момент текст в файле Тоо1Ваг.срр является точной копией текста файла МепиОето!.срр. Замените его текстом, приведенным в листинге 8.1. Листинг 8.1. Проект Тоо1Ваг ////////////////////////////////////////////////////////////////////// // Тоо1Ваг.срр #1пс1ибе <\л/1 пс1о\л/8. Н> #1пс1ибе <соттсЬг!.И> #1пс1ибе <8ЬсНо.И> #1пс1ибе "КМпб.Ь" продолжение & 1 Работа с ресурсом «Таблица строк» описана в главе 5.
Листинг 8.1 {продолжение} #1пс1ис1е "гезоигсе.б" #с1еТ1 пе 1л1 200 // ширина фигуры #с1еТ1 пе Н 140 // высота фигуры епит ЗИареЗые { МАХ, М1И }; ЁуребеТ збгисб { Ш 1б_збаре; // идентификатор фигуры В001_ ТВеб; // компонент красного цвета В001_ Гбгееп; // компонент зеленого цвета В001_ ГВ1ие; // компонент синего цвета Ш 1с1_Ьг1д1тЕ; // идентификатор яркости цвета } ЗбареВаба; #беТ1 пе Ю_Т001_ВАВ 201 #беТппе МиМ_ВиТТ0М5 И #беТп пе 5ЕРАВАТ0В-ШВТН 10 НШ0 ЫпсГГоо! Ваг; НШ0 1пШоо1Ваг(ШШ Шпс1); уо1с1 брбабеТоо!Ваг(50аре0аба& зс1); ВВЕЗВЕТ САЕЕВАСК 1л1пс1Ргос(Н1л1МВ, ЮТ, НРАВАМ, ЕРАВАМ); //========================================== 1пб ШИАР1 ШпМапп(Н1МЗТА1\1СЕ Шнапсе, Н1МЗТАМ1СЕ ОРгеуТпзбапсе, ЕРЗТВ 1рСтсВ1пе, Ш пСтбЗВюУл/) { М56 тзд; К1л1пс1 та1п1л!пс1("Тоо1Ваг", Мпзбапсе, пСтбЗВюУл/, ИпбРгос, МАКЕ1МТВЕ30ВВСЕ( 1ВВ_МЕ1\11Ш, 100, 100, 400, 300); // Инициализация библиотеки "Сотгтюп Сопбго! Шагасу" 1М1ТС0ММ0МС01Х1ТВ0ЕЗЕХ п сс; 1сс.сШхе = зпгеоТ(IИ1ТС0ММ0МС01Х1ТВ0ЕЗЕХ); ПСС. 6\Л/1СС = 1СС-ШМ95-СЕАЗЗЕЗ; 1т’бСоттопСопбго1зЕх(&1сс); \л/М1е (СебМеззадеС&тзд, МИН, 0, 0)) { ТгапзШеМеззадеС&тзд); ВтзрабсбМеззадеС&тзд); } геТигп тзд.мРагат; } //========================================== НИШ 1пШоо1Ваг(НИШ Шпс1) { НИШ НТоо1Ваг; 1пб ЬбпЖШМ-ВиТТОМЗ] = { Ю-ВЕСТАМЗЕЕ, Ю-ВНОМВ, 1В-ЕШР5Е, Ю-ЗЕР, Ю-ВЕВ, 1В_ЗВЕЕМ, Ю-ВЕВЕ, 1В_5ЕР, 1В_ВАВК, 1О_МЕО1НМ, 1В_Е13НТ }; Ш Ьбп5бу1е[ШМ_ВНТТ0Ш] = { ТВЗТУЕЕ-ВВТТОМ, ТВЗТУЕЕ-ВиТТОМ, ТВЗТУЕЕ-ВВТТОМ, ТВЗТУЕЕ-ЗЕР, ТВЗТУЕЕ-СНЕСК, ТВЗТУЕЕ-СНЕСК, ТВЗТУЕЕ-СНЕСК, ТВ5ТХЕЕ_5ЕР, ТВ5ТУЕЕ_СНЕСК6В0бР, ТВ5ТУЕЕ_СНЕСК6В0бР, ТВЗТУЕЕ-СНЕСКЗВОВР }; ТВВбТТОМ ШэЕШМ-ВиТТОМЗ];
шетзеШЫэ, 0, з1хеоТ(ТЫэ)); Гог (1гтГ 1 = 0; 1 < М0М_В0ТТ0М8; ++1) { 1Т (ЬШШ == Ю_8ЕР) Шэ[1]. 1 ВПтар = 8ЕРАВАТ0В_Ш0ТН; е!зе ТЫэ[1 ]. 1 ВПтар = 1; Ш)[1]. 1с1Соттапс1 = ЬТп10[1]; Ш'ШШе = ТВЗТАТЕ-ЕМАВЕЕВ; ТЫэ[1]. ТзЗТуТе = ЬТпЗТу 1 е[ 1 ]; } НТоо1Ваг = СгеаТеТоо! Ьаг Ех( Шпс1, 08_СН1Е0 | О8_7181ВЕЕ | 08_В0В0ЕВ | ТВ8ТУЕЕ_Т00ЕТ1 Р8, 1О_ТООЕВАВ, М0М_В0ТТ0М8, ОеТМобы!еНапс11 е(МВЕЕ), IОВ_ТООЕВАВ1, Ш, М0М_В0ТТ0М8, 0, 0, 0, 0, з1 геотстввиттою); геТигп НТоо1Ваг; } //============================= уоЮ ОрбаТеТоо! Ваг(ЗНареОаТай зб) { 8епбМеззаде(ЫпбТоо1Ваг, ТВ_СНЕСКВОТТОМ, Ю_ВЕ0, зб.ТВеб); ЗепбМеззаде(ЫпбТоо1 Ваг, ТВ_СНЕСКВОТТОМ, Ю_ОВЕЕМ, зб.ТОгееп); ЗепбМеззаде(Н\л/пс1Тоо1 Ваг, ТВ_СНЕСКВОТТОМ, 10_ВЕВЕ, зб.ТВ1ие); Зепс1Ме88аде(И\л/пс1Тоо1 Ваг, ТВ_СНЕСКВОТТОМ, Ю_0АВК, (зб. 1б_Ьг1дНТ == Ю_0АВК)); ЗепбМеззаде(ЫпбТоо1 Ваг, ТВ_СНЕСКВОТТОМ, Ю_МЕ01иМ, (зб.1б_Ьг1дНТ == Ю_МЕ0ЮМ)); ЗепбМе88аде(Н\л/пс1Тоо1 Ваг, ТВ_СНЕСКВиТТ01\1, Ю_Е1(ЗНТ, (зб.1б_Ьг1дНТ == 10_Е16НТ)); } //========================================== ЕРЕЗОЕТ САЕЕВАСК ОпбРгос(НШ0 Шпб, 01ИТ иМзд, ОРАВАМ мРагат, ЕРАРАМ 1Рагат) { НОС НОС; РА1МТ8ТВ0СТ рз; ВЕСТ гесТ; 8ТаТ1с НМЕИО НМепи; // дескриптор главного меню Ш хО, уО, х1, у1, х2, у2; Р01М1Т рТЕ4]; 8ТаТ1с ЗНареЗые зНареЗые = М1И; зТаТ1с ВООЕ ЬЗНом = ТВОЕ; зТаТ1с НВВ08Н НВгизН, НОЮВгизН; сНаг* ПетВез1хеМате[2] = { "Оесгеазе!", "1псгеазе!"}; 1пТ 1пТепзПу[3] = { 85, 170, 255 }; // интенсивность ВОВ-компонентов цвета 1пТ ЬндНТпезз; зШ1с ЗНареОаТа зНареОаТа; ВЕСТ гсТВ; // позиция и размеры окна ЫпбТоо1Ваг 1пТ ТЬНе1дНТ; // высота окна ЫпбТоо1Ваг ЕРТООЕТ1РТЕХТ 1рТТТ; змПсН (иМзд) { сазе ОМ_СВЕАТЕ: НМепи = ОеТМепи(Шпб); 8еТМепи0еТаиИ1Тет(0еТ8иЬМепи(НМепи, 0), 10М_ОРЕМ, ЕАЕЗЕ);
Листинг 8.1 {продолжение} С0ескМепиВас11о1Тет(беТ8иЬМепи(ЬМепи, 1), 10М_8Н0И_8НАРЕ, ЮМ-НЮЕ-ЗНАРЕ, ЮМ_8Н01л1_8НАРЕ, МЕ_ВУСОММАМО); СЬескМепиВасНо1Тет(0еТ8иЬМепи(ЬМепи, 2), Ю_ВЕСТАМОЕЕ, Ю-ЕЕЕ1Р5Е, Ю_ВЕСТАМОЕЕ, МЕ_ВУСОММАМО); С0ескМепиВас11о1Тет(беТ8иЬМепи(ЬМепи, 2), 10_ОАВК, Ю-ЕКЗНТ, Ю_0АВК, МЕ_ВУСОММАМО); зЬареОаТа. 1с1_8Ьаре = Ю_ВЕСТАМОЕЕ; зЬареОаТа. 1с1_Ьг1дЬТ = 10_0АВК; // Создание панели инструментов Г|\л/пс1Тоо1 Ваг = 1тТТоо1Ваг(Шпс1); ЗепбМеззадеС Н\л/пс1Тоо’1 Ваг, ТВ_АЫТ0517Е, 0, 0); Ьгеак; // Настройка размеров панели инструментов сазе Ш_812Е: ЗепбМеззадеС Н\л/пс1Тоо’1 Ваг, ТВ_А0Т0812Е, 0, 0); Ьгеак; // Обработка запроса на вывод подсказки сазе Ш_М0Т1ЕУ: 1рТТТ = (ЕРТООЕТ1РТЕХТ)!Рагат; 1Т (1рТТТ->Нбг.собе == ТТ1\1_МЕЕ0ТЕХТ) { 1рТТТ->М’пзТ = ОеТМос1и1еНапс11е(МОЕЕ); 1рТТТ->1рзхТехТ = МАКЕ IК1ТВЕ800ВСЕ (1 рТТТ->Ыг. ЮЕгот); } Ьгеак; сазе 1л1М_СОММАМО: зтТсЬ (ЬОШВОЫРагат)) { /* Текст оператора из листинга 6.1 */ } ОрбаТеТоо!Ваг(зЬареОаТа); // обновление состояния кнопок 1пуа11 ОаТеВесТ(Шпс1, МОЕЕ, ТВОЕ); Ьгеак; сазе 1л/М_РА1МТ: НОС = ВедппРаппТ(Шпс1, &рз); Ьн’дИТпезз = 1 пТепз1 Ту[зЬареОаТа. 1 с1_Ьг1 дЬТ - 10_ОАВКЦ; 1Г (Ь8Ьом) { ЬВгизЬ = СгеаТе8о11ОВгизЬ(ВОВ( зЬареОаТа ,СВес1? ЬндИТпезз : 0, зЬареОаТа.СОгееп? Ьн’дИТпезз : 0, зЬареОаТа.ТВ1 ие? Ьн’дИТпезз : 0)); ЬОЮВгизЬ = (НВВ08Н)8е1 есТОЬцесТ(НОС, ЬВгизЮ; // Определение центра фигуры (хО, уО) ОеТС! 1епТВесТ(Шпс1, &гесТ); //Модифицированный код // Учитываем уменьшение клиентской области из-за // размещения панели инструментов ОеЫлНпбомВесТ(Н\л/пс1Тоо1 Ваг, &гсТВ); ТЬНе1дЬТ = гсТВ.ЬоТТот - гсТВ.Тор; хО = гесТ.н’дИТ / 2;
уО = ЬЬНещЬЬ + (гесЬ.ЬоЬЬот - 1з1эНе1 дН1з) / 2; // Координаты прямоугольника и эллипса 1Т (збареЗые == М1Ю { Х1 = хО - 1л1/2; у1 = уО - Н/2; х2 = хО + 1л1/2; у2 = уО + Н/2; } е!зе { х1 = 0; у1 = ЬЬНе1дЬЬ; х2 = гесЬ.г1д1тЕ; у2 = гесЬ.Ьоббот; } // Далее - заимствованный код из МепиОето! // Координаты ромба рб[0].х = (х! + х2) / 2; рб[0].у = у!; рб[1].х = х2; рб[1].у = (у! + у2) / 2; р-602: .х = (х! + х2) / 2; рШЪу = у2; р-603:.х = х1; р-603:.у= (у! + у2) / 2; змтЕсЬ (збареОаба. 1с1_зЬаре) { сазе Ю_КЕСТАМ60Е: Еесбапд!е(НОС, х1, у1, х2, у2); Ьгеак; сазе Ю-КНОМВ: Ро1удоп(НОС, рЬ, 4); Ьгеак; сазе 10-ЕЕЕ1Р5Е: Е111рзе(ИОС, х1, у1, х2, у2); Ьгеак; } □е! е’ЬеОЬзес'Ь(5е1 есЬОЬаесЬ(НОС, П01 сИЗгизП)): } Епс1Ра1 пб (Шпс1, &рз); Ьгеак; сазе Ш_0Е8ТК0У: РозЩпЬМеззаде(О); Ьгеак; ЬеТаиИ: гебигп ОеЛлИпсЫРгос(Шпс1, иМзд, мРагат, 1Рагат); } гебигп 0; } ////////////////////////////////////////////////////////////////////// В программе появились две новые (по сравнению с МепиОето!) функции: 1т’1:Тоо1Ваг и IIрда1:еТоо1Ваг. В теле функции 1шЧТоо1Ваг создается и инициализируется панель инструментов НТоо1Ваг. Массив структур ГЬЬ типа ТВ В11ТТО И заполняется в цикле Гог. Обратите вни- мание на то, что для первой группы из трех кнопок назначается стиль ТВ$ТУ1_Е_В11ТТОМ, для второй группы — стиль ТВ$ТУ1_Е_СН ЕСК, а для третьей — ТВ$ТУ1_Е_СН ЕСК6Р011Р. Полю ГЬЬ[1’]л’В1’1:тар всех кнопок, за исключением кнопок-разделителей, присваивается индекс изображения в растровом образе панели инструментов. Для кнопок-разде- лителей это поле получает значение константы ЗЕРАКАТОКДЛ/ЮТН. При вызове функции СгеаГеТооШагЕх помимо основных стилей окна передается также стиль ТВ5ТУЬЕ_ТОО1Т1Р$, что позволяет обеспечить поддержку встроенной подсказки для кнопок панели инструментов. Идентификатор панели инструмен- тов Ю_ТОО1_ВАК, передаваемый функции, определен с помощью директивы #с1еГ1 пе. Функция 1шЧТоо1Ваг вызывается из оконной процедуры \Л/пс1 Ргос в блоке обработ- ки сообщения ММ_СРЕАТЕ.
Кнопки панели инструментов, имеющие стиль ТВ$ТУ1_Е_СНЕСК или ТВ$ТУ1_Е_ СНЕСК6В011Р, требуют к себе повышенного внимания. Дело в том, что для них нуж- но обеспечить «обратную связь» от меню. Если изменение опции выполнено пользователем через пункт меню, то состояние указанных кнопок необходимо об- новить, иначе оно не будет синхронизировано с изменившимся состоянием при- ложения. Эту подзадачу решает функция IIрдаЕеТооЕВаг, посылая обновляемым кнопкам сообщение ТВ_СНЕСКВ11ТТОМ. Функция 11рс1а1:еТоо1Ваг вызывается в конце блока обработки сообщения ММ_С0ММА1\10. Обрабатывая сообщение ММ_$12Е, приложение посылает панели инструмен- тов сообщение ТВ_А11ТО512Е, чтобы панель изменила свои размеры в соответствии с изменившимися размерами родительского окна. Это же сообщение посылается сразу после вызова функции 1шЧТоо1Ваг. В принципе, в данной программе это не- обязательно. Но если вы создаете панель инструментов со стилем ТВ$ТУ1_Е_МКАРАВ1_Е, то приведенная инструкция обеспечивает нормальную настройку начального вида панели инструментов. Обрабатывая сообщение ММ_МОТ1ЕУ, приложение выбирает из таблицы строк текст подсказки для соответствующей кнопки панели инструментов. В результа- те панель инструментов отображает требуемое окно подсказки. Приведенный код мы разбирали выше в разделе «Поддержка элемента управления «"подсказка"». Код обработки сообщения ММ_РА1МТ слегка модифицирован, чтобы учесть уменьшение видимой части клиентской области из-за размещения панели инст- рументов. На рис. 8.5 показан вид работающего приложения Тоо1Ваг. Рис. 8.5. Окно приложения Тоо1Ваг с активизированной подсказкой «Эллипс» Панель инструментов с дополнительными текстовыми метками Ранее говорилось, что кнопки панели инструментов помимо растровых изобра- жений могут содержать и текстовые метки. Элемент управления Тоо1Ьаг имеет встроенный список текстовых строк, который можно заполнять, отправляя эле- менту сообщения ТВ_А005ТК1М6. Кроме того, при инициализации массива 1ЬЬ не- обходимо присвоить полю 1:ЬЬ[1] л’ЗЫпд соответствующий индекс (удобнее всего, если этот индекс будет совпадать с индексом изображения).
Чтобы посмотреть, как все это работает, сделаем небольшую доработку преды- дущей программы. Создайте новый проект с именем Тоо1ВагМй:НТех1:. Скопируйте из папки проекта Тоо1Ваг (см. листинг 8.1) в папку проекта Тоо1ВагМй:НТех1: файлы с расширениями .срр, .Н и .гс, скорректировав их имена заменой подстроки Тоо1Ваг на Тоо1Ваг\Л/1’1:НТех1:. Скопируйте также файл 1оо1Ьаг1.Ьтр. Добавьте эти файлы в со- став проекта. Также добавьте к настройкам проекта на вкладке Ипк библиотеку сотс1132.11’Ь. Отредактируйте текст файла Тоо1Ваг\Л/1ТНТех1:.срр так, чтобы он соответствовал листингу 8.2. Листинг 8.2. Проект Тоо1Ваг\М1ИТех1 ////////////////////////////////////////////////////////////////////// // Тоо1ВагШЖехб.срр /* Директивы препроцессора, объявления типов и глобальных переменных из листинга 8.1 */ 1КЕ861Т САЕЕВАСК ШбРгос(НШВ, 1ИМТ, ПРАВАМ, ВРАКАМ); //=========================== 1пб ШИАР1 ШпМа-т(Н1М5ТА1\1СЕ Шзбапсе, Н1М5ТАМСЕ ВРгеуШбапсе, ЕРЗТЕ 1рСтс1Е1пе, Ш пСтсЕбом) { М56 тзд; К1л1пс1 тапп1л/пс1 ("Тоо!Ваг1л/1 ЕНТех'Е", ВТпзбапсе, пСтсЗИом, МпбРгос, МАКЕ IМТЕЕЗОЫЕСЕ(IВЕ_МЕМЫ1), 100, 100, 430, 300); /* Здесь такой же текст, как и в листинге 8.1 */ гебыгп тзд.мРагат; } //========================================== НШВ 1пШоо1 Ваг(НШВ Шпс1) { /* Определения переменной 0Тоо1Ваг и массивов ЬбпЮ и Ьбп8Уу1е - из листинга 8.1 */ ТВВОТТОИ Ш[М0М_В0ТТ0М8]; тетзеШЬЬ, 0, зыеоШЫэ)); бог (пггВ 1 = 0; 1 < М0М_В0ТТ0МЗ; ++1) { 1С (йпЮШ == Ю-ЗЕР) ШЕИ . 1 ВПтар = 8ЕРАВАТ0Р_ШВТН; е!зе ОЬЬ[1].1В1Отар = 1; Ш)[1]. ЮСоттапб = Ьбп1В[1]; ШШ.ВМе = ТВЗТАТЕ-ЕМАВЕЕВ; ОЬЬ[1].Тз8Уу1е = ЬбпЗбу1е[1]; ОЬЬ[1].18бг1пд =1; // индекс строки в списке строк } Е1Тоо1 Ваг = СгеабеТоо1багЕх(Шпс1, и/3_СН1Е0 | 1л/3_У121ВЕЕ | 1л18_В0ВВЕР | ТВЗТУЕЕ_Т00ЕТ1 РЗ, Ю-ТООЕВАЕ, М0М-В0ТТ0МЗ, ОеОМосЫеНапсИ е(МОЕЕ), IВР_ТООЕВАР1, Шэ, М0М-В0ТТ0МЗ, 0, 0, 0, 0, 81хеоШВВОТТОЮ); // Заполнение списка строк в элементе управления Тоо1Ьаг
Листинг 8.2 {продолжение} сопз1з сИаг* 8^г[МиМ_ВиТТ0М8] = { "Рес^", "РВотЬ", "ЕШрз", "РесГ, "Огееп", "В1ые", "Вагк", "Мес11ит", "1_1д1гк" }; Гог (1 = 0; 1 < МиМ_ВиТТ0М8; ++1) ЗепбМеззадеСНТоо!Ваг, ТВ_А008ТРЖ, 0, (ЕРАРАММгИ ]); ге1ип ВТоо1Ваг; } //========================================== уо1с1 ВрсШеТоо! Ваг(8каре0а^а& зс1) { /* Здесь такой же текст, как и в листинге 8.1 */ } //=================================================================== 1ЖЕ51ЛТ САЕЕВАСК ШбРгос(НШ0 Шпс1, В11\1Т иМзд, АРАРАМ мРагат, ЕРАРАМ 1 Рагат) { /* Здесь такой же текст, как и в листинге 8.1 */ } ////////////////////////////////////////////////////////////////////// Обратите внимание на изменения и дополнения в тексте функции 1т1Тоо1Ваг. В цикле инициализации массива 1ЬЬ добавлено присваивание 1ЬЬ [1] л’ЗЫпд = 1’. После создания панели инструментов ее встроенный список строк наполняется текстовыми метками из массива 81т. Чтобы индексы этих строк совпадали с ин- дексами изображений кнопок в ресурсе ЮВ_Т001_ВАВ1, массив 81г содержит пустые строки в тех позициях, которые соответствуют кнопкам-разделителям. Окно работающего приложения показано на рис. 8.6. Рис. 8.6. Окно приложения Тоо1Ваг\Л/кИТех1 Рис. 8.7. Панель инструментов со стилем ТВ8ТУ1_Е_1_18Т
Теперь панель инструментов увеличила размеры кнопок с учетом размещения самого длинного текста. По умолчанию текстовые метки выводятся ниже рисун- ков. Но можно расположить их справа, если при вызове функции Сгеа1:еТоо1ЬагЕх указать дополнительный стиль ТВ$ТУ1_Е_1_1$Т. Вид панели инструментов для этого стиля показан на рис. 8.7. Кнопки вытянулись в ширину, поэтому при заданных размерах окна пользова- тель видит только первые шесть кнопок. Чтобы увидеть панель инструментов полностью, он вынужден раздвигать окно приложения с помощью мыши. Но если при вызове функции Сгеа1:еТоо1ЬагЕх указать не только стиль ТВ$ТУ1_Е_1_1$Т, но еще и стиль ТВ5ТУ1_Е_ЖАРАВ1_Е, то панель инструментов будет автоматически пе- реходить на новую строку, когда ширина окна оказывается недостаточной для нор- мального отображения панели. Этот вариант реализации панели инструментов по- казан на рис. 8.8. Рис. 8.8. Панель инструментов со стилями ТВ8ТУ1_Е_1_18Т | ТВ8ТУ1_Е_У\/НАРАВ1_Е Размещение на панели инструментов других элементов управления Панель инструментов поддерживает только кнопки, поэтому для расположения на ее поверхности какого-либо другого элемента управления следует создать до- чернее окно. Наиболее часто на панель инструментов добавляются комбиниро- ванные списки {сотЬо Ьох). Добавление дочернего окна элемента управления на панель инструментов связано с решением трех проблем: а) резервирование места для дочернего окна; б) обработка сообщений от дочернего окна; в) поддержка подсказки для дочер- него окна. Резервирование места под встроенный элемент управления несложно обеспе- чить, поместив кнопку-разделитель требуемой ширины. Напомним, что факти- ческая ширина кнопки-разделителя задается присваиванием нужного значения полю 1:ЬЬ[1] л’Вйтар. Обычно это значение подбирается экспериментально, исходя из желательной ширины встраиваемого элемента управления. Уведомительные сообщения от встроенного элемента управления поступают в виде сообщений ММ_СОММАМО в родительское окно панели инструментов. Так как оконная процедура панели инструментов (спрятанная в недрах АУшсЬм^) эти
сообщения не обрабатывает, она передает их родительскому окну панели инстру- ментов, то есть главному окну приложения. Поэтому в блоке обработки сообще- ния ММ_СОММАМО оконной процедуры \Л/пс1Ргос необходимо предусмотреть обра- ботку этих уведомительных сообщений. Мы уже знаем, что панель инструментов поддерживает окна подсказок для всех своих кнопок. Но на встроенные элементы управления эта поддержка не распро- страняется. В следующем разделе будет показано, как решить эту проблему с по- мощью автономных элементов управления То о 11:1р. Рассмотрим технику добавления комбинированного списка на панель инст- рументов на примере разработки приложения СотЬо1пТоо1Ьаг, которое является модификацией приложения Тоо1Ваг. Цель модификации — добавить возможность выбора толщины пера, которым обводится контур рисуемой фигуры (до сих пор использовалось перо по умолчанию толщиной в 1 пиксел). Создайте новый проект с именем СотЬоТпТооШаг. Затем скопируйте из папки про- екта Тоо1Ваг (см. листинг 8.1) в папку проекта СотЬоТпТооШаг файлы с расширениями .срр, .Н и .гс, скорректировав их имена заменой подстроки Тоо1Ваг на СотЬоТпТооШаг. Скопируйте также файл 1:ооШаг1.Ьтр. Добавьте эти файлы в состав проекта. Добавьте к настройкам проекта на вкладке Ипк библиотеку сотс1132.НЬ. Откройте вкладку Кезоигсе\/1е\л/ в окне Могкзрасе и вызовите редактор панелей инструментов двойным щелчком мыши на элементе ЮК_Т001_ВАК1. Добавьте в на- чале панели инструментов две кнопки без изображений с идентификаторами Ю_5ЕР. Откройте текст файла СотЬоТпТооШаг.срр и отредактируйте его так, чтобы он соответствовал листингу 8.3. Листинг 8.3. Проект СотЬоТпТооШаг ////////////////////////////////////////////////////////////////////// // СотЬо1пТоо1Ваг.срр /* Директивы #1пс1ис1е и #с1еГ1пе, объявление типов ЗИареЗые и ЗЬареВаТа - из листинга 8.1 */ #с1еГ1пе 1В_ТООЕВАК 201 #с1еГ1пе IВС_ТВ_СОМВОВОХ 202 #с1еГ1пе МЬМ_ВЬТТ0МЗ 13 #с1еГ1пе ЗЕРАВАТ0В_ШВТН 10 #с1еГ1пе С0МВ0_ЗРАСЕ_ШВТН 50 #с1еГ1пе С0МВ0_8РАСЕ_НЕ1СНТ 100 НШВ ЫпсГГоо! Ваг; НШВ Ыпс1СотЬо; НШВ 1пШоо1Ваг(НШВ Шпс1); уо1с1 НрбаТеТоо!Ваг(ЗЬареВаТа& зб); ЕРЕЗНЬТ САЕЕВАСК ШЬРгос(НШВ, Н1МТ, ПРАВАМ, ЬРАВАМ); //========================================== пгтЬ ШИАР1 ШпМапп(НIИЗТАИСЕ ЫпзТапсе, Н1МЗТАМСЕ ЬРгеуШТапсе, ЬРЗТР 1рСтс1Е1пе, 1 пТ пСтс18Ьом) { М2б тзд;
К1л/пс1 та1п1л/пс1("СотЬо 1пТоо1 Ваг", ЫпзТапсе, пСгткОом, ОпОРгос, МАКЕ 1МТВЕ300ВСЕ(IВВ_МЕМ01), 100, 100, 400, 300); /* Здесь такой же текст, как и в листинге 8.1 */ геТигп тзд.мРагат; } //========================^ НШО 1пШоо1Ваг(НШ0 Шпс1) { НШВ ЬТоо1Ваг; 1пТ ЬТп1ВЖМ_В0ТТ0М8] = { 1В_ЗЕР, 1В_ЗЕР, Ю-РЕСТАМСЬЕ, ю_вномв, 1В-ЕЕЕ1Р5Е, 1В_8ЕР, 1В_ВЕВ, 1В_ОВЕЕМ, 1В_ВЕЬЕ, 1В_8ЕР, 1В_ВАВК, 1В_МЕВЮМ, 1В_Ы0НТ }; 1 пТ ЬТпЗТу!еЕМ0М_В0ТТ0МЗЦ = { ТВ8ТУЬЕ_8ЕР, ТВ8ТУЬЕ_8ЕР, ТВЗТУЕЕ-ВЬТТОМ, ТВЗТУЕЕ_ВЬТТ0М, ТВЗТУЕЕ_ВЬТТ0М, ТВЗТУЕЕ_ЗЕР, ТВЗТУЕЕ-СНЕСК, ТВЗТУЕЕ_СНЕСК, ТВЗТУЕЕ_СНЕСК, ТВЗТУЕЕ_ЗЕР, ТВ8ТУЕЕ_СНЕСК0В00Р, ТВ8ТУЕЕ_СНЕСКСР0ЬР, ТВ8ТУЕЕ_СНЕСКСР0ЬР }; ТВВЬТТОМ ТЬЬ[М0М_В0ТТ0М8]; тетзеШЬЬ, 0, з1хеоТ(ТЬЬ)); Гог (1пТ 1 = 0; 1 < МЬМ_ВЬТТ0МЗ; ++1) { 1Т (И) ТЬЬ[1 ]. 1 ВПтар = С0МВ0_ЗРАСЕ_ШВТН; е!зе 1Т (ЬЬпЮЕИ == Ю_ЗЕР) ТЬЬ[1 ]. 1 ВПтар = 8ЕРАВАТ0В_ШВТН; е!зе ТЬЫО ]. 1 В1Ттар = 1; ТЬЬ[1]. 1с1Соттапс1 = ЬТпЮ[1]; ТЬЬ[1].Сз8ТаТе = ТВЗТАТЕ_ЕМАВЕЕВ; ТЬЬ[1].Тз8Ту1е = ЬТпЗТу1е[1]; } ЬТоо1Ваг = СгеаТеТоо! Ьаг Ех( Шпс1, 1л/3_СН1ЕВ | 1л/3_У131ВЕЕ | И8_В0ВВЕВ | ТВЗТУЕЕ_Т00ЕТ1 РЗ, 1В_Т00ЕВАВ, МиМ-ВЬТТОМЗ, ОеТМосЫеНапсПе(МЬЕЕ), IВВ_Т00ЕВАВ1, ТЬЬ, мим_виттом3, 0, 0, 0, 0, з1 геоГ(ТВВЬТТОМ)); // Определение позиции и размеров для элемента управления СотЬо Ьох 1пТ х, у, сх, су; ВЕСТ гсТТет; Зепс1Ме88аде(ЬТоо1Ваг, ТВ_ОЕТ1ТЕМВЕСТ, 0, (ЕРАВАМ)&гс1Тет); х = гс1Тет.1еТТ + 2; у = гс Кет. Тор; сх = СОМВО-ЗРАСЕ-ШВТН; су = С0МВ0_ЗРАСЕ_НЕ 13НТ; // Создание элемента управления СотЬо Ьох ЬмпсЮотЬо = СгеаТеШпс1ом("сотЬоЬох", МОЕЕ, О3_СН1ЕВ | 1л/3_\/131ВЕЕ | СВЗ-ВВОРВОШ, х, у, сх, су, ЬТоо1Ваг, (НМЕМО)IВС_ТВ_СОМВОВОХ, ЗеТМоОиОеНапсО е(МОЕЕ), 0); // Инициализация списка для ЫпсЮотЬо 8епс1Ме88аде(Ьшс1СотЬо, СВ_АВВ8ТВЖ, 0, (ЕРАВАМ)" 1 "); ЗепОМеззаде(И\л/пс1СотЬо, СВ_АВВЗТВЖ, 0, (ЕРАВАМ)" 2 ");
Листинг 8.3 {продолжение} ЗепбМеззадеСЬшбСотЬо, СВ_А003ТВЖ, О, (ЕРАВАМ)" 5 "); ЗепбМеззадеСЬшбСотЬо, СВ_А003ТВЖ, О, (ЕРАВАМ)" 10 "); ЗепбМеззаде (ЫпсЮотЬо, СВ_ЗЕТС0ВЗЕЕ, 0, 0); геШ ЬТоо1Ваг; } //========================================== уо16 ОрбаЬеТоо! Ваг(ЗЬаре0аЬа& зб) { /* Здесь такой же текст, как и в листинге 8.1 */ } //========================== Е^ЕЗВЕТ САЕЕВАСК ШбРгос(НШ0 Шпб, В11\1Т иМзд, ПРАВАМ мРагат, ЕРАВАМ 1Рагат) { /* Объявления локальных переменных из листинга 8.1 */ зЬаЫс НРЕМ ЬРеп, И01бРеп; зЬаЫс Ш 1пс1Реп: Ш реп1л/1бЬИ[] = { 1, 2, 5, 10 }; з\л/1ЬсИ (иМзд) { сазе Ш_СВЕАТЕ: /* Здесь такой же текст, как и в листинге 8.1 */ Ьгеак; сазе Ш_312Е: ЗепбМез8аде(ЬшбТоо1Ваг, ТВ_АВТ0817Е, 0, 0); Ьгеак; сазе Ш_М0Т1ЕУ: /* Здесь такой же текст, как и в листинге 8.1 */ Ьгеак; сазе 1л1М_С0ММАМ0: з\л/1ЬсИ (ЕОШВОЫРагат)) { /* Обработка для кодов сообщений ЮМ_ОРЕМ, ЮМ_СЕ03Е, ... , ЮМ_ВЕ312Е - как в листинге 8.1 */ сазе ЮМ-АВ0ВТ: МеззадеВохЖпб, "СотЬо1пТоо1Ваг\п\/ег81оп 1.0\пСоруг1 дЬЬ: " "ПпезоГЕ СогрогаЫоп, 2005.", "АЬоьЕ СотЬо1пТоо1Ваг", МВ_0К); Ьгеак; // Обработка уведомительного сообщения от комбинированного списка сазе Ж-ТВ-00МВ0В0Х: з\л/1ЬсИ (НШОВОЫРагат)) { сазе СВМ-ЗЕЕСНАМВЕ: // Выбираем текущий индекс пера 1пс1Реп = ЗепбМеззаде(ЫпсЮотЬо, СВ_ВЕТСВКЗЕЕ, 0, 0);
Ьгеак; (Маи1Ь: Ьгеак; } Ьрс1аЬеТоо1 Ваг(зкареОаЬа); 1пуа11с1аЬеВесЬ(Шг1с1, 1Ш, ТВОЕ); Ьгеак; сазе Ш_РА1МТ: НОС = Вед1 пРа1 пЬ(Шпс1, &рз); ЬпдЫпезз = 1пЬепз1Ьу[зкареОаЬа. 1с1_Ьг1дЩ - 1В_ВАВК]; И (Ь8ком) { НРеп = СгеаЬеРеп(Р8_80Ы0, репШс1Ш1пс1Реп], Р8В(0,0,0)); Н01с1Реп = (НРЕЮ8е1есШэ!есШ0С, кРеп); /* Здесь такой же текст, как и в листинге 8.1 */ 0е1еЬеОЬдесЬ(8е1есЬОЬдесЬ(НОС, НО!СВгизН)); Ое!еЬеОЬдесЬ(8е!есЬОЬдесЬ(НОС, НО!СРеп)); } ЕпбРа! пЬ (Н1л/пб, &рз); Ьгеак; сазе Ш_ВЕ8ТВ0У: РозЬОьп ЬМеззаде(0); Ьгеак; ЬеТаиИ: геЬигп ВеТШпсЫРгос(Нл1пс1, иМзд, мРагагтг 1Рагат); } геЬигп 0; } ////////////////////////////////////////////////////////////////////// В этой программе количество кнопок на панели инструментов увеличено до тринадцати, так как добавлены две кнопки-разделителя в начале панели. Первая из этих кнопок-разделителей, имеющая ширину С0МВ0_5РАСЕ_МЮТН, использует- ся для резервирования места под комбинированный список. Вторая кнопка-раз- делитель создает промежуток между комбинированным списком и первой стан- дартной кнопкой панели инструментов. Обратите внимание на то, что в массивах ЫпЮ и Ып51:у1е учтено появление новых кнопок. Первые два элемента в ЫпЮ инициализированы значением Ю_5ЕР, а первые два элемента в Ып51:у1е инициализированы значением ТВ$ТУ1_Е_$ЕР. Панель инструментов создается, как и раньше, вызовом функции Сгеа1:еТоо1ЬагЕх. Затем определяются позиция и размеры для встраиваемого комбинированного списка. Поскольку он размещается на месте первой кнопки-разделителя, мы уз- наем координаты изображения этой кнопки, имеющей нулевой индекс, отправив сообщение ТВ_6ЕТ1ТЕМРЕСТ. В результате структура гсИет получает искомые ко- ординаты кнопки-разделителя. Эти координаты используются затем для вычис- ления позиции (х, у) встраиваемого элемента управления.
Дочернее окно комбинированного списка Нмпс1СотЬо создается вызовом функ- ции Сгеа1е\Л/1’пс1о\л/. Инициализация списка осуществляется отправкой серии сооб- щений СВ_А005ТК1М6. В тексте оконной процедуры \Л/пс1Ргос произошли определенные изменения. Прежде всего были добавлены определения переменных: $Шс НРЕМ НРеп, ЬСШРеп; 8'Ьа'Ыс 1пЬ 1пс1Реп: Ш репШсЕШ = { 1, 2, 5, 10 }; Также добавлена обработка уведомительного сообщения СВМ_$Е1_СНАМ6Е от ком- бинированного списка: сазе ЮС_ТВ_СОМВОВОХ: змПсб (НШОРВЫРагат)) { сазе СВИ-ЗЕЕСНАМСЕ: // Выбираем текущий индекс пера 1пс1Реп = ЗепбМеззаде (ЫпбСотЬо, СВ_6ЕТСНР8ЕЕ, 0, 0); } Ьгеак; В блоке обработки сообщения ММ_РА1МТ выбранный индекс пера используется при создании пера НРеп: ИРеп = СгеабеРеп(РЗ_ЗОЫВ, репШбШ1пбРеп], РбВ(О.О.О)); Ь01бРеп = (НРЕМ)3е1 есЬОЬдесб(НОС, НРеп); На рис. 8.9 показано окно работающего приложения СотЬоТпТооШаг после вы- бора в окне комбинированного списка значения 10. Рис. 8.9. Приложение СотЬо1пТоо1Ьаг. Выбрана толщина пера 10 логических единиц К сожалению, в нашей новой программе комбинированный список, встроен- ный на панель инструментов, отличается от кнопок панели тем, что не имеет окна подсказки. В следующем разделе мы сможем устранить этот недостаток, приме- нив автономный элемент управления ТооПпр. Окно подсказки В отличие от встроенного на панель инструментов элемента управления ТооЙ1р, автономный элемент управления ТооЙ1р создается в явном виде как окно предоп- ределенного оконного класса Т001_Т1Р$_С1_А$$ при помощи функции Сгеа1е\Л/1’ пс1о\л/Ех. В дальнейшем изложении, употребляя термин «окно подсказки», мы будем гово- рить именно об автономном элементе управления ТооШр.
Следующий фрагмент кода показывает, как обычно создается окно подсказки: Ь/псГПр = СгеабеШпбомЕх(1л18_ЕХ_Т0РМ08Т, Т00ЕТ1 Р8_СЕА88, МОЕЕ, 1л18_Р0РбР | ТТ8_М0РЕЕЕIX | ТТ8_АЕл1АХ8ТIР, СЕМЗЗЕОЕЕАиЕТ, СЕМЗЗЕОЕЕАиЕТ, СМ_иЗЕ0ЕЕАЕЕТ, С1л/_иЗЕ0ЕЕАЕЕТ, ЫпсЮшег, ИЕ1ЕЕ, Щпзб, ИЕ1ЕЕ): Элемент управления ТооПпр всегда имеет стили М$_ЕХ_Т0РМ05Т и М$_Р0Р11Р, неза- висимо от того, указаны ли они при вызове функции Сгеа1е\Л/1 пдо\л/Ех. Дополнительно можно использовать стиль ТТ$_М0РКЕЕ1Х, означающий, что символ амперсанда (&) не является префиксом для обозначения «горячей» клавиши в меню и выводится в строке подсказки наравне с другими символами. Также можно использовать стиль ТТ$_А1_МАУ5Т1Р, означающий, что окно подсказки появляется при наведении кур- сора мыши на инструмент даже тогда, когда окно-владелец подсказки неактивно. Напомним, что элемент управления Тоо1±1р может хранить список «горячих» областей или инструментов (кю!). Каждый инструмент — это некоторая прямоу- гольная область в обслуживаемом окне (в пределе — все окно). Элемент управления То о 111 р начинает поддерживать некий инструмент только после его регистрации. Регистрация инструмента осуществляется отправкой со- общения ТТМ_А00Т001_, например, при помощи следующей инструкции: ЗепбМеззадеШшсГПр, ТТМ_АООТООЕ, 0, (ЕРАЕАМ)(ЕРТООЕIМЕО) Ш ; Последнему параметру функции 5епс1Ме$$аде передается адрес структурной пе- ременной В типа ТОО И МЕО. Структура ТОО И МЕО имеет следующее определение: УуресЕТ збгысб 1;адТ00Е1МЕ0 { Е1МТ сЬ81хе; // размер структуры в байтах Е1МТ ыПадз; // флаги НШО Ыпс1; // дескриптор окна, содержащего инструмент Е/РАЕАМ и1с1; // идентификатор инструмента ЕЕСТ гесб; // ограничивающий прямоугольник для инструмента Н1М8ТАМСЕ М пзб; // дескриптор экземпляра приложения, содержащего // строковый ресурс с текстом подсказки ЕРТ8ТЕ 1рзхТехб; // указатель на буфер с С-строкой или идентификатор // строкового ресурса #1Т (-ШМ32-1Е >= 0x0300) РАРАМ 1Рагат; // необязательное 32-битное значение, // связанное с инструментом #епс!1Т } Т00Е1МЕ0, ЕАР* ЕРТ00Е1МЕ0; Поле и Е1ад$ может содержать один или несколько флагов из тех, что приведе- ны в табл. 8.11. Таблица 8.11. Флаги для поля иНадз Флаг Описание ТП=_СЕ1\1ТЕкТ1Р Окно подсказки выводится в центре по горизонтали под инструментом, а не как по умолчанию — ниже и справа относительно курсора мыши ТТЕ_Ю18Н\Л/МО Означает, что в поле и1б задается дескриптор дочернего окна. Если этот флаг не задан, то поле и1б является идентификатором инструмента ТТЕ_811ВС1_А88 Означает, что окно подсказки будет иметь свою оконную процедуру для перехвата и обработки сообщений, таких как \Л/М_МО118ЕМО\/Е Если флаг ТТЕ_Ю1$НММ0 не указан, то поле и!с1 содержит произвольный иден- тификатор для инструмента. В принципе, этот идентификатор может и не исполь- зоваться в дальнейшей работе.
Комментарий к полю [рзгТех!: показывает, что текст подсказки может хранить- ся либо в виде С-строки, либо в виде строкового ресурса в таблице строк прило- жения. Таким образом, создав элемент управления ТооИз'р и зарегистрировав в нем «го- рячую» область какого-либо окна, можно обеспечить вывод требуемой подсказки. Перечисленные действия имеет смысл инкапсулировать в теле какой-нибудь функ- ции. Такой подход будет продемонстрирован в рассмотренном ниже примере. Замена класса К№к1 на класс КУУпйЕх Вернемся к нашей последней программе, в которой комбинированный список, встроенный в панель инструментов, не имел всплывающей подсказки. Попробу- ем устранить этот недостаток. Одновременно заменим хорошо послуживший нам класс К\Л/пс1 на его модифи- кацию — класс К\Л/пс1Ех. Цель замены — обеспечить регистрацию оконных классов для элементов управления общего пользования в конструкторе класса, чтобы осво- бодить программиста от этой рутинной операции. Кроме того, в файлах К\Л/пс1Ех.Н, К\Л/пс1Ех.срр будут размещены также интерфейс и реализация функций общего при- менения. В состав функций общего применения войдут следующие функции: □ перегруженная функция 8ГпТ±\ЛЛпс1о\л/, предназначенная для сдвига и модифи- кации размеров окон (эта функция использовалась ранее в листинге 7.5); □ функция АдсПооШр, добавляющая окно подсказки к указанному окну; □ функция ТКАСЕ, которую можно использовать для отладочного вывода в окно Ои±ри1: (аналогично макросу ТКАСЕ в библиотеке МЕС). Все во имя программиста, все для блага программиста! Создайте новый проект с именем ТооГПр. Скопируйте из папки проекта СотЬоТпТооШаг (см. листинг 8.3) в папку проекта Тоо1Т1р файлы с расширениями .срр, .К и .гс, скорректировав их имена заменой подстроки СотЬоТпТооШаг на ТооГПр. Скопируйте также файл 1:ооШаг1.Ьтр. Измените имена файлов Кмпс1.Ь, К\л/пс1.срр на К\л/пс1Ех.Н и К\л/пс1Ех.срр соответственно. Добавьте все перечисленные файлы в со- став проекта. Также к настройкам проекта на вкладке Ипк надо добавить библио- теку сотсД32.ИЬ. Откройте вкладку Ке$оигсе\йедо в окне Могкзрасе. В списке ресурсов откройте папку 5Ыпд 1:аЫе и вызовите редактор таблицы строк двойным щелчком мыши на элементе 5±ппд 1:аЫе. Добавьте в таблицу строк следующие две строки: Идентификатор Строка Ю8_ТВ_СОМВОВОХ Доступ к списку для выбора толщины пера Ю8_ЕО1Т_11\1_СОМВО Толщина пера для контура фигуры Обратите внимание на то, что потребовалось указать две строки, чтобы эле- мент управления СотЬо Ьох получил все необходимые подсказки. Первый текст подсказки будет отображаться при наведении курсора мыши на стрелку, исполь- зуемую для открывания списка, второй текст подсказки — при наведении курсо- ра мыши на окно редактирования. Отредактируйте тексты файлов Кмпс1Ех.Ь, Кмпс1 Ех.срр, ТооГПр.срр так, чтобы они соответствовали листингу 8.4.
Листинг 8.4. Проект ТооГПр ////////////////////////////////////////////////////////////////////// // КИпОЕх.б #1пс1иОе <\л/1 пбомз. 6> с1а88 КИпОЕх { риЫ 1с: КИпОЕх(ЕРСТ8ТР \л/1 пбомМате, Н1М8ТАМСЕ 61п8Р, 1 пр ст086ом, ЕКЕЗЕЕТ (ШИАР1 *рИпОРгос) (НШО, Н1МТ,ИРАРАМ, ЕРАРАМ), ЕРСТ8ТР тепиМате = ШЕЕ, 1пР х = СЕМЕЗЕОЕЕАиЕТ, 1 пр у = О, 1 пр \л/1 0Р6 = СМ_Е18Е0ЕЕАЕ1ЕТ, 1 пр 6е1д6Р = О, 01 ИТ с! а883Су1е = С8_НРЕ0РАИ | СЗ_УЕЕ0ЕАМ, ОМОРО м1пбом8Ру1е = МЗ_0УЕЕЕАРРЕ0М IИООМ, НШО бРагепР = ИЕ1ЕЕ); НШО СеРНИпОО { геРигп бИпО; } ргоРесРеО: НШО бИпО; МИОСЕАЗЗЕХ мс: }; //=======================^^ II Функции общего применения // //Сдвиги окон // Сдвиг окна верхнего уровня с модификацией размеров уоЮ 861 ТРМ1 пс1о\л/(НММ0 бшб, 1 пр ОХ = О, 1 пр с1У = О, 1 пр с11л/ = 0, 1 пр ОН = 0): // Сдвиг дочернего окна с модификацией размеров уоЮ 861ТРМ1 пОо\л/(НММО 6С6110, НМИО бРагепР, 1 пр ОХ = О, 1 пр ОХ = О, 1 пр 01л/ = 0, 1 пр ОН = 0): // Сдвиг окна элемента управления с модификацией размеров уоЮ 861 ТР1л/1 пОо\л/( 1 пр сРгПО, НМИО бРагепР, 1 пр ОХ = О, 1 пр ОХ = 0, 1 пр 01л/ = 0, 1 пр ОН = 0): // // Добавление окна подсказки уоЮ А00Тоо1Р1р (НШ0 бшООшег, ЕРТ8ТР 1рМзд): // // Функция отладочной печати (вывод в окно "ОиРриР") // - работает аналогично макросу ТРАСЕ в библиотеке МЕС уоЮ ТРАСЕ(ЕРСТ8ТР ТрзхЕогтаР, ...): ////////////////////////////////////////////////////////////////////// // КМпбЕх.срр #1пс1и0е "КИпОЕх.б" #1пс1и0е <соттсРг!.б> К1л/пОЕх::К1л/пОЕх(ЕРСТ8ТР тпОомбате, Н1М8ТАМ1СЕ 61пзР, 1 пр ст08бом, ЕРЕ8НЕТ (М1ИАР1 *рИпОРгос) (НШ0,Н1МТ,МРАРАМ, ЕРАРАМ), ЕРСТ8ТР тепиМате, 1 пр х, 1пР у, 1 пр \л/10Р6, 1 пр 6е1д6Р, ЕПМТ с1аз88Ру1е, 0ШР0 \л/1 пОомЗРу 1 е, НММО бРагепР) { /* Такой же текст, как и в конструкторе КМпб (листинг 1.2) */ // Инициализация библиотеки "Сотгтюп СопРго! ЕЮгагу"
Листинг 8.4 {продолжение} 1М1ТС0ММ01\1С01\1ТК01_8ЕХ 1 сс; 1сс.бм81хе = 81 геоТ(IИ1ТС0ММ0МС0МТЕ013ЕХ); 1сс.с1\л/1СС = 1СС_ШМ95_СЕА88Е8; 1тТСоттопСопТго18Ех(&1сс); } //=======================^^ II Функции общего применения // // Сдвиг окна верхнего уровня с модификацией размеров уопб 8МШ1пбом(ШМ0 Н\л/пс1, Ш 6Х, Ш 6Х, Ш 61л/, Ш 6Н) { /* Текст функции из листинга 7.5 (файл КИпбРШ.срр) */ } // Сдвиг дочернего окна с модификацией размеров уопб 851ТШ пбоННШО ИСМ16, И РРагепТ, Ш 6Х, Ш 6Х, Ш 61л/, Ш 6Н) { /* Текст функции из листинга 7.5 */ } // Сдвиг окна элемента управления с модификацией размеров уо16 8МТШпбо\л/(Ш сбгПО, И РРагепТ, Ш 6Х, Ш 6Х, Ш 6К Ш 6Н) { /* Текст функции из листинга 7.5 */ } // // Добавление окна подсказки уо16 АббТооИ1р (НШО ЫпбОшег, ЕРТ8ТЕ 1рМзд) { НШО И\л/пбТ1р; // дескриптор элемента управления ТооИ1р 8баТ1с Т00Е1МЕ0 ; // информация об инструменте (о «горячей» области), // обслуживаемом элементом ЫпбТ1р Н1М8ТА1Х1СЕ Н1П8-6 = (Н1М8ТАМСЕ)СеШпбомЕопд(ЫпбОшег, 31л/Е_НIИ8ТАМСЕ); ЫпбТ1р = Сгеа-ЕеШпбомЕх(И8_ЕХ_Т0РМ08Т, Т00ЕТ1 Р8_СЕА88, ШЕЕ, И8_Р0РбР | ТТ8_1\10РЕЕЕ1Х | ТТ8_АЕ1л1АХ8Т1Р, СиМДЗЕОЕЕАбЕТ, С1л/_бЗЕ0ЕЕАбЕТ, С1л/_бЗЕ0ЕЕАбЕТ, С1л/_бЗЕ0ЕЕАбЕТ, ЫпбОмпег, МЫЕЕ, бТпзТ, ШЕЕ); // Инициализация структуры Т1.сЬ81хе = зпгеоТ(ТООЕIМЕО); Т1. ыПадз = ТТЕ_8бВСЕА88; . Ыпб = ЫпбОшег; .МпзТ = б1пзб; .и1б = 0; .ТрзхТехТ = 1рМзд; // Местоположение инструмента в окне ЫпбОмпег // (инструмент покрывает всю клиентскую область) 6еЮ1 епТРесТ (ЫпбОшег, &Т1.гесТ); // Регистрация инструмента с информацией 8епбМе88аде(ЫпбТ1р, ТТМ_АООТООЕ, 0, (ЕРАВАМ)(ЕРТООЕIМЕО) Ш ); } //
// Функция отладочной печати (аналог макроса ТРАСЕ в библиотеке МЕС) #0еС1пе М 512 7016 ТРАСЕ(ЕРСТ8ТР зхЕогтаб, ...) { уа_11зб агдз; 7а_зТагТ(агдз, зхЕогтаб); Ш пВиТ; сбаг згВиТТегЕМ]; пВиТ = _78прг1пШз2ВиТТег, И, зхЕогтаб, агдз); 1Т (пВиГ < 0) { МеззадеВох(МОЕЕ, "Слишком длинная строка для ТРАСЕ!", "Ошибка", МВ_ОК | МВ-1С0И5Т0Р); зхВиТТег[М-2] = '\п'; зхВиТТег[М-1] = 0; } 0иТриТ0еЬид8Тг1пд(зхВиСТег); уа_епб(агдз); } ////////////////////////////////////////////////////////////////////// // ТооП1р.срр #1пс1ис1е <т/1 пбот/з. И> #1пс1ис1е <соттсбг1 ,Е1> #1пс1ис1е <зТс11о.Е1> #1пс1ис1е "К0пс1Ех.ЕГ #1пс1ис1е "гезоыгсе.И" /* Определения макросов, типов и глобальных переменных из листинга 8.3 */ ЕРЕЗОЕТ САЕЕВАСК ОпОРгос(НШО, 01МТ, ОРАРАМ, ЕРАРАМ); //============================================ 1пТ ШМАР1 1л/1пМапп(Н1М8ТАМСЕ ЕПпзТапсе, Н1М8ТАМСЕ МРгеуТпзТапсе, ЕР8ТР 1рСтс1Е1пе, Ш пСтсЮИот/) { М80 тзд; КОпбЕх та1п1л!пс1("ТооП1р", ЕПпзТапсе, пСтОЗИот/, ОпбРгос, МАКЕ1МТРЕ800РСЕ(10Р_МЕМ01), 100, 100, 400, 300); // Добавление окна подсказки А0с1Тоо1Т1р (та1п1л!пс1.6еТШпс1(), "Это клиентская область главного окна приложения"); т/Е111е (ОеТМеззадеС&тзд, МОЕЕ, 0, 0)) { ТгапзШеМеззадеС&тзд); 01зраТсММеззадеС&тзд); } геТигп тзд.т/Рагат; } II
Листинг 8.4 {продолжение} НШ0 1пШоо1Ваг(ШМ0 Шпс1) { /* Здесь такой же текст, как и в листинге 8.3 */ // Добавление окон подсказки Ас1сГГооИ1р (ЫпсЮотЬо, МАКЕ I МТ^ЕЗОб^СЕ (Ю8_ТВ_С0МВ0В0Х)); НШВ НмпбЕсН = 6еШ пскМЫпсЮотЬо, 61л1_СН1Ь0); Ас1с1ТооИ1р (Ыпс1Ес1П, МАКЕIМТЕЕ501ЖСЕ(IВЗ_ЕВ1Т_1 И_СОМВО)); гебыгп ЬТоо1Ваг; } //========================================== уо1с1 Ьрс1аЬеТоо1Ваг(8ЬареВаЬа& зс!) { /* Здесь такой же текст, как и в листинге 8.3 */ } //========================================== ЕЕЕЗВЕТ САЫВАСК ШЬРгос(ШИВ Шпс1, б11\1Т иМзд, ПРАВАМ мРагат, ЕРАВАМ 1Рагат) { /* Здесь такой же текст, как и в листинге 8.3, за исключением фрагмента: сазе 1ВМ-АВ06Т: МеззадеВох(Шпс1, "ТооП1р\п\/ег81оп 1.0\пСоруг1 дЬЬ: ПпезоЫ СогрогаЫоп, 2005.", "АЬоыб ТооП1р", МВ_ОК): Ьгеак: */ } ////////////////////////////////////////////////////////////////////// Обратите внимание на следующие особенности программы: □ Файлы К\Л/пс1Ех.Н и К\Л/пдЕх.срр содержат интерфейс и реализацию класса К\Л/пс1Ех, а также интерфейс и реализацию функций общего применения. Класс К\Л/пс1Ех отличается от класса К\Л/пд только реализацией конструктора. В новом классе добавлен вызов функции 1пйСоттопСоп1:го1$Ех, регистрирую- щей оконные классы элементов управления общего пользования. □ Функция АдсПооШр, имеющая следующий прототип: уо1с1 Ас1сГГоо1Ыр (НЮ ЫпсЮшег, ЕРТЗТВ 1рМзд): предназначена для добавления элемента управления ТооПпр к окну НдопсЮдопег. Второму параметру функции передается либо указатель на С-строку, либо иден- тификатор строкового ресурса в таблице строк приложения. Хотя элемент управления То о 11л р допускает регистрацию нескольких инстру- ментов для окна НдопдОдопет, для упрощения реализации функция АдсПооШр ре- гистрирует только один инструмент, интерпретируемый как вся клиентская область окна НдопсЮдопег. Первый раз функция АдсПоо1йр вызывается в теле функции УУтпИатп: Ас1сГГоо1Ыр (та1п1л!пс1.6еЬН1л1пс1(), "Это клиентская область главного окна приложения"): Второй и третий вызовы включены в текст функции 1шЧТоо1Ваг: Ас1сГГоо1Ыр (ЫпбСотЬо, МАКЕIМТЕЕЗОЬЕСЕ(103_ТВ_С0МВ0В0Х)); ШИВ И\л/пс1Ес11Ь = беШпбоНЬшбСотЬо, СН1ЕВ): Ас1сГГоо1Ыр (ЫпбЕЬП, МАКЕ1МТВЕЗОЬВСЕ(IВЗ_ЕВ1Т_11\1_СОМВО));
Обратите внимание на использованный способ получения дескриптора окна редактирования, входящего в состав комбинированного списка. □ Функция ТРАСЕ, имеющая следующий прототип: 7016 ТКАСЕ(1_РСТ5ТК згЕоггтМ, ...) предназначена для отладочного вывода в окно 0ьйри!:. Функция принимает пе- ременное количество аргументов, и обращение к ней похоже на обращение к функции рпгйГ из библиотеки С. Реализация функции основана на использо- вании функции 0и1:ри1:0еЬид51:ппд. Программисты, написавшие хотя бы один проект с библиотекой МЕС, сразу оце- нивают удобство использования макроса ТРАСЕ в режиме отладки. К сожалению, АУш32 АР1 этот макрос не поддерживает. Но теперь, подключив к любому проекту файлы Кмпс1Ех.Н и К\л/пс1 Ех.срр, вы не будете чувствовать себя ущемленными! Чтобы использовать отладочную печать, не забудьте выполнить два условия. Во-первых, проект должен компилироваться в отладочной конфигурации (АУш32 ВеЬп§). Во-вторых, программа должна запускаться на выполнение в режиме от- ладки при помощи команды меню 5±аг± ОеЬид ► 6о или клавиши Е5. Откомпилируйте проект и проверьте, как работает подсказка для комбиниро- ванного списка в панели инструментов и для главного окна приложения. Проверьте также работу функции отладочного вывода ТРАСЕ. В блок обработ- ки сообщения ММ_М0Т1ЕУ можно добавить следующую инструкцию: ТВАСЕС'иМзд = %04Х, т/Рагат = %08Х, 1 Рагат = йО8Х\п", иМзд, мРагат, 1 Рагат); Теперь, если запустить программу в режиме отладки, благодаря функции ТКАСЕ в окно Ои±ри1: будут выводиться значения иМ$д, доРагат и [Рагат при каждом по- ступлении в оконную процедуру сообщения ММ_М0Т1ЕУ. Отладочная печать будет выглядеть примерно так: иМзд = 004Е, т/Рагат = 000000С9, 1 Рагат = 0012Е768 иМзд = 004Е, мРагат = 00009С48, 1Рагат = 00135АС0 иМзд = 004Е, мРагат = 00009С48, 1Рагат = 0012ЕА34 Строка состояния Строка состояния {зШиз Ьаг) — это окно, обычно располагающееся в нижней части главного окна приложения и предназначенное для информирования поль- зователя о текущем состоянии программы, о выполняемых операциях и режи- мах. Довольно часто строка состояния используется для отображения подроб- ного описания пунктов меню при наведении на них курсора мыши. Когда меню не просматривается, приложение может выводить другую справочную инфор- мацию. Для создания строки состояния следует использовать функцию Стеа1:е51:а1:и$\Л/1’ п дом: ЫпсЕШизВаг = Сгеа^еЗШизШпс1ом(1л18_СН1ЕВ | 1л18_\/181ВЕЕ, Шпс1РагеггЕ, Ю_8ТАТи8_ВАР); В процессе своего выполнения эта функция вызывает функцию Сгеа1:е\Л/1’пс1о\л/, которая создает дочернее окно с родительским окном Н\Л/пс1 РагепЕ идентификато- ром Ю_5ТАТ115_ВАР и выводит в окно строки состояния текст, заданный вторым параметром. В данном примере будет использоваться пустая строка.
По умолчанию окно строки состояния имеет дополнительные стили СС5_ВОТТОМ (см. табл. 8.7) и 5ВАР5_512Е6Р1Р. Стиль 5ВАР5_512Е6Р1Р задает наличие «манипу- лятора размера» в правом углу строки состояния. Этот декоративный элемент соз- дает область, за которую можно ухватиться при изменении размера окна прило- жения. Строка состояния может работать в двух режимах: □ Стандартный, или многочастный, режим {Ми1йр1е-РаП 8Шиз Вагз), в кото- ром строка состояния разбивается на несколько частей (полей). В каждом поле выводится отдельная строка текста. □ Простой режим {51тр1е Мойе МаШз Вагз), в котором строка состояния реали- зована как единый элемент и отображает только одну строку. Простой режим чаще всего используется при отображении справочной информации для пунк- тов меню, когда пользователь перемещается по меню. Переключение между режимами осуществляется посылкой сообщения $В_$1МР1_Е: 8епбМе88аде(Ыпс18Ши8Ваг, 8В_81МР1Е, ГМобе, 0); Когда параметр ГМос1е имеет значение ТРОЕ, то устанавливается простой режим. При использовании значения ЕА1_8Е строка состояния будет работать в стандарт- ном (многочастном) режиме. Разделение строки состояния на поля Если строка состояния используется в стандартном режиме, то вы должны разде- лить ее на отдельные поля при помощи отсылки сообщения 5В_5ЕТРАКТ5: 8епбМе88аде(Ыпс18Ши8Ваг, 8В_8ЕТРАВТ8, пРаг^8, (ЕРАВАМ) аШ с1Ш); Здесь параметр п РатЕз задает число полей, а аМ1сН±1$ — адрес целочисленного массива, каждый элемент которого определяет позицию (в клиентских координа- тах) правой границы соответствующего поля. Если элемент массива аУУтсШпз равен -1, то границей соответствующего поля считается правая граница строки состоя- ния. Обычно в каждое поле строки состояния выводится отдельное текстовое сооб- щение. Но вы можете также разместить в любом поле другой элемент управле- ния, например индикатор процесса (Ргодгезз Ьаг). При таком размещении полезно знать клиентские координаты этого поля, которые могут быть получены при по- мощи отправки сообщения 5В_6ЕТКЕСТ: 8епбМе88аде(Ыпс18Ши8Ваг, 8В_8ЕТВЕСТ, 1Раг^, (ЕРАВАМ)йгесД); где 1 РагЕ — номер поля, отсчитываемый от нуля, а гесЕ — структура типа КЕСТ, при- нимающая координаты поля. Вывод текстового сообщения Для отображения текста в строке состояния посылается сообщение 5В_5ЕТТЕХТ: 8епбМе88аде(Ыпс18Ши8Ваг, 8В_8ЕТТЕХТ, мРагат, (ЕРАВАМ)8хТехЩ; где згТех!: — указатель на С-строку, а доРагат задает номер поля в строке состояния и графический стиль для этого поля. Значение параметра доРагат может быть задано в виде объединения 1 Раг± | иТуре. Если строка состояния работает в стандартном режиме, то 1 Раг± содержит но- мер поля. Если используется простой режим, то параметр 1 Раг± должен иметь зна- чение 255.
Флаг иТуре может иметь одно из следующих значений: Значение флага иТуре Интерпретация 0 (значение по умолчанию) Поле рисуется с вдавленной рамкой 8ВТ_1\ЮВОКОЕк8 Поле рисуется без рамки 8ВТ.РОРО11Т Поле рисуется с выпуклой рамкой 8ВТ_СМ/ЫЕкОИА\Л/ За прорисовку поля отвечает родительское окно Максимальная длина строки, выводимой в каждом поле, составляет 127 сим- волов. Размеры и позиция строки состояния Оконная процедура дочернего окна строки состояния автоматически устанавли- вает начальную позицию и размеры этого элемента управления. Ширина строки состояния равна ширине клиентской области родительского окна. Высота строки состояния устанавливается на основе метрик шрифта, выбранного по умолчанию в контекст устройства элемента управления. В дальнейшем оконная процедура строки состояния автоматически регулирует ее позицию и ширину всякий раз, когда получает сообщение ММ_$12Е. Из этого выте- кает, что при каждом изменении размеров родительского окна, то есть при получе- нии сообщения ММ_$12Е, оконная процедура \Л/пс1 Ргос должна отправить строке состо- яния такое же сообщение, передав текущие значения параметров мРагат и [Рагат: Зепс1Ме88аде (ЫпсЕШизВаг, Ш_812Е, мРагат, 1 Рагат); Если высота строки состояния по умолчанию вас не устраивает, вы можете ее изменить, послав сообщение $В_$ЕТМ1МНЕ16НТ: 8епбМе88аде(Ыпс18Ши8Ваг, 5В_5ЕТМ1ИНЕ16НТ, т1пНе1д1тЕ, 0); где 1тппНетдЫ: — минимальная высота окна строки состояния в пикселах. На самом деле \У1пс1о\\ъ устанавливает высоту окна строки состояния, равной гтпНетдЫ: + + 2*М/В, где \лА/В — ширина вертикальной рамки окна. Возможность увеличить высоту строки состояния бывает полезной, если на ней размещены другие дочерние окна элементов управления (см. ниже приложе- ние РгодгеззВаг). Уменьшение видимой части клиентской области главного окна Так же как и панель инструментов, строка состояния занимает часть клиентской области главного окна приложения. К сожалению, АУтйомга не корректирует ав- томатически размеры и начало координат клиентской области при появлении панели инструментов и строки состояния. В приложении Тоо1Ваг мы вычисляли прямоугольник, ограничивающий видимую часть клиентской области, с учетом высоты панели инструментов 1ЬНе1’дН1. Точно так же при использовании строки состояния необходимо учитывать уменьшение высоты видимой части клиентс- кой области на величину, равную высоте строки состояния. Поддержка просмотра меню Пользователи, работающие с АУшбом^-программами, привыкли, что в строке со- стояния отображается информация о назначении выбранного ими пункта меню. Меню посылает сообщение ММ_МЕМ11$Е1_ЕСТ, когда пользователь осуществляет
навигацию по его пунктам с помощью мыши или клавиатуры. Поэтому для поддерж- ки отображения справочной информации необходимо обрабатывать это сооб- щение. Когда оконная процедура получает сообщение ММ_МЕМ11$Е1_ЕСТ, из его парамет- ров можно извлечь следующую информацию: □ Младшее слово параметра доРагат содержит идентификатор пункта меню, если это пункт-команда, или индекс подменю (если это пункт-подменю). □ Старшее слово параметра доРагат содержит один из флагов, характеризующих статус выбранного пункта меню. Из всех возможных флагов сейчас нас будет интересовать только флаг МЕ_РОР11Р, означающий, что данный пункт открыва- ет всплывающее меню или подменю. □ Параметр 1Рагат содержит дескриптор меню, которому принадлежит выбран- ный пункт. АУш32 АР1 содержит функцию МепиНеф, предназначенную для упрощения об- работки сообщений ММ_МЕМ11$Е1_ЕСТ и отображения текста в строке состояния. Тех- нология использования этой функции описана в [1]. Однако, на мой взгляд, эта технология довольно громоздка и неудобна. Здесь предлагается альтернативная технология, в которой функция МепиНеф не применяется. Ранее мы уже убедились, что бывает очень удобно использовать ресурс табли- цы строк при отображении окон подсказок для кнопок панели инструментов. Простота решения базировалась на совпадении идентификаторов кнопок и иден- тификаторов строк. Хотелось бы использовать аналогичную идею и для решения рассматривае- мой проблемы. Однако если пункты-команды в меню имеют уникальные иденти- фикаторы и мы можем использовать эти же идентификаторы для строк в таблице строк, то с пунктами-подменю ситуация гораздо сложней. В пределах меню каж- дого уровня пункты-подменю нумеруются начиная с нуля, поэтому ни о какой уникальности не может быть и речи. Идея решения, предлагаемая здесь, базируется на некоторой договоренности о системе идентификации таких пунктов меню. Сразу уточним, что мы рассмат- риваем только те меню, которые имеют не более трех уровней. Нулевой уровень — это меню верхнего уровня, подменю в нем имеет первый уровень, а подменю в меню первого уровня получает второй уровень. Большинство АУшскгдоз-программ удовлетворяет этому ограничению. Процедура однозначной идентификации пунктов-подменю Для пунктов-подменю, принадлежащих меню нулевого уровня, в качестве иден- тификаторов используются индексы, присваиваемые им АУтсФмга, то есть 0, 1, 2 и т. д. Эти значения содержатся в младшем слове параметра доРагат сообщения ММ_МЕМ11$Е1_ЕСТ. Для пунктов-подменю, принадлежащих некоторому меню первого уровня, уни- кальный идентификатор формируется на базе индекса йетЮ, извлекаемого из младшего слова параметра доРагат, посредством добавления соответствующего смещения: ПетЮ += 100 * (зиЬтепиЮ + 1); где зиЬтепиЮ — индекс рассматриваемого подменю в меню нулевого уровня.
Таким образом, для меню первого уровня с нулевым индексом ($иЬтепиЮ=0) входящие в него пункты-подменю получат номера 100, 101, 102 и т. д. Для меню первого уровня с единичным индексом входящие в него пункты-подменю полу- чат номера 200, 201, 202 и т. д. Такие же идентификаторы мы будем использовать и для строк в таблице строк приложения. Из принятого нами ограничения на количество уровней меню вытекает, что все пункты второго (последнего) уровня являются пунктами-командами и поэто- му имеют уникальные идентификаторы. Получив в результате обработки сообщения ММ_МЕМ11$Е1_ЕСТ уникальный иден- тификатор пункта меню НетЮ, мы можем извлечь из таблицы строк соответству- ющую символьную строку в текстовый буфер 1ех1, а затем отобразить этот текст в строке состояния, которая работает в простом режиме. Это может быть реализо- вано следующими инструкциями: 8епбМе88аде(Ь/пс18Ши8Ваг, 8В_81МР1Е, ТВОЕ, 0); Еоас181аг1 пд(6е1аМос1и1 еНапсПе(ШЕЕ), ПетЮ, 1ех1, 200); 8епбМе88аде(Ыпб8Ши8Ваг, 8В_8ЕТТЕХТ, 255, (ЕРАВАМ)1ех1); Для демонстрации предложенной технологии разработаем приложение 51а1и$ В а г, являющееся модификацией приложения Тоо1Ваг. В результате доработки к про- грамме будет добавлена строка состояния, работающая в простом и стандартном режимах. Приложение 51а1и$Ваг Создайте новый проект с именем 51а1и$Ваг. Скопируйте из папки проекта Тоо1Ваг (см. листинг 8.1) в папку проекта 51а1и$Ваг файлы Тоо1Ваг.срр, Тоо1Ваг.гс и гезоигсе.Н, скорректировав имена первых двух файлов заменой подстроки Тоо1Ваг на 51а1и$ В ат. Скопируйте также файл 1ооШаг1.Ьтр. Наконец, скопируйте из папки проекта Тоо 1Т1 р (см. листинг 8.4) файлы К\Л/пс1Ех.Н и К\Л/пс1Ех.срр. Добавьте скопированные файлы в состав проекта. К настройкам проекта на вкладке Ипк нужно добавить библиотеку сотс1132.11’Ь. Теперь откройте вкладку Резо и гее VI е\л/ в окне Могкзрасе. В списке ресурсов от- кройте папку 51л пд 1аЫе и вызовите редактор таблицы строк двойным щелчком мыши на элементе 51л пд 1аЫе. Добавьте в таблицу строк следующие строки: Идентификатор Строка 0 1 2 300 301 ЮМ_ОРЕЫ ЮМ_С1_О5Е ЮМ_8АУЕ ЮМ_ЕХ1Т ЮМ_5НО\Л/_5НАРЕ ЮМ_НЮЕ_8НАРЕ ЮМ_КЕ512Е ЮМ.АВО11Т Операции с файлами Параметры просмотра Параметры фигуры Тип фигуры Цвет фигуры Открыть файл Закрыть файл Сохранить файл Выход из программы Показать рисунок Скрыть рисунок Изменение размеров фигуры Информация о программе
Обратите внимание на целочисленные идентификаторы 0, 1,2, 300, 301, назна- ченные в соответствии с описанной выше процедурой однозначной идентифика- ции пунктов-подменю. Отредактируйте текст файла 51а1:и$Ваг.срр так, чтобы он соответствовал лис- тингу 8.5. Листинг 8.5. Проект 51а1и8Ваг ////////////////////////////////////////////////////////////////////// // З'ЕаШВаг.срр #1пс1ис1е <\л/1 пс1о\л/8. И> #1пс1ис1е <сотгтк±г1. Н> #1пс1ис1е #1пс1ис1е "К1л1пс1Ех.И" #1пс1ис1е "гезоигсе.И" #с1еТ1 пе 1л1 200 // ширина фигуры #с1еТ1 пе Н 140 // высота фигуры епит ЗНареЗые { МАХ, М1И }; УуребеГ з^гис^ { Ш тфзНаре; // идентификатор фигуры ВООЕ ГВес1; // компонент красного цвета ВООЕ ГСгееп; // компонент зеленого цвета ВООЕ ГВ1ие; // компонент синего цвета Ш 1 с1_Ьг1 д1тЕ; // идентификатор яркости цвета } ЗОареОа^а; #с1еОпе Ю-ТООЕВАК 201 #с1еОпе 1О_ЗТАТОЗВАК 202 #с1еОпе М0М-В0ТТ0МЗ 11 #с1еОпе ЗЕРАВАТ0В_Ш0ТН 10 #с1еОпе М-РАКТЗ 4 НШ0 ЫпсГГоо! Ваг; Ш ЫпсЕ^аШВаг; НШ0 1пШоо1 Ваг(НШ0 Шпс1); уо1с1 Е1рс1аЦеТоо1 Ваг(ЗИаре0аЦа& зс1); уо1с1 СрсШеЗ^аШВагЧШМО Ыпс1, 30аре0а^а& зс1); ЕкЕЗОЕТ САЕЕВАСК ШбРгос(НШ0, 01МТ, АРАРАМ, ЕРАВАМ); //============================================ 1пР ШИАР1 ШпМапп(Н1МЗТАМСЕ Шнапсе, Н1МЗТАМ1СЕ ОРгеуТпз^апсе, ЕРЗТк 1рСтс1Е1пе, Ш пСгт1с13Ио\л/) { МЗО тзд; КМпбЕх тапп1л/пс1 ("З'Еа'ЕизВаг", ОТпз^апсе, пСтс13Ио\л/, 1л1пс1Ргос, МАКЕ1МТкЕ300кСЕ(10к_МЕМ01), 100, 100, 400, 300); /* Здесь такой же текст, как в листинге 8.1 */ } //==================================================================== Н1л^0 1пПТоо1Ваг(Н1« ВМпС) { /* Здесь такой же текст, как в листинге 8.1 */ } II-
уо1с1 ОрбаТеТоо! Ваг(5Наре0а1.а& зс1) { /* Здесь такой же текст, как в листинге 8.1 */ } //============================== уо1с1 ОрОаТеЗТаТизВагСНЮ И\л/псЕ 80ареВаТа& зс1) { 1пТ рапеШс1ТЫ 1пТ а1л/1 сПоНз ЕМ_РАВТ8Ц: сИаг ТехЩОО]; ВЕСТ гесТ; 1пТ ЬндИТпезз; 1пТ 1пТепз1Ту[3] = { 85, 170, 255 }; ОеТС! 1 епТВесТ (Ыпс1, &гесТ); рапеШТИ = гесТ.г1дИТ / М_РАВТ8; аШШ [0] = рапеШТЫ аШсЕЫ [1] = рапеЫ 6ТИ * 2; аШсЕЫ [2] = рапеЫ 6ТИ * 3; аШШ [3] = -1; ЗепбМеззадеС И\л/пс18ТаТи8Ваг, 8В_8ЕТРАВТ8, М_РАВТ8, (ЕРАВАМ)аШ ОШ); ЕоабЗТгл пд(ЗеТМос1и1 еНапсПе(МОЕЕ), зс1.1 сККаре, ТехТ, 100); ЗепбМеззаде(ЫпсЮТаТизВаг, 8В_8ЕТТЕХТ, 0, (ЕРАВАМ)ТехТ); ЬндИТпезз = 1 пТепз1 Ту[зс1.1 с1_Ьг1 дКТ - 1В_ВАВК]; 1 пТ геб = зсЕ ТВеб? ЬндИТпезз : 0; зрг1 пШТехТ, "Веб = %сГ, геб); ЗепбМеззадеСИмпбЗТаТизВаг, 8В_8ЕТТЕХТ, 1, (ЕРАВАМ)ТехТ); 1 пТ дгееп = зсЕ ТСгееп? ЬндИТпезз : 0; зрг1пТТ(ТехТ, "Огееп = М', дгееп); ЗепбМеззадеСИ\л/пб8ТаТи8Ваг, 8В_8ЕТТЕХТ, 2, (ЕРАВАМ)ТехТ); 1 пТ Ыие = зс1. ТВ1 ие? ЬндИТпезз : 0; зрг1 пШТехТ, "В1ие = %сГ, Ыие); ЗепбМеззадеЫшбЗТаТизВаг, 8В_8ЕТТЕХТ, 3, (ЕРАВАМ)ТехТ); } //=============================== ЕВЕ8ЕЕТ САЕЕВАСК ШОРгос(НШВ Шпс1, Е1МТ иМзд, АРАРАМ мРагат, ЕРАВАМ 1Рагат) { /* Определения локальных переменных из листинга 8.1 */ ВЕСТ гс8В; // позиция и размеры окна ЫпсЕТаТизВаг 1пТ зЬНепдИТ; // высота окна ЫпсЮТаТизВаг НМЕИО КСЫскМепи; 1 пТ л'ТегпЮ; зТаТпс Е111Х1Т зиЬтепиЫ; сВаг ТехТЕ200Ц; 8\л/1ТсИ (иМзд) { сазе Ш_СВЕАТЕ: /* Здесь такой же текст, как в листинге 8.1 */ // Создание строки состояния продолжение
Листинг 8.5 {продолжение} ЫпсЕТаТызВаг = СгеаТеЗТаТызШпсЫ(08_СНIЬО | 1л18_\/181 В1_Е, Шпс1, I0_8ТАТ08ВАВ); Ьгеак; сазе Ш_812Е: ЗепОМеззадеСЫпсГГоо! Ваг, ТВ_А0Т0812Е, 0, 0); 0рс1аТе8ТаТизВаг(Шпс1, збареОаТа); ЗепОМеззаде (ЫпсЕТаТызВаг, Ш_812Е, мРагат, 1 Рагат); Ьгеак; сазе Ш_М0Т1ЕУ: /* Здесь такой же текст, как в листинге 8.1 */ Ьгеак; сазе 1л/М_МЕМЬ8ЕЕЕСТ: ПетЮ = ЕОШВОЫРагат); // меню нулевого уровня ИСИскМепы = (НМЕМЬ) 1 Рагат; Ю (НШОВОЫРагат) & МЕ_РОРОР) { 1Г (ИСИскМепы == ЬМепы) зыЬтепыЮ = 1ТетЮ; е!зе ПетЮ += 100 * (зыЬтепыЮ + 1); е!зе 1Г (! 1ТетЮ) ПетЮ = -1; 5епс1Ме88аде(Ыпс18ЬаЬи8Ваг, 5В_51МРЕЕ, ТВОЕ, 0); Ьоас18Тг1пд(0еТМос1ы1еНапс11е(М0ЕЕ), 1ТетЮ, ТехТ, 200); ЗепОМеззадеСЬшОЗТаТызВаг, 8В_8ЕТТЕХТ, 255, (ЬРАВАМ)ТехТ); Ьгеак; сазе 1л1М_С0ММАМ0: зтТсЬ (ЬОШВОЫРагат)) { /* Обработка для кодов сообщений ЮМ_ОРЕМ, ЮМ_СЬ08Е, ... , ЮМ_ВЕ812Е - как в листинге 8.1 */ сазе ЮМ-АВ00Т: МеззадеВох(Шпс1, "ЗТаТызВагХпУегзЮп 1.0\пСоруг1 дЬЬ: " "ПпезоГЕ СогрогаЫоп, 2005.", "АЬоыЬ ЗЬаЬызВаг", МВ_0К); Ьгеак; с1еГаы1Ь: Ьгеак; 0рбаЬеТоо1Ваг(зЬаре0аЬа); ЗепбМеззадеСЬшбЗЬаЬизВаг, 5В_51МРЕЕ, ЕАЕЗЕ, 0); ОрбаЬеЗЬаЬизВаг(Шпс1, зЬареОаба); 1пуа11 ЬаЬеВесЬ(Шпс1, МОЕЕ, ТВОЕ);
Ьгеак; сазе 1л/М_РА1МТ: НОС = Вед1 пРа1 пЬ(Шпс1, &рз); ЬндИЬпезз = ч’пЬепзПуЕзЬареОаЬа. 1с1_Ьг1д1тЕ - Ю_0АРК]; 1Т (ЬЗЬом) { ЬВгизЬ = СгеаЬеЗо! 1 сИВгизН (РОВ( зЬареОаба.ТРеб? ЬпдНЬпезз : О, зЬареОаба.Тбгееп? ЬндНЬпезз : О, зЬареОаба.Шие? Ьп’дИЬпезз : 0)); И01 бВгизИ = (НВРЫЗН)8е1 есЬОЬцесб(ВВС, НВгизЮ; // Определение позиции и размеров фигуры с учетом // уменьшения клиентской области из-за размещения // панели инструментов и строки состояния СМС11епЬРесЬ(Шпс1, &гесб); беЬ1л11пбомРесЬ(ЬшбТоо1Ваг, &гсТВ); ЬЬНе1дЬЬ = гсТВ.ЬоНот - гсТВ.Ьор; 0еШпс1омКесЬ(Ыг1с18ЬаЬи8Ваг, &гс8В); зЬНе1дИЬ = гсЗВ.ЬоЬЬот - гсЗВ.Ьор; хО = гесЬ.п’дИЬ / 2; уО = ЬЬНе1дЬЬ + (гесб.ЬоНот - ЬЬНе1 дЬЬ - зЬНе1дИЬ) / 2; // Координаты прямоугольника и эллипса 1Т (зИареЗпге == М1Ю { Х1 = хО - 1л1/2; у1 = уО - Н/2; х2 = хО + 1л1/2; у2 = уО + Н/2; } е!зе { х1 = 0; у1 = ЬЬНе1дЬЬ; х2 = гесЬ.н’дИЬ; у2 = гесб.ЬоЬЬот - зЬНе1дИЬ; } // Координаты ромба рШх = (х1 + х2) / 2; рЬ[0].у = у1; рЩ].х = х2; рШЪу = (у1 + у2) / 2; рб[2].х = (х1 + х2) / 2; рб[2].у = у2; рЬ[3] .х = х1; р-ЬВЗО .у = (у1 + у2) / 2; 8\л/1ЬсИ (зЬареОаба. 1 с1_зИаре) { сазе Ю-РЕСТАМЗЬЕ: РесЬапд!е(НОС, х1, у1, х2, у2); Ьгеак; сазе Ю-КНОМВ: Ро1удоп(НОС, рЬ, 4); Ьгеак; сазе Ю_ЕШР8Е: Е111рзе(НОС, х1, у1, х2, у2); Ьгеак; } □е!е'ЬеОЬзесКЗе!есЛОЬоесНКОС, Ь01 СВгизН)); } ЕпбРап пб (Шпс1, &рз); Ьгеак; сазе Ш_0Е8ТР0У: РозЬОьпЬМеззадеСО);
Листинг 8.5 {продолжение} Ьгеак; с1еГаи!Ь: геЬигп ОеГШ псЫРгос(Шпс1, иМзд, мРагат, 1 Рагат); } геЬигп 0; } ////////////////////////////////////////////////////////////////////// Приложение использует строку состояния в стандартном и простом режимах. Действия по формированию строки состояния, работающей в стандартном режиме, инкапсулированы в функцию IIрс1а1:е51:а1:и$Ваг. Эта функция вызывается при обработке сообщений ММ_$12Е и ММ_С0ММА1\10. Строка состояния, работающая в простом режиме, формируется кодом в бло- ке обработки сообщения ММ_МЕМ11$Е1_ЕСТ: ПетЮ = ЬОШРО(мРагат); ИСПскМепи = (НМЕМЫ) 1 Рагат; 1Г (НШОРОЫРагат) & МЕ_РОРЬР) { 1Г (ИСПскМепи == ЬМепи) зиЬтепиЮ = ПетЮ; е!зе ПетЮ += 100 * (зиЬтепиЮ + 1); } е!зе 1Г (ИЬетЮ) ПетЮ = -1; Алгоритм вычисления значения йетЮ реализует описанную выше процедуру однозначной идентификации пунктов-подменю. Дополнительного пояснения требуют две последние строки в этом фрагменте. В процессе отладки программы выяснилось, что при наведении курсора мыши на разделительную линию (5ЕРАВАТОВ) АУхпбом^з посылает сообщение ММ_МЕМ11$Е1_ЕСТ, в котором 1_ОМОКО(мРагат) равно нулю. Чтобы исключить интерпретацию этого зна- чения как индекса подменю, добавлена последняя ветвь е1$е. К счастью, старшее сло- во параметра доРагат в этой ситуации не содержит флага МЕ_РОР11Р. Это позволяет обнаружить разделитель и присвоить идентификатору йетЮ значение -1. Иденти- фикатор со значением -1 гарантированно отсутствует в таблице строк приложения. На рис. 8.10 показано окно работающего приложения 51а1:и$Ваг. Рекомендуем вам проверить работу приложения в других режимах. Рис. 8.10. Приложение ЗЩЩзВаг. Строка состояния — в стандартном режиме
Другие элементы управления Из других элементов управления общего пользования мы рассмотрим только Ргодгезз Ьаг, 511с1еги 5р1п. Индикатор процесса Индикатор процесса, или элемент управления Ргодгезз Ьаг реализован как дочер- нее окно, которое может использоваться в приложении для отображения процес- са выполнения некоторой длительной операции. Индикатор процесса выглядит как вытянутая и расположенная горизонтально или вертикально прямоугольная область. По мере выполнения операции область заполняется слева направо или снизу вверх небольшими прямоугольниками, что позволяет показать пользовате- лю прогресс в выполнении текущей операции. Индикатор процесса создается при помощи функции Сгеа1е\ЛЛпс1о\л/ или Сгеа1е \Л/1’пс1о\л/Ех с указанием идентификатора РКО6КЕ55_СЕА55 в качестве оконного клас- са, например: ЫпбРгодгеззВаг = СгеаШПпс1о\л/(РЕСОЕ88_СЕА88, ШЕЕ, 1л18_СНИ0 | 1л18_7181ВЕЕ, х, у, мсЖ Ие1дШ, Ел/пс1РагегД, МЕ1ЕЕ, МЕ1ЕЕ, ШЕЕ); По умолчанию индикатор процесса имеет горизонтальную ориентацию и за- полняется отдельными маленькими прямоугольниками, окрашенными системным цветом С0ЮК.Н16НИ6НТ1. Если при вызове функции СгеаЕеУУ! п с1о\л/ дополнительно указать стиль РВ$_УЕКТ1СА1_, то индикатор будет иметь вертикальную ориентацию. Термин «ориентация» здесь относится только к способу заполнения окна индикатора прямоугольниками: слева направо или снизу вверх. Форма же окна определяется только параметрами и НетдНЕ Если вы предпочитаете использовать сплошное заполнение окна индикатора процесса, без маленьких промежутков между прямоугольниками, то укажите до- полнительно стиль РВ5_5МООТН. Реализация процесса и его связи с индикацией полностью лежат на плечах разработчика приложения. Собственно индикатор является лишь средством ото- бражения отношения двух величин. Интервал (гап§е) индикатора процесса ассоциируется с полным выполнени- ем операции. Он задается как пара целых чисел \л/М1п и доМах. Текущая позиция (сиггепё розйюп) отображает смещение от начала интервала и соответствует сте- пени завершения операции. Оконная процедура индикатора процесса использует интервал и текущую позицию, чтобы определить процент заполнения окна инди- катора прямоугольниками. Для управления индикатором процесса используются сообщения, приведен- ные в табл. 8.12 (полный список сообщений см. в М8В?<). Задавая значения \л/М1’п и доМах при определении интервала, выбирайте их из диапазона от 0 до 65 535. Если вы не определите интервал индикатора с помощью сообщения РВМ_$ЕТКАМ6Е, то система использует установки по умолчанию, когда \л/М1 п = 0, амМах= 100. 1 Цвет, используемый АУтскиуз для подсвечивания выделенных элементов в дочерних окнах элемен- тов управления.
Таблица 8.12. Сообщения для управления элементом Ргодгезз Ьаг Код сообщения шРагат 1Рагат Описание РВМ_8ЕТкАЫСЕ 0 МАКЕЬРАкАМ (\л/М1п, \л/Мах) Установка интервала для индикатора РВМ_8ЕТРО8 пЫе\л/Роз 0 Установка текущей позиции РВМ_ОЕ1_ТАРО5 п1пс 0 Изменение текущей позиции прибавлением смещения п!пс РВМ_8ЕТ8ТЕР п81ер1пс 0 Установка шага приращения для индикатора РВМ_8ТЕР1Т 0 0 Изменение текущей позиции прибавлением шага п81ер1пс РВМ_5ЕТВАкСО1_Ок 0 (СОЮккЕЕ) с1гВаг Установка цвета заполняемых прямоугольников РВМ_5ЕТВКСО1_Ок 0 (СОЮккЕЕ) с1гВк Установка цвета фона Как видно из таблицы, текущее состояние индикатора можно изменять тремя аль- тернативными способами. Для этого может использоваться сообщение РВМ_$ЕТРО5, РВМД)Е1_ТАРО5 или РВМ_5ТЕР1Т. В третьем способе текущая позиция изменяется при- бавлением шага приращения п51ер!пс, который можно предварительно установить, отправив сообщение РВМ_$ЕТ5ТЕР. Если вы не устанавливаете это значение, система использует значение по умолчанию пЗЕерТпс =10. Отметим, что если при обработке сообщения РВМ_$ТЕР1Т индикатор достигнет значения доМах или превысит его, то его текущая позиция сбрасывается в значе- ние \л/М1п и индикатор процесса стартует сначала. Два последних сообщения из табл. 8.12 позволяют изменить цветовые атрибу- ты индикатора процесса. Для демонстрации применения индикатора процесса разработаем приложе- ние РгодгеззВаг, в котором решается задача загрузки некоторого файла. Приложе- ние должно иметь строку состояния с двумя полями. В первом поле выводится текстовое сообщение о проценте выполненной операции. Во втором поле будет располагаться окно индикатора процесса. Предположим, что размер загружаемого файла равен 81/е байтов и файл чита- ется порциями по 0АТА_СН11МК байтов. Чтобы упростить связь процесса загрузки файла с отображением степени завершения операции в индикаторе, будем счи- тать, что \л/М1’п = 0, доМах = 8ые / 0АТА_СН11МК, п51ер1пс = 1, а текущая позиция будет изменяться отправкой сообщения РВМ_$ТЕР1Т. Выбирая значение 0АТА_СН11МК, вы должны понимать, что величина доМах не может превышать значение 65 535. Создайте новый проект с именем Ргодге$$Ваг. Добавьте к настройкам проекта на вкладке Ипк библиотеку сотс1132.ИЬ. Скопируйте из папки проекта ТооГПр фай- лы КЗЛ/пдЕх.Ь и К\Л/пс1 Ех.срр, после чего добавьте их в состав проекта. Также добавьте к приложению ресурс меню ЮК_МЕМ1П с одним пунктом. Пункт меню должен иметь имя 1_оас1Е1’1е и идентификатор ЮМ_1_0А0_Е11_Е. Добавьте к проекту файл Ргодге$$Ваг.срр с текстом, приведенным в листинге 8.6. Листинг 8.6. Проект РгодгеззВаг ////////////////////////////////////////////////////////////////////// // РгодгеззВаг.срр #1пс1ис1е <\л/1 пс1о\л/8. Н> #1пс1ис1е <сотгтк±г1. Н>
#1пс1ис1е #1пс1ис1е <Тзбгеат> из1пд патезрасе зТб; #1пс1ис1е "К1л1пс1Ех.И" #1пс1ис1е "гезоигсе.б" #с1еС1пе Ю_8ТАТи8ВАК 201 #с1еС1пе М_РАВТ8 2 #с1еС1пе 0АТА_СН1Ш 64 #с!еТ1пе Е1ЕЕ_МАМЕ "РгодгеззВаг.срр" НШО ИмпсВТаШВаг; НШВ ЫпбРгодгеззВаг; ЕРЕ8ВЕТ САЕЕВАСК Шс1Ргос(НШВ, ЮТ, ПРАВАМ, ЕРАВАМ); //============================================ 1пР ШИАР1 ШпМа1п(Н1М8ТАМСЕ Шзбапсе, Н1М8ТАМ1СЕ КРгеуТпзбапсе, ЕР8ТВ 1рСтс1Е1пе, Ш пСтсВИом) { М86 тзд; К1л/пс1Ех та1 п1л/пс1 ("РгодгеззВаг", МпзТапсе, пСтсВИом, МпбРгос, МАКЕ1МТВЕ80иВСЕ(1ВВ_МЕМи1), 100, 100, 400, 200); \л/М 1 е (СебМеззадеС&тзд, ШЕЕ, 0, 0)) { ТгапзШеМеззадеС&тзд); ОтзраТсИМеззадеС&тзд); } геТигп тзд.мРагат; } //=============================== ЕВЕ8ЕЕТ САЕЕВАСК ШбРгос(НШВ Шпс1, Е1МТ иМзд, ПРАВАМ мРагат, ЕРАВАМ 1Рагат) { НОС ВВС; РА1МТ8ТВЕСТ рз; сбаг РехР[100]; ВЕСТ гесб, г1; Ш аШс1Ш[М_РАВТ8]; 1ТзТгеат Т11е; 8ТаТ1с Ш зые; // размер файла в байтах 8ТаТ1с Ш гапде; // диапазон для индикатора процесса 8баб1с 1пТ пВуТезВеас!; // число прочитанных байтов боиЫе регсепбаде; сбаг ЬиТЕВАТА_СНиМК]; 8ТаТ1с 1пТ 1; М86 теззаде; змПсб (иМзд) { сазе Ш_СВЕАТЕ: // Создание строки состояния ИмпсВТаТызВаг = СгеаТе8ТаТиз1лМпс1ом(1л18_СН1 ЕО | 1л18_\/181ВЕЕ, И1л1пб, I0-8ТАТС8ВАВ); // Увеличиваем высоту строки состояния продолжение
Листинг 8.6 {продолжение} ЗепбМеззадеСЬшбЗЬаЬизВаг, ЗВ_ЗЕТМIИНЕ16НТ, 24, 0); а1лИс|-Ып8 [0] = 50; аШШ [1] = -1; ЗепсМеззадеСЫпсЕбабизВаг, ЗВ_ЗЕТРАЕТЗ, М_РАРТЗ, (ЕРАРАМ)а1л/1 сПаГ|8); // Извлекаем координаты 1-го поля ЗепсМеззаде(ЫпсЕЬаЬызВаг, ЗВ_ЗЕТРЕСТ, 1, (ЕРАРАМ)&г 1); // Создание индикатора процесса ЫпбРгодгеззВаг = СгеаЬеШпс1ом(РРОЗРЕЗЗ_СЕАЗЗ, МЫЕЕ, 1л/3_СН1ЕВ | 1л/3_У131ВЕЕ, г1.1еГЬ + 3, г1.Ьор + 3, г1. гпдЕтЕ - г1.1еГЬ, г1.ЬоЬЬот - г1.Ьор, ЫпсЕШызВаг, ШЕЕ, ШЕЕ, ШЕЕ): Ьгеак; сазе Ш_812Е: ЗепсМеззаде (ЫпсЕЬаЬизВаг, ИМ_312Е, мРагат, 1 Рагат); Ьгеак; сазе ИМ_С0ММАМ0: з\л/1ЬсИ (ЕОРЮРВ(\л/Рагат)) { сазе 1ВМ-Е0АВ-Е1ЕЕ: // Открывем файл Г1 "Iе.ореп(Е1 ЕЕ_МАМЕ, 1 оз: : 1 п | 1оз::Ыпагу); 1Г (!Г11е) { МеззадеВох(Шпс1, "Нет файла", МЬЕЕ, МВ_ОК | МВ_1С0МЗТ0Р); Ьгеак; } // Определяем размер файла ГП е.зеекд(0, поз: :епс1); з1хе = Г11е.Ье11д(); Г11е.зеекд(0, 1оз::Ьед); // Настраиваем параметры индикатора процесса гапде = зпге / ВАТА_СНЫМК; 5епс1Ме88аде(Ыпс1Ргодге88Ваг, РВМ_ЗЕТРАМЗЕ, О, МАКЕЕРАРАМ(О, гапде)); ЗепбМеззаде(ЫпОРгодгеззВаг, РВМ_ЗЕТЗТЕР, 1, 0); ЗепбМеззаде(ЫпсРгодгеззВаг, РВМ_ЗЕТРОЗ, 0, 0); 1пуа11 (ШеРесШИпс!, МНЕЕ, ЕАЕЗЕ); пВубезРеаб = 0; // Загрузка файла \л/Ы1е (! Тп 1 е. еоТ()) { // Чтение порции данных Г11е. геасКЬиГ, ВАТА_СН1Ж); пВубезРеаб += ВАТА_СННМК; 1Г (ГПе.еоГО) Ьгеак; // Здесь возможен вызов процедуры для обработки порции данных // ... // Отображение процента выполненной операции регсепбаде = 100.0 * пВубезРеас! / зпге;
зргл пШТехТ, " ГОСИ", регсепТаде); ЗепбМеззаде(ЫпсЕТаТизВаг, 5В_5ЕТТЕХТ, О, (ЕРАВАМ)ТехТ); Зепс1Ме8заде(Н\л/пс1РгодгеззВаг, РВМ_5ТЕР1Т, О, О); 31еер(100); // Предотвращение зависания приложения ШРеекМеззадеС&теззаде, Шпс1, О, О, РМ_ЕЕМОУЕ)) { Тгапз1аТеМе88аде(&те88аде); В1зраТсЬМе88аде(&те88аде); Зепс1Меззаде(И\л/пс13ТаТизВаг, ЗВ_ЗЕТТЕХТ, О, (ЕРАВАМ)" 100 Г); МеззадеВох(Шпс1, "Файл загружен.", "Завершение операции", МВ-ОК I МВ-1С0М1МЕ0ЕМАТ10Ю; // Сброс индикатора процесса в исходное положение ЗепсГеззадеТшсГгодгеззВаг, РВМ_ЗЕТРОЗ, 0, 0); Зепс1Меззаде(Н\л/пс13ТаТизВаг, ЗВ_ЗЕТТЕХТ, 0, (ПРАВАМ)" Веабу"); Ьгеак; сазе ЮМ_ЕХ1Т: 8епс1Ме88аде(Шпс1, И1М_ВЕ8ТВ0У, 0, 0); Ьгеак; с1еСаи!Т: Ьгеак; } Ьгеак; сазе 1л/М_РА1МТ: ПОС = Ведп пРап пТ(Ыл1пс1, &рз); 6еТС11 епТВесТ( Ыл1пс1, &гесТ); зрг1пТТ(ТехТ, "81 хе = Жс1, ВАТА_СНЫМК = Жс1, гапде = И", 81 хе, ВАТА—СНЫМК, гапде); ВгаГГехШВС, ТехТ, -1, &гесТ, ВТ_31МЗЕЕЕIМЕ | ВТ_СЕМТЕВ | ВТ_7СЕМТЕВ); ЕпЫРап пТ (Шпс1, &рз); Ьгеак; сазе 1л/М_ВЕЗТВ0У: РозТ0и1ТМе8заде(0); Ьгеак; с1еСаи!Т: геТигп ВеСШпсЫРгос(Шпс1, иМзд, мРагат, 1Рагат); геТигп 0; } ////////////////////////////////////////////////////////////////////// Имя загружаемого файла в этом примере задано следующей директивой: #с1еС1пе Е1ЕЕ_МАМЕ "РгодгеззВаг.срр" В приложении используется строка состояния с увеличенной высотой, чтобы лучше смотрелся встроенный индикатор процесса.
Прежде чем поместить индикатор процесса во второе поле строки состояния, мы узнаем координаты этого поля, посылая соответствующее сообщение: ЗепбМеззадеСЫпбЗШизВаг, 8В_0ЕТЕЕСТ, 1, (ЕРАВАМ)&г1); Загрузка файла осуществляется в цикле доННе. На каждой итерации цикла чи- тается очередная порция данных, после чего окну индикатора процесса посыла- ется сообщение РВМ_$ТЕР1Т. В теле цикла имеется инструкция 51еер(1ОО), которая приостанавливает вы- полнение программы на 100 мс. Преднамеренное замедление работы програм- мы преследует демонстрационные цели. Дело в том, что загружаемый файл РгодгеззВаг.срр имеет небольшие размеры, и поэтому без этой задержки индика- тор процесса будет заполнен практически мгновенно. Также следует обратить внимание на фрагмент кода ШРеекМеззадеС&теззаде, Шпс1, О, О, РМ_КЕМОУЕ)) { ТгапзШеМеззадеС&теззаде): Втзра^сВМеззадеС&теззаде); } Он предотвращает «зависание» приложения на время, в течение которого про- исходит загрузка файла. Об этом инструментальном приеме мы рассказывали в главе 1. На рис. 8.11 показан вид работающего приложения РгодгеззВаг после выбора команды меню ГоаПЕПе. Рис. 8.11. Загрузка файла в приложении РгодгеззВаг Посмотрите, как изменится работа индикатора, если при его создании в функ- ции Сгеа1е\ЛЛпс1о\л/ задать дополнительный стиль РВ5_5М00ТН. Регулятор Регулятор, или элемент управления 5Н Пег, который ранее назывался ТгаскЬаг, пред- ставляет собой окно с линейкой и перемещаемым по ней ползунком. Внешне этот элемент управления напоминает регуляторы тембра, используемые в бытовой радиоаппаратуре. Подобный регулятор дает возможность пользователю выбирать дискретные значения в заданном диапазоне. Создание регулятора Элемент управления 5Пс1ег может быть создан с помощью функции Сгеа1е\Л/1’пс1о\л/ или Сгеа1е\Л/1’пс1о\л/Ех с указанием идентификатора ТКАСКВАК_С1_А$$ в качестве окон- ного класса, например:
Ыпс1811с1ег = Сгеа^еШпбом(ТКАСКВАК_СЕА88, Ш, Ш_СННО | 1л18_7181ВЬЕ, х, у, мсЖ Пе1д1тЕ, Шпс1РагегД, ШЕЕ, МОЕЕ, ШЕЕ); Кроме стилей М$_СН11_0 и М$_\/1$1В1_Е можно также задавать дополнительные стили, определяющие внешний вид элемента управления (табл. 8.13). Таблица 8.13. Стили элемента управления 8Нс1ег Стиль Описание ТВ5_НОК2 Линейка имеет горизонтальную ориентацию (стиль по умолчанию) ТВ8_УЕНТ Линейка имеет вертикальную ориентацию ТВ8_А11ТОТ1СК8 Линейка имеет метки для всех значений в заданном диапазоне значений. Без этого стиля линейка может иметь метки только для начального и конечного положений ползунка ТВ5_1\ЮТ1СК5 Исключает отображение каких-либо меток, в том числе и для начального и конечного положений ползунка ТВ8_Н1СНТ Элемент отображает метки справа (снизу) от линейки; ползунок направлен в ту же сторону. Этот стиль используется по умолчанию ТВ5_1ЕЕТ Элемент отображает метки слева (сверху) от линейки; ползунок направлен в ту же сторону ТВ8_ВОТН Элемент отображает метки с обеих сторон; ползунок имеет прямоугольную форму ТВ5_ТОО1Т1Р5 Поддерживается всплывающая подсказка, отображающая текущую позицию ползунка Альтернативный способ создания регулятора — с помощью редактора диа- логовых окон на этапе визуального проектирования формы диалога. В этом случае вы выбираете щелчком мыши элемент 811 дег на панели инструментов Соп1го1з и повторным щелчком помещаете регулятор в нужное место формы диалога. Размеры элемента управления при размещении на форме диалога, как обыч- но, регулируются с помощью мыши или при помощи команд подменю кауои!. Затем нужно вызвать окно свойств 811с1ег РгореФ’ез и на вкладке 6епега1 в поле Ю ввести идентификатор элемента управления. На вкладке 51у1е$, показанной на рис. 8.12, можно задать дополнительные свойства регулятора. Рис. 8.12. Свойства элемента ЗНс1ег на вкладке 51у1ез Открывающийся список ОпегйаВоп позволяет выбрать ориентацию элемен- та управления: Ноп2оп1:а1 (по умолчанию) или УегВсаЬ Другой список, Ро1 п1, пред- назначен для выбора стиля размещения меток: Во1Н (по умолчанию), Тор/ЬеЛ: или Войот/К1дНЕ Флажок Иск тагкз задает наличие меток, флажок Аи1о 11’скз за- дает стиль ТВ5_А11ТОТ1СК5, флажок ВоМег определяет наличие рамки у элемента управления.
Создание регулятора с помощью редактора диалоговых окон является более простым способом. Именно эту технологию мы используем в рассматриваемом ниже примере разработки приложения ТгаскЬаг. Параметры и состояние регулятора С элементом управления 5Нс1ег связан внутренний счетчик, определяющий его поведение. Счетчик имеет минимальное значение \л/М1п и максимальное значение доМах. По умолчанию \л/М1’п = 0, а доМах = 100. Вы можете изменить диапазон регу- лятора, послав ему сообщение ТВМ_$ЕТКАМ6Е. Текущее состояние счетчика однозначно связано с текущей позицией ползун- ка. Пользователь может перемещать ползунок по линейке регулятора как с помо- щью мыши, так и с помощью клавиатуры. Второй вариант работы предполагает, что регулятор имеет фокус ввода. Минимальный интервал, на который можно изменить состояние регулятора с помощью клавиш со стрелками, называется «строкой» (/те). По умолчанию он равен единице. Размер «строки» можно изменить, послав регулятору сообщение ТВМ_5ЕТ1_1МЕ512Е. Более крупный интервал, на который можно изменить состояние регулятора с помощью клавиши Раде IIр или Раде Оодоп, называется «страницей» (ра^е). По умол- чанию размер «страницы» равен одной пятой части диапазона регулятора. Размер «страницы» можно изменить, послав регулятору сообщение ТВМ_5ЕТРА6Е512Е. Если регулятор создан со стилем Аи1о Бскз (ТВ5_А11Т0Т1СК5), то линейка имеет метки во всем диапазоне значений с шагом \л/Егер, который по умолчанию равен единице. Вы можете изменить этот шаг, послав сообщение ТВМ_5ЕТТ1СЕКЕ0. Действия пользователя и уведомляющие сообщения Регулятор уведомляет свое родительское окно о действиях пользователя, посылая сообщение ММ_Н$СК01_1_ или ММ_\/$СК01_1_ — в зависимости от ориентации элемента уп- равления (Нопноп^а!. или Х/егИсаЕ). В любом случае в младшем слове параметра доРагат содержится код уведомления, а параметр 1Рагат содержит дескриптор регулятора. Возможные коды уведомления приведены в табл. 8.14. Для кодов ТВ_ТН11МВР05Ш01\1 и ТВ_ТН11МВТКАСК старшее слово параметра доРагат содержит позицию ползунка. Таблица 8.14. Коды уведомления от элемента управления 8Нс1ег Код уведомления Значение Описание ТВ-ИЫЕ11Р 0 Нажата клавиша «стрелка влево» (\/К_1_ЕРТ) или клавиша «стрелка вверх» (\/К_11Р) — ползунок сдвигается на вели- чину «строки» ТВ_1_1ЫЕОСМ/Ы 1 Нажата клавиша «стрелка вправо» (УК_К1СНТ) или кла- виша «стрелка вниз» (УК_00\Л/1\1) — ползунок сдвигается на величину «строки» ТВ_РАСЕОР 2 Нажата клавиша «Раде 11р» (УК_РкЮК) или щелчок мышью на линейке левее или выше ползунка — ползунок сдвигается на величину «страницы» (левее — для гори- зонтальной ориентации регулятора, выше — для верти- кальной ориентации) ТВ_ РА6Е0СМ/Ы 3 Нажата клавиша «Раде Оодоп» (УК_ЫЕХТ) или щелчок мышью на линейке правее или ниже ползунка — ползунок сдвигается на величину «страницы»
Код уведомления Значение Описание тв_тнимвро5шоы 4 Пользователь отпустил кнопку мыши после перемещения ползунка (это сообщение следует всегда после сообщения ТВ_ТН11МВТКАСК) ТВ_ТН11МВТКАСК 5 Ползунок перемещается с помощью мыши ТВ_ТОР 6 Нажата клавиша «Ноте» (\/К_НОМЕ) — ползунок устанав- ливается в крайнее левое (верхнее) положение, соответ- ствующее значению \л/М1п ТВ-ВОТТОМ 7 Нажата клавиша «Епс!» (УК_ЕЫО) — ползунок устанавли- вается в крайнее правое (нижнее) положение, соответ- ствующее значению \л/Мах Часто приложение может обойтись без обработки этих сообщений, так как с помощью управляющего сообщения ТВМ_6ЕТРО5 (см. ниже) можно легко узнать текущую позицию ползунка, и в большинстве случаев этого оказывается доста- точно. Управляющие сообщения Для управления регулятором предусмотрено несколько десятков сообщений, сим- волические коды которых начинаются с префикса ТВМ_. В табл. 8.15 приведены некоторые из этих сообщений. Таблица 8.15. Сообщения для управления элементом 5Кс1ег Сообщение шРагат 1Рагат Описание ТВМ-СЕТРО8 0 0 Возвращает текущую позицию ползунка ТВМ-8ЕТРО8 ГИедга\л/ пе\л/Роз Устанавливает новую позицию ползунка. Если ГИедга\л/ равно ТИ11Е, регулятор перерисовывается после этого ТВМ_8ЕТИАНСЕ ГИедга\л/ МАКЕ1РАКАМ (\л/М1п, \л/Мах) Устанавливает диапазон регулятора ТВМ_8ЕТТ1СЕИЕ(2 \л/Егец 0 Устанавливает шаг меток ТВМ_8ЕТ1_1ЫЕ812Е 0 п1_1пе812е Устанавливает размер «строки» ТВМ-8ЕТРАСЕ812Е 0 пРаде812е Устанавливает размер «страницы» Приложение ТгаскВаг Для демонстрации применения регуляторов разработаем приложение ТгаскВаг, которое является клоном приложения Мос1е1е$$01д, рассмотренного в главе 7. При- ложение Мос1е1е$$01д позволяло изменять цвет фона окна с помощью полос про- крутки. В нашем новом приложении вместо полос прокрутки будут применены элементы управления 5 Иде г. Создайте новый проект с именем ТгаскВаг. Скопируйте из папки проекта Мос1е1е$$01д (см. листинг 7.6) в папку проекта ТгаскВаг файлы Мос1е1е$$01д.срр, Мос1е1е$$01д.гс и гезоигсе.Н, скорректировав имена первых двух файлов заменой подстроки Моде1е$$01д на ТгаскВаг. Также скопируйте из папки проекта Тоо 1Т1 р (ли- стинг 8.4) файлы К\Л/пдЕх.срр и К\Л/пдЕх.Н. Все скопированные файлы нужно доба- вить в состав проекта. Также к настройкам проекта на вкладке Ипк следует доба- вить библиотеку сотс1132.11’Ь. Откройте вкладку Резо и гееVI е\л/ в окне Могкзрасе. В списке ресурсов откройте папку 01 а 1од и вызовите редактор диалоговых окон двойным щелчком мыши на эле-
менте Ю0_М00Е1_Е$5. Удалите все элементы управления с формы диалога. Размес- тите три надписи и три элемента управления 5Пс1ег, как показано на рис. 8.13. Рис. 8.13. Форма диалога после размещения элементов управления Для элементов управления установите следующие атрибуты: Элемент Ю Сарйоп Свойства 51аИс ЕехЕ ЮС_8ТАТ1С_кЕО Нес! По умолчанию 51аИс ЕехЕ ЮС_8ТАТ1С_СкЕЕЫ Сгееп По умолчанию 51аИс ЕехЕ ЮС_5ТАТ1С_ВШЕ В1ие По умолчанию 8Пс1ег ЮС_81_ЮЕк_кЕО — Стиль меток — Войот/к1д1тЕ Установлены флажки Тюк тагкз, АиЕо йскз, Вогс1ег 8Пс1ег ЮС_81_ЮЕк_6кЕЕЫ — 8Нс1ег ЮС_81_ЮЕк_ВШЕ — Остальные свойства — по умолчанию Отредактируйте исходный код в файле ТгаскВаг.срр, чтобы он соответствовал листингу 8.7. Листинг 8.7. Проект ТгаскВаг ////////////////////////////////////////////////////////////////////// // ТгаскВаг.срр #1пс1ыс1е <\л/1 пс!о\л/8. Н> #1пс1ыс1е <8ТсП о. Н> #1пс1ыс1е <соттсДг1.к> #1пс1ис1е "КИпс1Ех.Н" #1пс1ыс1е "гесоигсе.Н" епит ОсегМсд { 0М_СНАМ6Е = ИМ_08ЕВ+1 }; НИМО НМос1е1е8801д; ВЕСТ гсИогк; // прямоугольник рабочей области Ш гдЬЕЗ]: // интенсивность для В-, 6-, В-компонентов цвета ВООЕ САЕЕВАСК Мобе!е8801 дРгос(НИИО, 01ИТ, ПРАВАМ, ЕРАВАМ); уоЮ Абци8бО1дР1 асе(НИИ0, НИМО); ЕВЕЗОЕТ САЕЕВАСК ИпбРгос(НИМО, 01ИТ, НРАВАМ, ЕРАВАМ); //====================================================================
Ш ШИАР1 ШпМапп(НIМ8ТАИСЕ Шнапсе, Н1М5ТАМСЕ бРгеуШТапсе, ЕРЗТР 1рСтс1Е1пе, Ш пСтс15Р|0\л/) { М80 тэд; КИпбЕх та1 пМпсЦ"ТгаскВаг", бШТапсе, пСтс186ом, ИпОРгос, ШЕЕ, 100, 100, 400, 270); \л/М 1 е (ОеТМеээадеС&тэд, ИЕ1ЕЕ, 0, 0)) { 1Т (!1801 а!одМеззадеС6Мобе1еззО! д, &тэд)) { ТгапзШеМеззадеС&тзд); О18раТс6Ме88аде(&т8д); } } геТигп тэд.мРагат; } //========================================== ЕРЕЗЕЕТ САЕЕВАСК ИпОРгос(НЕМО Шпс1, Н1МТ иМзд, НРАВАМ мРагат, ЕРАВАМ 1Рагат) { /* Здесь такой же текст, как в листинге 7.6 */ } //========================================== ВООЕ САЕЕВАСК МобеО68801дРгос(НЕМО 601д, О1МТ иМзд, НРАВАМ мРагат, ЕРАВАМ 1Рагат) { 8ТаТ1с НЕМО 6811бегВеб; зШ1с НЕМО 681 ЮегОгееп; зШ1с НЕМО 6811бегВ!ие; зШс НЕМО 68Ш1 сВеО; 8ТаТ1с НЕМО 68ТаТ1сОгееп; 8ТаТ1с НЕМО 68ТаТ1сВ1ие; сбаг ТехТ[100]; этТсб (иМзд) { сазе ИМ_1М1Т01АЕОЗ: // Определяем дескрипторы элементов управления 311с1ег 6811бегВеб = ОеШд 10ет(601 д, ЮС_8ЕЮЕВ_ВЕ0); 681 ЮегОгееп = ОеШ д 10ет(601 д, ЮС_8ЕЮЕВ_6ВЕЕЮ ; 681пс1егВ1ие = 6е001 дЮет(601 д, ЮС_8ЕЮЕВ_ВЕ0Е); 68ОаО1сВес1 = ОеОО!дЮет(601 д, 10С_8ТАТ1 С_ВЕС); 68Ш1с6гееп = ОеШ д 10ет(601 д, ЮС_8ТАТ1С_6ВЕЕЮ ; 68Ш1сВ1ие = 6еТ01 дЮет(601 д, ЮС_8ТАТЮ_ВЕ0Е); // Инициализация элементов управления 81Юег ЗепбМеззаде(6811бегВеб, ТВМ_8ЕТВАИ0Е, ТВОЕ, МАКЕЕОИОСО, 255)); ЗепбМеззадеС681 ЮегВеб, ТВМ_8ЕТТ1 СЕВЕО, 32, 0); ЗепбМеззадеС6811бегЗгееп, ТВМ_8ЕТВАИ0Е, ТВОЕ, МАКЕЕОИОСО, 255)); ЗепбМеззаде(6811ОегОгееп, ТВМ_8ЕТТ1СЕВЕО, 32, 0); 8епбМеззаде(681пбегВ!ие, ТВМ_8ЕТВАИ0Е, ТВОЕ, МАКЕЕОИОСО, 255)); ЗепбМеззадеСбЗ!пбегВ!ие, ТВМ_8ЕТТ1СЕВЕО, 32, 0); // Вывод фона окна для начального состояния регуляторов 8епбМеззаде(601д, ИМ_Н8СВ0ЕЕ, 0, 0); продолжение &
Листинг 8.7 {продолжение} гебигп ТРЬЕ; сазе 1л/М_Н8СК0ЕЕ: // Получаем текущие позиции элементов управления 811с1ег гдЬ[0] = ЗепбМеззаде(6811 ЬегРеб, ТВМ_6ЕТР08, 0, 0); зргШШехЬ, "Реб = Ж, гдЬ[0]); 8еШпс1омТехШ8ЬаЬ1сРес1, Ьехб); гдЬ[1] = 8епс1Ме88аде(Ь811с1ег6гееп, ТВМ_6ЕТР08, 0, 0); зрг1 пШЬехб, "Сгееп = %сГ, гдЬ[1]); 8еШ пбоУл/Тех-Ь(И8ЬаЫ сбгееп, Ьехб); гдЬ[2] = ЗепбМеззадеС Н811 с1егВ1 ые, ТВМ_6ЕТР08, 0, 0); зрг1 пЬШехЬ, "В1ие = %сГ, гдЬ[2]); 8еШг1с1омТехЬ(Ь8ЬаЬ1сВ1ие, Ьехб); // Сообщение родительскому окну об изменении цвета фона 5епс1Ме88аде(6ебРагепШВ1д), ЫМ_СНАМЗЕ, 0, 0); Ьгеак; } гебигп ГАЕЗЕ; } /======================= уо1с1 АбцЦ86В1 дР1 асе(НШВ ЬРагепб, ШИВ ЬВ1д) { /* Здесь такой же текст, как в листинге 7.6 */ } ////////////////////////////////////////////////////////////////////// В блоке обработки сообщения ММ_1М1Т01А1_06 всем трем регуляторам для уста- новки диапазона и шага меток посылаются управляющие сообщения ТВМ_$ЕТКАМ6Е и ТВМ_5ЕТТ1СЕКЕО. В блоке обработки сообщения ММ_Н$СКО1_1_ мы узнаем текущую позицию для всех трех ползунков, отправляя трижды сообщение ТВМ_6ЕТРО5. Полученные зна- чения гдЬ[О], гдЬ[1] и гдЬ[2] используются для вывода текстовой информации в элементы управления НЗ^ай’сКеф Н5±а1г1 сбгееп и НЗ^айсВ 1ие, а также для формиро- вания цвета фона окна. Цвет фона окна устанавливается оконной процедурой \Л/пс1 Ргос, когда она получает сообщение 11М_СНАМ6Е. Именно поэтому далее в коде программы следует отправка сообщения: 5епс1Ме88аде(6еЬРагепШ01д), ЬМ_СНАМ6Е, 0, 0); Рис. 8.14. Окно приложения ТгаскВаг
Код обработки сообщения 11М_СНАМ6Е, находящийся в теле оконной процеду- ры \Л/пс1Ргос, здесь не приведен, поскольку все тело процедуры повторяет текст, заимствованный из листинга 7.6. На рис. 8.14 показан вид работающего приложения. В настоящий момент ползунки регуляторов задают кремовый цвет фона окна. Счетчик и поле с прокруткой Элемент управления счетчик (8р1п) реализован как две кнопки со стрелками, с помощью которых пользователь может увеличивать или уменьшать некоторое числовое значение. Значение, связанное со счетчиком, называется его текущей позицией. Кроме этого счетчик можно ассоциировать с другим элементом управления, называемым приятельским окном (ЬъиМу ю'тйою}. Чаще всего таким окном явля- ется окно редактирования. Комбинацию счетчика с окном редактирования назы- вают также полем с прокруткой. Поле с прокруткой воспринимается пользовате- лем как единый элемент управления. Содержимое окна редактирования в таком элементе отображает текущую позицию счетчика (рис. 8.15). Рис. 8.15. Поле с прокруткой Не имея ассоциированного с ним окна, счетчик функционирует как упро- щенный вариант полосы прокрутки. Например, в элементе управления ТаЬ соп!го1 счетчик используется для осуществления доступа к дополнительным вкладкам (рис. 8.16). 8ип(1ау | Мопйау ] Тиезйау ] \Уе(1п 4 | ► Рис. 8.16. Элемент управления 5р1п в правом верхнем углу элемента управления ТаЬ соп1го1 Создание счетчика Счетчик можно создать несколькими способами. В первом способе используется вызов функции Сгеа1е\Л/1’пс1о№Ех с указанием оконного класса 11РООММ_С1_А$$. Во вто- ром способе вызывается функция Сгеа1е11рОо\л/пСоп1:го1, создающая счетчик и одновременно определяющая его минимальную, максимальную и текущую по- зиции, а также его приятельское окно. Третий способ создания счетчика — при помощи редактора диалоговых окон на этапе визуального проектирования формы диалога. Элемент управления 8р1п выбирается с помощью мыши на панели инструментов Соп1го1$ и просто помеща- ется в нужное место формы диалога. Затем надо вызвать окно свойств 8р1п РгорегВез и на вкладке 6епега1 в поле Ю указать идентификатор элемента управления. На вкладке 51у1е$, показанной на рис. 8.17, можно задать дополнительные свойства счетчика.
Рис. 8.17. Свойства элемента Зр1п на вкладке 81у1ез Список Опеп^айоп предназначен для выбора ориентации элемента управления. По умолчанию используется значение Уег1:1’са1, но можно также установить значе- ние Ноп’2оп1:а1. Список АПдшпеп!: позволяет выбрать один из трех стилей размещения счет- чика: АНдптеп1 Описание размещения 11пайасНес1 Счетчик располагается рядом с ассоциированным окном (так, как вы поместили его на форму диалога) ЬеЙ: Счетчик располагается в левой части ассоциированного окна (уменьшая тем самым его клиентскую область) к1дИ1: Счетчик располагается в правой части ассоциированного окна (уменьшая тем самым его клиентскую область) Флажки на вкладке 81у 1е$ позволяют задать дополнительные параметры: Флажок Интерпретация АЫо Ьибс1у Автоматический выбор в качестве ассоциированного окна ближайшего предыдущего элемента управления в файле описания ресурсов 8е1 Ьибс1у 1п1:едег Вместе с предыдущим флажком определяет синхронную работу счетчика Ыо Щоизапбз и ассоциированного окна: любое изменение позиции счетчика сразу отображается в ассоциированном окне. Аналогично при вводе в ассоциированное окно допустимого целого числа оно устанавливает новую позицию счетчика Если этот флажок выключен, в изображение десятичного числа после каждых трех цифр вставляется пробел, если включен — пробел не вставляется \Л/гар По умолчанию текущая позиция счетчика не изменяется, если пользователь пытается перейти максимальное (при увеличении) или минимальное (при уменьшении) значение. Если установлен данный флажок, то счетчик рабо- тает как циклический. После максимального значения текущим становится минимальное, и наоборот Аггоуу кеуз Поддерживается управление счетчиком с помощью клавиш «стрелка вверх» и «стрелка вниз» (независимо от ориентации элемента управления) Таким образом, при создании поля с прокруткой необходимо всегда выпол- нять два правила. Во-первых, элемент управления 8р1п нужно поместить на форму диалога сразу вслед за размещением приятельского окна редактирова- ния. Во вторых, надо установить флажки А1Й0 Ьидду и 8е1 Ьидду 1 поедет. Отмет- ка флажка Аггом кеуз установлена по умолчанию, и эту установку следует со- хранить.
Следует отметить высокий «интеллект» поля с прокруткой. Если пользова- тель введет в окно редактирования числовое значение больше максимально допу- стимого или меньше минимально допустимого, то позиция счетчика будет зафик- сирована на соответствующей границе диапазона. Правда, пользователь узнает об этом только после очередного воздействия на стрелки счетчика. При попытке ввести в окно редактирования нецифровые символы счетчик со- храняет текущую позицию. Сообщения от поля с прокруткой При нажатии одной из стрелок элемент управления 5р1 п посылает своему роди- тельскому окну сообщение ММ_\/$СКО1_1_ или ММ_Н$СКО1_1_ (в зависимости от ори- ентации счетчика), в котором младшее слово параметра доРагат содержит код $В_ТН11МВР05Ш0М. Кроме того, счетчик посылает уведомляющее сообщение 110М_0Е1_ТАР05 в форме сообщения ММ_МОТ1ЕУ. Обычно в приложении нет необходимости обрабатывать все сообщения. Час- то бывает достаточно получить текущую позицию счетчика, обрабатывая сооб- щение ММ_\/$СКО1_1_ или ММ_Н$СКО1_1_. Это можно сделать, отправив элементу 5р1п управляющее сообщение 1ЮМ_6ЕТРО5. При непосредственном клавиатурном вводе нового числа в окно редактирова- ния элемент ЕсПЕ Вох посылает родительскому окну сообщение ММ_СОММАМО с ко- дом уведомления ЕМ_СНАМ6Е. Если вы хотите, чтобы приложение немедленно от- реагировало на изменившуюся текущую позицию счетчика (а он отслеживает все изменения в приятельском окне), то предусмотрите обработку этого сообщения. Пример такой обработки приведен ниже в приложении Зршпег. Управляющие сообщения Для управления счетчиком предусмотрено более двадцати сообщений, символи- ческие коды которых начинаются с префикса 110М_. В табл. 8.16 приведены неко- торые сообщения, чаще всего используемые в работе. Таблица 8.16. Сообщения для управления элементом 5р!п Сообщение шРагат 1Рагат Описание 1ЮМ.СЕТРО8 0 0 Возвращает текущую позицию счетчика 1ЮМ.8ЕТРО8 0 МАКЕ1РАРАМ (ПРОЗ, 0) Устанавливает новую позицию счетчика 1ЮМ.8ЕТВА8Е пВазе 0 Устанавливает систему счисления. пВазе должно быть равно 10 или 16. По умолчанию установлена десятичная система счисления 1ЮМ_8ЕТИА1\1СЕ 0 МАКЕ1РАИАМ (п11ррег, п1_см/ег) Устанавливает минимальную и макси- мальную позиции для счетчика (в ин- тервале -0х7ЕЕЕ...0х7ЕЕЕ) Если после создания счетчика не определить его диапазон при помощи сооб- щения 1ЮМ_$ЕТКАМ6Е, то будет использоваться диапазон по умолчанию со значе- ниями п1_омег=100 и п11ррег=О. В этом случае «стрелка вверх» вызывает уменьше- ние значения счетчика, а «стрелка вниз» — увеличение значения. Чем обусловлен такой оригинальный диапазон по умолчанию, в котором максимум меньше, чем минимум, в справочных материалах М8^^ не поясняется.
Сообщения, перечисленные в табл. 8.16, работают с 16-разрядной версией эле- мента управления 8р1п. Библиотека СотсИЗ2.с111, начиная с версии 4.71, поддерживает 32-разряд- ную версию счетчика, который управляется сообщениями, приведенными в табл. 8.17. Таблица 8.17. Сообщения для управления 32-разрядной версией элемента 5р!п Сообщение \л/Рагат 1Рагат Описание 1ЮМ_6ЕТРО832 0 (1_РВОО1_) рГЕггог Возвращает текущую позицию счетчика. рГЕггог — указатель на булеву переменную, которая получает значение ЕА1.8Е, если значение успешно получено, и ТН11Е — в случае ошибки 1ЮМ_5ЕТРО532 0 пРоз Устанавливает новую позицию счетчика 1ЮМ_5ЕТКА1\1СЕ32 ||_О\Л/ |Н|дИ Устанавливает минимальную и максимальную позиции для счетчика в интервале от - 0x71+1+1-1+ до 0x71+1+1+ Приложение 5р!ппег Рис. 8.18. Форма диалога после размещения элементов управления Для демонстрации применения поля с прокруткой разработаем приложение Зртппег, которое является модификацией приложения ТгаскВаг, рассмотренного выше. При- ложение ТгаскВаг позволяло изменять цвет фона окна с помощью регуляторов. В нашем новом приложении вместо каждого регулятора будет при- менено поле с прокруткой. Создайте новый проект с именем Зршпег. Скопируйте из пап- ки проекта ТгаскВаг (см. листинг 8.7) в папку проекта Зршпег фай- лы с расширениями .срр, .Н и .гс, скорректировав их имена заме- ной подстроки ТгаскВаг на Зршпег. Добавьте скопированные файлы в состав проекта. Также к настройкам проекта на вкладке И п к надо добавить библиотеку сотс1132.11’Ь. Затем откройте вкладку Резо и гееVI е\л/ в окне Могкзрасе. В списке ресурсов откройте папку О1’а1од и вызовите редактор диалоговых окон двойным щелчком мыши на элементе Ю0_М00Е1_Е55. Удалите элементы управления 511с1ег с формы диалога. Раз- местите вместо них три пары элементов (элемент ЕсИ! Вох и ря- дом элемент 8р1 п), как показано на рис. 8.18. В каждой паре ука- занных элементов счетчик помещается на форму диалога сразу после установки приятельского окна редактирования. Для расположенных элементов управления установите следующие атрибуты: Элемент Ю Свойства ЕбЛ Вох ЮС_ЕО1Т_кЕО АНдп ГехГ — СепГегеб. Остальные свойства — по умолчанию ЕсП1 Вох ЮС_ЕО1Т_СкЕЕЫ ЕсйГ Вох ЮС_ЕО1Т_ВШЕ 8р1п ЮС_8Р11\1_кЕО АПдптеп!: — Р1д 1+. Установлены флажки АиГо Ьибс1у, 8еГ Ьибс1у 8р1п ЮС_8Р1М_6кЕЕЫ 1п1едег. Остальные свойства — по умолчанию 8р1п ЮС_8Р1М_ВШЕ Приведите текст файла Зршпег.срр к виду, соответствующему листингу 8.8.
Листинг 8.8. Проект 5р!ппег ////////////////////////////////////////////////////////////////////// // 8р1ппег.срр #1пс1ис1е <\л/1 пОомз. 6> #1пс1ис1е <8бс11о.6> #1пс1ис1е <сотгпсРг! . 6> #1пс1ис1е "К0пс1Ех.6" #1пс1ис1е "гезоигсе.б" епит ОзегМзд { иМ_СНА1\1СЕ = ОМ_08ЕВ+1 }; НОМО 6Мос1е1е8801д; РЕСТ гсОогк; // прямоугольник рабочей области Ш гдЬЕЗ]; // интенсивность для В-, 6-, В-компонентов цвета ВООЕ САЕЕВАСК Мобе!е8801дРгос(НОМО, 011\1Т, ОРАВАМ, ЕРАВАМ); уоЮ АбцизТО!дР1 асе(НОИО, НОВО); ЕВЕ80ЕТ САЕЕВАСК ОпОРгос(НОВО, О1ВТ, ОРАВАМ, ЕРАВАМ); //============================================ Ш О1ВАР1 01пМапп(НIВ8ТАВСЕ НШТапсе, Н1М8ТАМ1СЕ НРгеуШТапсе, ЕР8ТВ 1рСтс1Е1пе, Ш пСтс18В|О\л/) { М80 тзд; КОпбЕх та1пОпб("8р1ппег", НТпзТапсе, пСтбЗбом, ОпбРгос, МОЕЕ, 100, 100, 400, 260); \л/М 1 е (ОеТМеззадеС&тзд, МОЕЕ, 0, 0)) { 1С (!1801а1одМе88аде(6Мобе1е8801д, &тзд)) { ТгапзШеМеззадеС&тзд); ОтзраТсбМеззадеС&тзд); } } геТигп тзд.мРагат; } //========================= ЕВЕ80ЕТ САЕЕВАСК ОпбРгос(НОВО НОпб, О1ВТ иМзд, ОРАВАМ мРагат, ЕРАВАМ 1Рагат) { /* Здесь такой же текст, как в листинге 7.6 */ } //========================= ВООЕ САЕЕВАСК Мобе!е8801дРгос(НОВО 601д, 01ВТ иМзд, ОРАВАМ мРагат, ЕРАВАМ 1Рагат) { 8баТ1с НОВО 68р1пВеО; 8баТ1с НОВО 68р1п0гееп; 8баТ1с НОВО 68р1пВ1ие; 8баТ1с НОВО 68ТаТ1сВеО; 8баТ1с НОВО 68ТаТ1с0гееп 8баТ1с НОВО 68ТаТ1сВ1ие; 8\л/Пс6 (иМзд) { сазе ОМ_1М1ТО1АЕО(3: // Определяем дескрипторы элементов управления 8р1п 68р1пВеб = 0еТ01д1Тет(601д, 10С_8Р1М_ВЕ0); 68р1п0гееп = ОеШдЮет(601 д, ЮС_8Р1М_0ВЕЕМ); 68р1пВ1ие = ОеШ д!Тет(601 д, 10С_8РIМ_ВЕОЕ);
Листинг 8.8 {продолжение} И$ЬаЫ сРеб = беШд1Ьет(601 д, ЮС_8ТАТ1С_РЕ0); ЬЗЬаЫсбгееп = беШ д 1Шп(601 д, ЮС_8ТАТ1С_бРЕЕЮ; НЗ-Ьа-ЫсВ! ие = беШ д!Шт1(601 д, ЮС_8ТАТ1С_ВЕЬЕ); // Инициализация элементов управления $р1п ЗепбМеззаде (1пЗрп пРес!, ШМ_5ЕТВАМ6Е, ТРЕ1Е, МАКЕЕРАРАМ(255, 0)); 8епс1Ме88аде(Ь8р1пбгееп, ШМ_8ЕТРАМ6Е, ТВОЕ, МАКЕЕРАВАМ(255, 0)); ЗепбМеззадеСИЗрппВ1ие, ШМ_8ЕТРАМ6Е, ТРЕ1Е, МАКЕЕРАВАМ(255, 0)); гебигп ТРЕ1Е; сазе Ш_78СР0ЕЬ: // Получаем текущие позиции элементов управления Зрлп гдЬЕОП = 5епбМеззаде(1э5р1пРеб, ЕЮМ_6ЕТР05, 0, 0); гдЬ[1] = Зепс1Ме88аде(НЗрлпбгееп, ЕЮМ_6ЕТР03, 0, 0); гдЬ[2] = ЗепбМе88аде( И$р1 пВ1 ие, ЕЮМ_6ЕТР03, 0, 0); // Сообщение родительскому окну об изменении цвета фона ЗепПМеззадеСбебРагепШЕПд), Е1М_СНАИбЕ, 0, 0); Ьгеак; сазе Ш_С0ММАШ: // Обновление фона основного окна после ввода // через приятельское окно редактирования 15 (НШОРВЫРагат) == Е1\1_СНАМ6Е) ЗепбМеззаде(ИВ1 д, ММ_\/ЗСР0ЕЕ, 0, 0); Ьгеак; } гебигп ЕАЬЗЕ; } //==================================================================== уо1с1 АбцизШдР1 асе(И ЬРагепЬ, Ш ЬО1д) { /* Здесь такой же текст, как в листинге 7.6 */ } ////////////////////////////////////////////////////////////////////// Использование поля с прокруткой, как видите, очень просто. Обратите внима- ние на обработку сообщения ММ_СОММАМО с кодом уведомления ЕМ_СНАМ6Е. Мы отправляем диалоговому окну сообщение ММ_\/$СКО1_1_, чтобы приложение немед- ленно отреагировало на изменившееся вследствие изменения текста в приятельс- ком окне состояние счетчика. На рис. 8.19 показано окно работающего приложения. Рис. 8.19. Приложение 8р!ппег. С помощью полей прокрутки задан голубой цвет фона окна
Многозадачность Эта глава содержит краткое изложение поддержки многозадачности в АУш32 АР1. Более подробную информацию вы можете найти в книге [5] или в справочных материалах М8ВК Объекты ядра Современные процессоры позволяют выполнять программы в одном из двух режи- мов: Кете1 (режим ядра) или 1}зег (режим пользователя). В режиме ядра приложе- нию, кроме всего прочего, разрешено выделять память для других приложений. Поэтому изменить важные данные в памяти компьютера, например, управляющие распределением памяти, может только приложение, работающее в режиме ядра. Архитектура операционной системы АУтйомга КГ/2000 состоит из двух основ- ных частей: привилегированной подсистемы режима ядра (рп^11ере(1кеп1е1то(1ераг1д и непривилегированной подсистемы пользовательского режима (попрпеИе^еб изег тобе рагк). Подсистема ядра содержит следующие компоненты: □ НАБ (На1х1\\ аге АЬзНасНоп Бауег) — уровень, абстрагирующий другие компо- ненты от аппаратных различий, зависимых от платформы. □ Микроядро (МхсгоКегпе!), которое отвечает за планирование потоков, пере- ключение задач, обработку прерываний и исключительных ситуаций, много- процессорную синхронизацию. □ Драйверы устройств — драйверы оборудования, файловой системы и сетевой поддержки, реализующие пользовательские функции ввода-вывода. □ Управление окнами и графическая подсистема реализуют функции графичес- кого интерфейса. □ Исполнительная часть отвечает за управление памятью, управление процес- сами и потоками, безопасность, ввод-вывод данных и взаимодействия между процессами. Объекты ядра используются системой и приложениями для управления са- мыми разнообразными ресурсами, например процессами, потоками, файлами, се- мафорами, событиями и многими другими.
Каждый объект ядра — это блок памяти, выделенный ядром и доступный только ему. В блоке размещается структура данных, поля которой содержат информа- цию об объекте. Некоторые поля (например, дескриптор защиты и счетчик коли- чества пользователей) присутствуют во всех объектах, но большая их часть спе- цифична для объектов конкретного типа. Приложение не имеет прямого доступа к структурам данных, представляющим собой объекты ядра. Оперировать объек- тами ядра приложение может только через специальные функции АУшбом^. Когда вы вызываете функцию, создающую объект ядра (например, Сгеа1:еН1е), она возвращает дескриптор созданного объекта. Этот дескриптор может быть ис- пользован любым потоком вашего процесса. Обычно значения дескрипторов объектов ядра действительны только в адресном пространстве процесса, их создав- шего. Поэтому все попытки использования такого дескриптора в другом процес- се приводят к ошибкам. Исключение составляют объекты ядра, предназначенные для синхронизации или обмена данными между процессами. Более подробно о них будет сказано ниже. Объекты принадлежат ядру, а не процессу. Другими словами, если процесс со- здает какой-либо объект ядра, а затем завершает свою работу, то объект ядра мо- жет быть и не разрушен. В большинстве случаев объект все же разрушается, но если этим объектом пользуется другой процесс, то ядро не позволит разрушить объект до тех пор, пока второй процесс не откажется от него. Ядру известно, сколько процессов использует конкретный объект ядра, по- скольку в каждом объекте есть счетчик пользователей. В момент создания объек- та ядра счетчику присваивается единичное значение. Когда к этому объекту обра- щается другой процесс, значение счетчика увеличивается на единицу. А когда какой-то процесс завершается, счетчики всех используемых им объектов ядра уменьшаются на единицу. Как только счетчик пользователей объекта обнуляется, ядро уничтожает этот объект. Многие объекты ядра находятся всегда в одном из двух состояний: свободном состоянии (8Щпа1ес1 шске') либо несвободном состоянии (поп8Щпа1е<1 леке). Переход из одного состояния в другое осуществляется по правилам, определенным Мхсго- зой для каждого из объектов ядра. Эти состояния могут использоваться так называ- емыми есай-функциями для синхронизации выполнения потоков. Объекты ядра можно защитить с помощью дескриптора защиты (зесигИу Фезспркуг). Он содержит информацию о том, кто создал объект и кто имеет пра- ва на доступ к нему. Дескрипторы защиты обычно используют при написании серверных приложений. Создавая клиентское приложение, можно просто игно- рировать это свойство объектов ядра. Почти все функции, создающие объекты ядра, принимают в качестве аргумен- та указатель на структуру 5ЕС11К1ТУ_АТТК1В11ТЕ5. В большинстве случаев на месте соответствующего аргумента можно указать значение МОП, и тогда объект созда- ется с защитой по умолчанию. В этом случае создатель объекта и любой член груп- пы администраторов получают полный доступ к объекту, а все прочие процессы к объекту не допускаются. Вне зависимости от того, как был создан объект ядра, после окончания работы с ним его нужно закрыть вызовом функции С1озеНапс11е: В001 С1озеНапсПе(НАМО1Е ИОЬаесД); Эта функция сначала проверяет таблицу дескрипторов данного процесса, что- бы убедиться, что процесс имеет доступ к объекту Н ОЬ] ее!. Если доступ разрешен,
то система получает адрес структуры данных объекта НОЬ]’ес1! и уменьшает в ней счетчик количества пользователей. Как только счетчик обнулится, ядро удаляет объект из памяти. Если же дескриптор неверен, то функция С1о$е Н ап с11е возвращает значение ЕА15 Е, а функция СеИазЕЕггог — код ЕККОК_1МУА1Ю_НАМО1Е. Перед самым возвратом управления функция С1озеНапс11е удаляет соответству- ющую запись из таблицы дескрипторов. После этого дескриптор НОЬ]ес± считает- ся недоступным для данного процесса, и его нельзя более использовать. Но если счетчик пользователей этого объекта не обнулен, то объект остается в памяти. Это означает, что объект используется другими процессами. Когда и остальные процессы завершат свою работу с этим объектом, тоже вызвав функцию С1о$еНапс11е, он будет разрушен. Ранее указывалось, что объекты ядра используются только в рамках процес- са, их создавшего. Но все же иногда возникает необходимость в совместном ис- пользовании объектов ядра несколькими процессами, например, в следующих ситуациях: □ объект ядра «проекция файла» позволяет двум процессам, исполняемым на одной машине, совместно использовать одни и те же блоки памяти; □ почтовые ящики и именованные каналы дают возможность программам обме- ниваться данными с процессами, исполняемыми на других машинах в сети; □ мьютексы, семафоры и события позволяют синхронизировать потоки, испол- няемые в разных процессах, чтобы одно приложение могло уведомить другое приложение об окончании той или иной операции. \Уш32 АР1 предоставляет три механизма, позволяющие процессам исполь- зовать одни и те же объекты ядра: а) наследование дескриптора объекта в дочер- нем процессе; б) именованные объекты; в) дублирование дескрипторов объек- тов. Второй из указанных механизмов основан на использовании совпадающих имен для разделяемых объектов. Именно он будет применен в программных примерах, рассматриваемых ниже в разделе «Обмен данными между процес- сами». Процессы и потоки При запуске приложения операционная система АУтсЕпуз создает процесс. Про- цесс (ргосезз) — это совокупность ресурсов, необходимых для выполнения про- граммы. Процесс владеет виртуальным адресным пространством, выполняемым кодом, данными, дескрипторами необходимых объектов и иными ресурсами. Од- нако сам по себе процесс не выполняется. Вместо этого он запускает единствен- ный поток, который часто называют первичным потоком (рптагу 1кгеас1). Если процесс имеет только один первичный поток, то, фактически, понятия «процесс» и «поток» совпадают. Первичный поток может создавать другие потоки, те, в свою очередь, новые потоки и т. д. Поток (1Игеас[) — это основная выполняемая единица, для которой операци- онная система выделяет процессорное время. Каждый поток работает со своим контекстом. Контекст потока (ЪИгеаН сопЪехк') — это структура, содержащая зна-
чения всех регистров процессора. Кроме того, поток имеет доступ ко всем ресур- сам своего процесса, включая память, открытые файлы и другие ресурсы. Обычно приложение содержит только один процесс, поэтому термины «про- грамма» и «процесс» часто используются как синонимы. В то же время любой поток процесса может создать дочерний процесс, выполняющийся одновременно с родительским процессом. Планирование потоков Чтобы все потоки работали, операционная система выделяет каждому из них оп- ределенное процессорное время. Тем самым создается иллюзия одновременного выполнения потоков. Разумеется, для многопроцессорных систем возможен ис- тинный параллелизм. Каждый поток может находиться в одном из трех состояний, показанных на рис. 9.1. Выполнение Готовность Блокировка Рис. 9.1. Диаграмма состояний потока В однопроцессорной системе в любой момент времени только один поток мо- жет находиться в состоянии выполнения. Все остальные потоки находятся либо в состоянии готовности, либо в состоянии блокировки. С самого начала поток попадает в очередь готовых к выполнению потоков. Через какое-то время операционная система выделяет в его распоряжение цент- ральный процессор, и поток переходит в состояние выполнения. В системе АУтскпуз реализована система вытесняющего планирования на основе приоритетов. Это означает, что освободившийся процессор продолжает обслуживать тот поток из очереди, который обладает наибольшим приоритетом. О правилах назначения приоритетов мы поговорим чуть позже. Выбранный для выполнения поток работает в течение некоторого периода, называемого квантом. АУшйоауз оперирует квантом потока не как отрезком вре- мени, а как целым числом. Обычно поток стартует со значением кванта, равным 6 — для \У1пс1о\\-з 2000 РгоГеззюпа! или 36 — для АУтскпуз 2000 Зегуег. Каждый раз, когда возникает прерывание от системного таймера, из кванта выполняющегося потока вычитается фиксированное значение 3, и так продолжается до тех пор, пока значение кванта не достигнет нуля. Поэтому под управлением АУшсЬауз 2000 РгоГеззюпа! поток будет выполняться в течение двух интервалов системного таймера, а под управлением АУшсклуз 2000 Зегуег — в течение 12 интер- валов. Интервал (или период) системного таймера обычно равен 10 мс или около 15 мс, в зависимости от аппаратной платформы. Точное значение этого интервала
можно получить с помощью функции 6е15у$1етТ1’теАф’и$1:теп1: (см. главу 10). На- пример, для моего компьютера с процессором 1п1е1 Се1егоп СРП 2.0 ГГц период срабатывания системного таймера равен 15,625 мс. При таком периоде кванту потока соответствует временной интервал 15,625 • 2 = 31,25 мс. Когда после очередного прерывания квант потока становится равным нулю, \Упк1о\\ъ переводит поток в состояние готовности и ищет в системе поток с са- мым высоким приоритетом, находящимся в состоянии готовности. Если в состоя- нии готовности находятся несколько потоков с приоритетом не ниже предыдуще- го выполняемого потока, то следующий поток из очереди будет переведен в состояние выполнения и начнет функционировать. Таким образом, потоки с оди- наковым уровнем приоритета обслуживаются в циклическом порядке. Впрочем, при отсутствии других претендентов предыдущий поток может получить еще один квант. Однако выполняемый поток не всегда полностью использует свой квант. Его выполнение может быть прервано при ненулевом кванте в двух ситуациях: □ когда появился в состоянии готовности другой поток с более высоким при- оритетом; при этом текущий поток вытесняется и переводится в состояние го- товности; □ текущему потоку потребовался какой-либо системный ресурс (или объект ядра), который в настоящий момент времени является занятым; в этом случае поток переводится в состояние блокировки (ожидания события). Выбрав новый поток, операционная система переключает контекст. Эта опе- рация заключается в сохранении содержимого регистров процессора для вытес- ненного потока и загрузке контекста для выбранного потока. Классы приоритетов процесса и приоритеты потоков \У1пс1о\\ъ поддерживает 32 приоритета (от 0 до 31) — чем больше номер, тем выше приоритет. Приоритет потока складывается из двух составляющих: класса при- оритета процесса, его создавшего, и относительного приоритета потока внутри этого класса. Классы приоритетов процессов приведены в табл. 9.1. Таблица 9.1. Классы приоритетов Класс Флаг в функции Сгеа1еРгосе$$ Базовый уровень 1с11е Ю1_Е_РК1ОКГГУ_С1_А88 4 Ве1о\л/ поппа! ВЕЮЕ_ЫОкМА1_РкЮк1ТУ_С1_А88 6 Могта1 1\10кМА1__РКЮК1ТУ_С1_А88 8 АЬоуе поппа 1 АВО7Е_ЫОкМА1__РКЮкПУ_С1_А88 10 Н|дН Н1СН_РкЮК1ТУ_С1_А88 13 Кеа1йте кЕА1_Т1МЕ_РК1ОК1ТУ_С1_А88 24 Классы Ве1о\л/ погта1 и АЬоуе погта1 стали использоваться, начиная с АУхпбом^з 2000. Класс 1с11е назначается процессу, который должен простаивать в случае ак- тивности других процессов, например для приложения — хранителя экрана.
Процессам, запускаемым пользователем, присваивается класс Могта1. Это са- мые многочисленные процессы в системе. Как правило, они являются интерак- тивными, то есть требуют постоянного взаимодействия с пользователем, как, на- пример, графические или текстовые редакторы. Процессы класса Могта1 делятся на процессы переднего плана ^огергоипс!) и фоновые (Ъаск^гоипФ). Для процесса, с которым пользователь в данный момент работает, то есть для процесса передне- го плана, уровень приоритета поднимается на две единицы. Это повышает ком- фортабельность общения пользователя с прикладной программой. Создавать процессы, относящиеся к классу Н1’дН, следует с большой осторож- ностью. Если поток с классом приоритета Н1’дН занимает процессор достаточно долго, то в это время другие потоки вообще не получат доступа к процессору. Обыч- но с классом Н1’дН работают некоторые системные процессы, которые большую часть времени ожидают какого-либо события, например, М1’п1одоп.ехе. Если в вашем приложении какая-то подзадача требует быстрой реакции на некоторое событие, то вы можете повышать класс приоритета процесса до значения Н1дН именно на тот период, когда решается эта подзадача, а затем возвращать его к зна- чению Могта1. Для изменения класса приоритета процесса во время работы при- ложения может применяться функция 5е1:РлолгуС1а$$. Практически никогда вы не должны использовать класс приоритета КеаШте, поскольку в этом случае ваше приложение будет прерывать системные потоки, управляющие мышью, клавиатурой и дисковыми операциями. Система будет фактически парализована. Только в особых случаях, когда программа взаимодей- ствует непосредственно с аппаратурой или решаются короткие подзадачи, для которых нужно гарантировать отсутствие прерываний, класс приоритета КеаШте может быть кратковременно использован. По умолчанию создаваемый поток получает базовый приоритет в соответствии с классом своего процесса. После создания потока его приоритет может изме- няться как операционной системой, так и приложением с помощью функции ЗеЕГНгеадРпопЧу. В табл. 9.2 приведены относительные приоритеты потоков. Таблица 9.2. Относительные приоритеты потоков Относительный приоритет Флаг в функции ЗеСТН геас! Рпоп^у Описание 1с11е ТНкЕАО_РкЮк1кУ_Ю1_Е Для процессов класса кеа1йте прио- ритет потока равен 16, для процес- сов остальных классов равен 1 1_о\л/е81 ТНкЕАО_РкЮк1кУ_1_СМ/Е8Т Приоритет потока меньше базового приоритета на 2 Ве1о\л/ погта! ТНкЕАО_РкЮк1кУ_ВЕЮ\Л/_ЫОкМА1 Приоритет потока меньше базового приоритета на 1 Ыогта1 ТНкЕАО_РкЮк1кУ_1\ЮкМА1_ Приоритет потока равен базовому приоритету АЬоуе погта 1 ТНкЕАО_РкЮк1кУ_АВОУЕ_ЫОкМА1_ Приоритет потока больше базового приоритета на 1 Н|дИе51 ТНкЕАО_РкЮк1кУ_Н1СНЕ5Т Приоритет потока больше базового приоритета на 2 "Пте спйса! ТН кЕАО_РкЮк1НУ_Т1М Е_СРШСА1_ Для процессов класса кеа1йте прио- ритет потока равен 31, для процес- сов остальных классов равен 15
Управление процессами Самый распространенный способ начала процесса — это осуществить запуск при- ложения в Проводнике (Ехр1огег), либо в меню Пуск (51:аг1:), либо набрав название программы в командной строке. Кроме того, АУш32 АР1 содержит несколько функ- ций, которые можно использовать для создания и управления процессами. Использование функции Сгеа(еРгосе55 Функция Сгеа1:еРгосе$$ создает новый процесс и его первичный поток. Она имеет следующий прототип: В001 СгеабеРгосе88( 1РСТЗТВ 1рАрр11саб1опИате, // имя исполняемого файла ЕРТЗТЕ 1рСоттапс111пе, // командная строка 1Р5ЕС1Ж1ТУ—АТТВ1ВЫТЕЗ ргосеззАигчбьгЕез, // атрибуты доступа к процессу ЕРЗЕСЫВ1ТУ—АТТВ1ВЫТЕЗ ббгеабАНг! Ьыбез, // атрибуты доступа к потоку ВООЕ ЫпбегчбНапсПез, // флаг наследования дескрипторов ОШРО скСгеабтопПадз, // флаги создания и флаги класса приоритета ЕРУОЮ 1рЕпу1гоптепб, // указатель на параметры настройки окружения ЕРСТЗТВ 1рСигтепбО1гесбогу, // путь к текущему каталогу ЕРЗТАВТ0Р1МЕ0 1р8багбир1пбо, // указатель на структуру ЗТАВТ0Р1МЕ0 ЕРРВ0СЕЗЗ_1 МЕОВМАТЮМ 1 рРгосе881пбогтаб1оп // указатель на структуру // РВ0СЕЗЗ-1МЕ0ВМАТ10М ); Имя исполняемого файла можно задать в первом или втором параметре. Параметр 1рСоттапсИ1’пе позволяет указать полную командную строку, исполь- зуемую функцией СгеабеРгосе$$ при создании нового процесса. Разбирая эту строку, функция полагает, что первый компонент в ней представляет собой имя исполняе- мого файла. Если в этом имени расширение не указано, она считает его .ехе. Далее функция приступает к поиску заданного файла и делает это в следующем порядке: 1. Текущий каталог вызывающего процесса. 2. Системный каталог АУшсЬмгз. 3. Основной каталог АУшсЬмгз. 4. Каталоги, перечисленные в переменной окружения РАТН. Конечно, если в имени файла указан полный путь доступа, то система сразу обращается туда и не просматривает эти каталоги. Чаще всего параметру фАррПсаЧопМате передается значение МОП, а имя ис- полняемого файла содержится в параметре 1рСоттапсИ1’пе. Перед вызовом функции СгеабеРгосе$$ вы должны определить две структурные переменные типа 8ТАКТ11Р1М РО и РКОСЕ55_1МЕОКМАТЮМ: ЗТАВТВР1МЕ0 81; РР0СЕЗЗ_1МЕОРМАТ1ОМ р1 ; Адреса этих структур передаются в двух последних параметрах функции СгеабеРгосе$$. Элементы структуры 5ТАКТ11Р1М ЕО задают атрибуты отображения нового про- цесса. Надо сказать, что большинство приложений порождает процессы с атрибу- тами по умолчанию. Но и в этом случае вы должны инициализировать все поля структуры 5ТАКТ11Р1М ЕО хотя бы нулевыми значениями, а в поле сЬ занести размер этой структуры.
Если запуск нового процесса осуществлен успешно, то функция Сгеа1:еРгосе$$ возвращает значение ТРОЕ и помимо этого заполняет поля структуры РКОСЕ55_ I МЕО КМ АТЮМ, которая определена следующим образом: 1урес1еТ _РЕ0СЕ88_1МЕ0ЕМАТ10М { НАМО1Е ИРгосезз; // дескриптор нового процесса НАМОЕЕ ИТИгеаб; // дескриптор первичного потока нового процесса ОШРО б\л/Ргосе881б: // идентификатор нового процесса ОШРО с1\л/ТГ|геабIб; // идентификатор первичного потока нового процесса } РЕ0СЕ88_1МЕ0РМАТЮМ; Описание остальных параметров функции Сгеа1:еРгосе$$ можно найти в спра- вочных материалах М8ВК Для этих параметров часто используются значения по умолчанию. Когда поток в приложении вызывает функцию Сгеа1:еРгосе$$, система создает объект ядра «процесс» с начальным значением счетчика его пользователей, рав- ным единице. Затем система создает для нового процесса виртуальное адресное пространство1 и загружает в него код и данные как для исполняемого файла, так и для любых ВЬЕ, если они используются в работе. Далее система формирует объект ядра «поток» со счетчиком, равным единице, для первичного потока ново- го процесса. Первичный поток начинает с исполнения стартового кода из библиотеки С/С++, который в конечном счете вызывает функцию п Ма1 п, \лЛЛ/1 п Ма1 п, таю или мташ2 в запускаемой программе. Завершение процесса Процесс можно завершить четырьмя способами: □ Входная функция первичного потока, например \Л/1пМа1п, возвращает управле- ние (предпочтительный способ). □ Один из потоков процесса вызывает функцию ЕхйРгосезз. □ Поток другого процесса вызывает функцию Тептппа1:еРгосе$$ (нежелательный способ). □ Все потоки процесса завершаются по своей воле. Но это случается очень редко. Рекомендуется проектировать приложение так, чтобы его процесс завершался только после возврата управления функцией первичного потока. Это единствен- ный способ, который гарантирует корректную очистку всех ресурсов, принадле- жащих первичному потоку. При таком завершении любые объекты С++, создан- ные данным потоком, уничтожаются соответствующими деструкторами. Система освобождает память, которую занимал стек потока, и устанавливает код заверше- ния процесса, который и возвращает входная функция. Вы можете также завершить процесс, вызвав функцию ЕхйРгосезз. В справоч- ных материалах М8^^ этот способ указан как рекомендуемый. В то же время Дж. Рихтер указывает [5], что возможны ситуации, когда при данном способе 1 Более подробно о виртуальном адресном пространстве говорится в разделе «Виртуальная память. Адресное пространство процесса». 2 С символа «м-» начинаются имена Птсобе-версий входных функций первичного потока. Функции шатп и уутатп используются в консольных приложениях.
завершения процесса не для всех объектов С++ будут вызваны деструкторы. Ко- нечно, операционная система и в этом случае корректно очистит все ресурсы, выде- ленные процессу. Но при этом весьма вероятна утечка памяти или других ресурсов. Вызов функции Теггтпа1:еРгосе$$ тоже завершает процесс. Главное отличие этой функции от ЕхйРгосезз заключается в том, что ее может вызвать любой поток и за- вершить при этом любой процесс. Пользуйтесь функцией Теггтпа1:еРгосе$$ лишь в крайнем случае, когда иным способом завершить процесс не удается. Процесс не получает абсолютно никаких уведомлений, что он завершается, и приложение не может выполнить очистку ресурсов или предотвратить свое неожиданное за- вершение. При этом теряются все данные, которые программа не успела перепи- сать из памяти на диск. Но операционная система и в этом случае освобождает все принадлежавшие процессу ресурсы. Четвертая ситуация может возникнуть, если все потоки вызвали ЕхйТНгеас! или они были закрыты другими потоками, вызвавшими функцию Тептппа1:еТНгеас1. Об- наружив, что в процессе нет исполняющихся потоков, операционная система не- медленно завершает его. Код завершения процесса приравнивается к коду завер- шения последнего потока. В случае завершения процесса системой выполняются следующие действия. □ Выполнение всех потоков в процессе прекращается. □ Все Пзег- и ОН 1-объекты, созданные процессом, уничтожаются, а объекты ядра закрываются, если их не использует другой процесс. □ Объект ядра «процесс» переходит в свободное, или незанятое (зщпсйеб}, со- стояние. □ Счетчик пользователей объекта ядра «процесс» уменьшается на единицу. Запуск обособленных дочерних процессов В большинстве случаев приложение создает другие процессы как обособленные {(1е1:с1ске(1 ргосеззез). Это значит, что после создания и запуска нового процесса родительскому процессу нет нужды взаимодействовать с ним или ждать, пока он закончит работу. Именно так и действует ЕхрЕогег. Это приложение запускает для пользователя новые процессы, а после этого более не следит за их судьбой. Приведенное ниже приложение Сгеа1еМуРгосе$$ демонстрирует, как можно выз- вать из вашей программы стандартное приложение АУтскпуз Калькулятор (сак.ехе). Создавая приложение Сгеа1еМуРгосе$$, добавьте к нему ресурс меню с иден- тификатором ЮК_МЕМ1П. Меню должно содержать один пункт с именем СгеаГе ргосезз и идентификатором ЮМ_СКЕАТЕ_РКОСЕ55. Скопируйте также в папку про- екта Сгеа1еМуРгосе$$ файлы КМпс1.Н и К\Л/пс1.срр из листинга 1.2 и добавьте их в состав проекта. Листинг 9.1. Проект Сгеа^еМуРгосезз ////////////////////////////////////////////////////////////////////// // Сгеа^еМуРгосезз.срр #1пс1ис1е <\л/1 пбомз. И> #1пс1ис1е "гезоигсе.И" #1пс1ис1е "К1л1пс1.И" ЬРЕЗиЬТ САИВАСК 1л1пс1Ргос(ШМ0, ЮТ, ПРАВАМ, ЕРАВАМ); //===================================================
1пЬ ШИАР1 ШпМа а п (НIИЗТАМСЕ Шнапсе, Н1М5ТАМСЕ ИРгеу1пзЬапсе, ЕРЗТР 1рСтс1Е1пе, Ш пСтс15Ио\л/) { МЗЗ тзд; К1л/пс1 та1 п1л/пс1 ("Сгеа1аеМуРгосе88", ЫпзЬапсе, пСггкОом, МгЕРгос, МАКЕ1МТРЕ30иРСЕ( 10Е_МЕШ1), 100, 100, 400, 300); \л/М1е (ОеЬМеззадеС&тзд, ШЕЕ, 0, 0)) { ТгапзШеМеззадеС&тзд); ОтзраЬсИМеззадеС&тзд); } геЬигп (тзд.мРагат); } /======================= ЕРЕЗЕЕТ САЕЕВАСК ШЬРгос(ШИВ Шпс1, ЕIИТ иМзд, ПРАВАМ мРагат, ЕРАВАМ 1Рагат) { НОС НОС; РА1МТЗТРЕСТ рз; ЗТАВТОРЖО з1 ; 8-Ьа-Ыс РР0СЕЗЗ-1МР0РМАТЮМ р1 ; ВООЕ зиссезз; змПсИ (иМзд) { сазе ЕМ_С0ММАМ0: зтЬсИ (Е01лЮВ0(\л/Рагат)) { сазе I0М_СРЕАТЕ_РР0СЕЗЗ: 2егоМетогу( &з1, з1геоТ(з1) ); з1,сЬ = з1геоГ(з1); зиссезз = СгеаЬеРгосезз( ИЕЕЕ, "са!с.ехе", ИЕЕЕ, ИЕЕЕ, ЕАЕЗЕ, 0, ИЕЕЕ, ИЕЕЕ, &з1, &р1); 1Г (!зиссезз) МеззадеВохСШпс1, "Еггог оС СгеаЬеРгосезз", ИЕЕЕ, МВ_0К); Ьгеак; с1еГаи!Ь: Ьгеак; } сазе Ш_РА1МТ: НОС = ВедапРаапЬ(Н1л1пс1, &рз); Епс1Ра1 пЬ(Н1л1пс1, &рз); Ьгеак; сазе Ш_0Е8ТВ0У: 01озеНапсП е(р1.ИРгосезз); 01 озеНапсПе(р1. ИТИгеас!); РозЩпЬМеззадеСО); Ьгеак; с1еГаи!Ь: геЬигп 0еГ1л11 пЬомРгос(И1л1пс1, иМзд, мРагат, 1Рагат); } геЬигп 0; } //////////////////////////////////////////////////////////////////////
После запуска приложения Сгеа1:еМуРгосе$$ пользователь может вызвать при- ложение Калькулятор, выполнив команду меню Сгеа1е ргосезз. После этого оба при- ложения будут работать независимо друг от друга. Вы можете пользоваться по- переменно и тем и другим приложением, а также в любой последовательности прекращать их выполнение. Управление потоками Каждый поток начинает выполнение с некоторой входной функции. В первич- ном потоке используется одна из функций: п Ма1 п, \лЛЛ/1 и Ма1 п, таю или мташ. Если вы хотите создать вторичный поток, в нем тоже должна быть входная функция, которая выглядит примерно так: ОШКО 1Л/1МАР1 ТНгеас1Еипс(Р7010 руРагат) { 01лЮР0 (МезиИ = 0; гебигп скл/РезиИ; } Функция потока может выполнять любые задачи. Рано или поздно она закон- чит свою работу и вернет управление. В этот момент поток остановится, память, отведенная под его стек, будет освобождена, а счетчик пользователей его объекта ядра «поток» уменьшится на единицу. Когда счетчик обнулится, этот объект ядра будет разрушен. В отличие от входной функции первичного потока, имеющей одно из предоп- ределенных имен: УУ1пМа1П, \л/\Л/1пМаш, таю или мташ, — функции других потоков могут иметь произвольные имена. Функция Сгеа(еТ11геас1 Для создания потока используется функция СгеаЕеТНгеас!: НАМО1Е Сгеа^еТИгеасК ЬР8ЕСиР1ТУ_АТТР1 ВЫТЕ5 ббгеабАНп’бибез, // атрибуты доступа ОШЕО (МбаскЗтге, // размер стека ЕРТНРЕАО—5ТАРТ—ЕОЫТ1 ИЕ 1 р8багбАс1с1гезз, // адрес функции потока ЕРУОЮ 1рРагатебег, // параметр функции потока ОШЕО (Мтеаб1опПадз, // флаги потока ЬРОШЕО "IрТНгеас!Iб // идентификатор потока Как и при работе с функцией Сгеа1:еРгосе$$, для многих параметров можно за- давать значения по умолчанию (0 или МОП). Третий параметр не может иметь значение по умолчанию, ему всегда передается адрес функции потока. Четвертый параметр часто используется для организации взаимосвязи вызывающего потока с дочерним потоком (листинг 9.2). При каждом вызове функции СтеаЕеТНгеас! система создает объект ядра «поток» с начальным значением счетчика его пользователей, равным единице. Система вы- деляет память под стек потока из адресного пространства процесса. Новый поток выполняется в адресном пространстве того же процесса, что и родительский поток. Завершение потока можно организовать четырьмя способами: □ функция потока возвращает управление (предпочтительный способ);
□ поток самоуничтожается вызовом функции ЕхйТНгеад (рекомендация такая же, как и для функции ЕхйРгосезз); □ один из потоков данного или стороннего процесса вызывает функцию Теггш’па1:е ТНгеас! (нежелательный способ); □ завершается процесс, содержащий данный поток (тоже нежелательно). В случае завершения потока сначала уничтожаются все Пзег-объекты, при- надлежащие потоку, а именно окна и ловушки (Иоокз). После этого объект ядра «поток» переходит в свободное состояние, а счетчик пользователей объекта ядра «поток» уменьшается на единицу. Функция §1еер Поток может попросить у системы не выделять ему процессорное время на опре- деленный период, вызвав функцию 51еер: 7010 81еер(0ШК0 с1\л/М1111 зесопОз); Эта функция приостанавливает выполнение потока на д\л/М11115есопс18 милли- секунд. При использовании данной функции следует учитывать несколько до- полнительных аспектов. □ Вызывая функцию 51еер, поток добровольно отказывается от остатка выделен- ного ему кванта времени. □ Система прекращает выделять потоку процессорное время на период, пример- норавный заданному. Вопрос точности, с которой функция обеспечит затребо- ванную задержку, подробно рассматривается в главе 10. □ Если параметр д\л/М1 Шзесопдз равен нулю, то текущий поток уступает оставшу- юся часть своего кванта другому потоку, обладающему равным с ним приори- тетом и готовому к выполнению. Однако система может снова запустить теку- щий поток, если других планируемых потоков с тем же приоритетом нет. В главе 10 (раздел «Программирование задержек в исполнении кода») приво- дятся программные эксперименты для уточнения характеристик функции 51еер. Пример многопоточного приложения Приведенное ниже приложение Сгеа1еМуТНгеасЕ демонстрирует создание и парал- лельную работу трех потоков. Листинг 9.2. Проект Сгеа1еМуТИгеас18 ////////////////////////////////////////////////////////////////////// // Сгеа^еМуТИгеабз.срр #1пс1ис1е <\л/1 псЫз. И> #1пс1ис1е <з^г1пд> из1пд патезрасе з^б; #1пс1ис1е "К1л/пс1.ГГ епит ЕзегМзд { иМ_ТНЕЕАО_ООМЕ = Ш_Е8ЕЕ+1 }; з^гисД ТИгеабМападег { ТИгеабМападег(з^п’пд _пате) : пате(_пате) { п\/а1ие = 0; } НШ0 Ыпс1РагепР; продолжение &
Листинг 9.2 {продолжение} збгапд пате; Ш п\/а1ие; }: Т6геас1Мападег Ш_А(з^г1пд( "Поток А")); Т6геас1Мападег Ш_В(з^г1пд( "Поток В")); Т6геас1Мападег Ш_С(з^г1пд( "Поток С")); ВШИВ 1л/1МАР1 ТНгеас1ЕипсА(1_РУ01В); ВШИВ ШИАР1 ТИгеас1РипсВ(1_РУ01В); ВШИВ ШИАР1 ТИгеас1РипсС(1_РУ01В); ВРЕ8П1Т САЕЕВАСК Шс1Ргос(НШВ, ПIИТ, АРАРАМ, ВРАКАМ); //============================ Ш ШИАР1 ШпМа1п(НIИЗТАМСЕ Шнапсе, Н1М5ТАМСЕ 6Ргеу1пзШсе, ЕРЗТР 1рСтбЕ1пе, Ш пСтс15Р|О\л/) { М86 тзд; К1л1пс1 та1пМпб("СгеабеМуТИгеабз", 61пз^апсе, пСгткОом, МгбРгос, МПИ, 100, 100, 400, 160); \л/М 1 е (6е^Меззаде(&тзд, МбВВ, 0, 0)) { ТгапзШеМеззаде(&тзд); В1зраШМеззаде(&тзд); } ге^игп (тзд.мРагат); } //========================= ЕРЕЗПЕТ САЕЕВАСК Шс1Ргос(НШП Шпс1, ЮТ иМзд, ПРАВАМ мРагат, ВРАРАМ 1Рагат) { НВС ВВС; РА1МТЗТРПСТ рз; зТаТ1с НАИВВЕ Югеас1А, Югеас1В, ВТИгеабС; зТаТ1 с сбаг ТехЩОО]; Т6геас1Мападег* рТт; 8‘Еа'Ыс 1 гтЕ у = 0; змПсб (иМзд) { сазе Ш_СВЕАТЕ: бт_А. ЫгбРагепТ = Шпб; ВТИгеабА = СгеаТеТОгеасОПЕЕ, 0, Т6геас1ЕипсА, Ш1_А, 0, ИбЕЕ); 1Т (!ВТбгеабА) МеззадеВох(1а1л1пб, "Еггог оТ сгеа^е 6Т6геас1А", ИбЕЕ, МВ_0К); Ш-В. ЫгбРагегР = Шпб; ВТбгеабВ = Сгеа^еТПгеасКМПЕЕ, 0, Т6геас1ЕипсВ, Ш1_В, 0, ббЕЕ); 1Т (!6Т6геабВ) МеззадеВох(Шпб, "Еггог оТ сгеа^е 6Т6геас1В", ИНЕЕ, МВ_0К); бт_С. ЫгбРагегР = Шпб; КТбгеабС = Сгеа^еТПгеасКМПЕЕ, 0, Т6геас1ЕипсС, Ш1_С, 0, ббЕЕ);
1Г (! Г|ТГ|геас1С) МеззадеВох(Шпс1, "Еггог оГ сгеаГе ЬТЬгеасЮ", М1ЛЕ, МВ_ОК); Ьгеак; сазе ЬМ-ТНВЕАВ-ВОМЕ: рТт = (ТЬгеас1Мападег*)мРагат; зрг1 пШЬехЬ, ’7з: соипЬ = %сГ, рТт->пате.с_зГг(), рТт->п\/а1 ие); У += 30; 1пуа11 (ШеВесГ(Шпс1, МОЕЕ, ЕАЕЗЕ); Ьгеак; сазе Ш_РА1МТ: ПОС = ВедапРаапЬ(Г|1л/пс1, &рз); ТехЬОиШВС, 20, у, ЬехЬ, зЬг!еп(ЬехЬ)); Епс1Ра1пЬ(Шпс1, &рз); Ьгеак; сазе Ш_ВЕ8ТВ0У: С1озеНапсП е(НТИгеабА); С1озеНапсП е(НТИгеабВ); С1озеНапсП е(ИТЕгеасЮ); РозЬ0и1ЬМеззаде(0); Ьгеак; с1еГаи!Г: геЬигп ВеГШ пЬомРгос(Г|1л/пс1, иМзд, мРагат, 1 Рагат); } геЬигп 0; } /============================= ВШРВ ШИАР1 ТЬгеаЬЕипсА(ЕР701В 1ру) { ТЬгеас1Мападег* рТт = (ТЬгеас1Мападег*)1ру; 1пЬ соипЬ = 0; Гог (1 гтЕ 1 =0; 1 < 100000000; ++1) соипГ++; рТт->п\/а1ие = соигЕ; Зепс1Ме88аде(рТт->Ьшс1РагепГ, Е1М_ТНЕЕАВ_ВОИЕ, (ПРАВАМ)рТт, 0); геГигп 0; } /============================= ВШВВ ШИАР1 ТЬгеаЬЕипсВ(ЕР701В Ору) { ТЬгеас1Мападег* рТт = (ТЬгеас1Мападег*)1ру; Ш соигЕ = 0; Гог (1 гтЕ 1 =0; 1 < 50000000; ++1) соипГ++; рТт->п\/а1ие = соипГ; продолжение
Листинг 9.2 {продолжение} Зепс1Ме88аде(рТт->Ыпс1РагепР, иМ_ТНРЕАО_ООМЕ, (1л1 РАКАМ)рТш, 0); геРигп 0; } //========================^ ОШКО ШИАР1 ТНгеас1ГипсС(ЕРУО1О 1ру) { ТИгеабМападег* рТт = (ТИгеас1Мападег*)1ру; 1пР соипР = 0; Рог (1пР 1 =0; 1 < 20000; ++1) соипР++; рТт->п\/а1ие = соипР; Зепс1Ме88аде(рТт->Ыпс1РагепР, иМ_ТНРЕАО_ООМЕ, (1л1 РАКАМ)рТт, 0); геРигп 0; } ////////////////////////////////////////////////////////////////////// В приложении определена структура ТВгеадМападег, объекты которой Рт_А, Рт_В и Рт_С используются для взаимосвязи дочерних потоков с первичным потоком. Адреса этих объектов передаются в качестве четвертого параметра при вызовах функции СгеаРеТНгеас!. В блоке обработки сообщения ММ_СКЕАТЕ создаются три дочерних потока с дес- крипторами НТНгеас1А, НТНгеас1В и НТНгеас1С. Функции потоков ТНгеадЕипсА, ТНгеадЕипсВ, ТЬгеадЕипсС работают по одному и тому же сценарию. В каждой из них есть локальный счетчик соипР, инкременти- руемый в цикле Гог. Разница только в числе повторений цикла: 100 000 000, 50 000 000 и 20 000 раз соответственно. После завершения цикла каждая функция посылает окну первичного потока пользовательское сообщение 11М_ТНВЕАО_ООМЕ. Получив сообщение 11М_ТНВЕАО_ООМЕ, функция \Л/пс1Ргос выводит в свое окно информацию о завершившемся потоке. На рис. 9.2 показан результат запуска приложения СгеаРеМуТНгеасК Рис. 9.2. Результат выполнения программы Сгеа1еМуТНгеас15 Хотя потоки запущены почти одновременно (порядок запуска: поток А, поток В, поток С), быстрее всех завершается поток С, затем поток В и последним — по- ток А. В этой последовательности информация о них и выводится в главное окно приложения. В заключение стоит сделать одно замечание об использовании функции СгеаРеТНгеас!. Дж. Рихтер предостерегает о возможных проблемах в работе прило- жения, если оно использует функции стандартной библиотеки С/С++ и создает поток вызовом функции СгеаРеТНгеас!. В этом случае рекомендуется создавать поток
вызовом функции _Ьед1’п1:Нгеас1ех из библиотеки Ухзпа! С++. У функции _Ьед1’п1:Нгеас1ех тот же список параметров, что и у Сгеа1еТГ|геас1, но их имена и типы несколько различаются. Чтобы упростить замену вызовов Сгеа1:еТГ|геас1 вызовами _Ьед1’п1:Нгеадех, объя- вите с помощью оператора рес1еГ указатель на функцию Цурес1еГ ыпз1дпес1 (_8Рс1са11 *РТНВЕАО_$ТАВТ) (уо1с1*) Теперь для создания потока А в рассмотренном выше приложении вы можете использовать следующую инструкцию: Ш1геас1А = (НАМО1Е)_Ьед1 пШеас1ех(ШЕЕ, 0, (РТНВЕА0_ЗТАВТ)ТИгеас1ЕипсА, (уоШ*)Ш_А, О, ШЕЕ); В случае применения функции _Ьед1’п1:Нгеадех обязательно свяжите ваш про- ект с многопоточной версией библиотеки С/С++, иначе будут возникать ошибки компиляции. Для этого надо выполнить команду меню РгодесС ► ЗеЙпдз и в по- явившемся диалоговом окне Рто]ес1: ЗеЙпдз перейти на вкладку С/С++. В списке Са1едогу выберите строку Соде СепегаВоп, а в списке 11$е гип-Вте НЬтагу — одну из многопоточных версий библиотеки, например ОеЬид Ми1ВВиеас1ес1. Взаимодействие потоков через глобальную переменную Как только вы разобьете приложение на несколько потоков, вам придется решать проблемы, о существовании которых вы даже не подозревали, занимаясь програм- мированием в однопоточном режиме. Например, простая операция считывания и записи в общую глобальную пе- ременную из нескольких потоков может быть источником ошибок в работе программы. В качестве примера рассмотрим приложение, приведенное в лис- тинге 9.3. Листинг 9.3. Проект ВадСоигЧ ////////////////////////////////////////////////////////////////////// // ВабСоипР.срр #1пс1ис1е <\л/1 пс1о\л/8. И> #1пс1ис1е <зРс11о.И> #1пс1ис1е "КМпсЕГГ #с1еГ1пе И 50000000 1опд д_соигЕег = 0; ОЕОВО ШИАР1 ТШеас1Еипс(ЕР\/010); уо1с1 1псСоипРег(); уо1с1 ОесСоипРег(); ЕРЕЗЕЕТ САЕЕВАСК ШбРгос(НЕМО, Н1МТ, ПРАВАМ, ЕРАВАМ); //============================================ 1пР ШМАР1 ШпМа1п(Н1ШТАШЕ ШзРапсе, Н1МЗТАМСЕ ШгеуТпзРапсе, ЕРЗТВ 1рСтс1Е1пе, 1пР пСтсЕШм) { М86 тзд; КЕпс! та1 пЕпс1 ("Вас1СоигтЕ", ШпзРапсе, пСтсЕШм, ЕпбРгос, МНЕЕ, 100, 100, 400, 100); \л/М1е (СеРМеззадеС&тзд, МЕ1ЕЕ, 0, 0)) { . ТгапзШеМеззадеС&тзд); продолжение &
Листинг 9.3 {продолжение} 01 зраШМеззадеС&гпзд); } геРигп (тед.мРапат); } //=================================================================== 0КЕ300Т САЕЕВАСК Ипс1Ргос(НИМ0 Шпс1, 1ИМТ иМед, НРАВАМ мРапат, ЕРАЕАМ 1Рапат) { НОС НОС; РА1МТ8ТЕ0СТ р8; НАМОЮ ИТИпеаН; сИап РехЩОО]; 8\лпТсН (иМед) { саее ИМ_СВЕАТЕ: ИТИпеас! = СгеаЬеТПгеасКМНЕЬ, 0, ТИпеаНЕипс, ИНЕЕ, О, МНЕЕ); 1Р (! ИТИпеас!) Ме88адеВох(ИНпс1, "Еггог оТ СгеаЬеТИгеасГ, МНЕЕ, МВ_ОК); ОесСоипРепО; 1л1а1 РЕоп81 пд!еОЬ)есР(НТИгеаб, 1ИЕ1И1ТЕ); 1пуа110аРеВесР(Шпс1, ИНЕЕ, ТЕНЕ); Ьгеак; саее ИМ_РА1МТ: НОС = Вед1 пРа1 пЬ(НИпс1, &р8); 8рг1пШЬехЬ, "д_соипЬеп = %сГ, д_соипЬеп); ТехЬОиШОС, 20, 20, ЬехЬ, 8Ьп1еп(ЬехЬ)); Епс1Ра1 пЬ (НИпс1, &р8); Ьгеак; саее Ш_0Е8ТР0У: Ро8Ь0и1ЬМеееаде(0); Ьгеак; НеСаиИ: геРигп ОеСШпНомРгос(ИИпс1, иМед, мРапат, 1Рапат); } геРигп 0; } //========================================== 0ШР0 И1ИАР1 ТИгеаНЕипс(ЕР7010 1ру) { 1псСоипЬеп(); геРигп 0; } //========================================== уоЮ 1псСоипЬеп() { Топ (1пЬ 1 =0; 1 < И; ++1) ++д_соипЬеп; } //========================================== уоЮ ОесСоипЬепО { Топ (1пЬ 1 =0; 1 < И; ++1) --д_соипЬеп; } //////////////////////////////////////////////////////////////////////
Первичный поток создает с помощью Сгеа1еТГ|геас1 дочерний поток, имеющий входную функцию ТНгеас1Еипс. В теле функции ТНгеас1Еипс вызывается функция ТпсСоигйег, которая выполняет в цикле И раз операцию инкремента для глобаль- ного счетчика д_соип1ег. Запустив дочерний поток и не дожидаясь окончания его выполнения, первич- ный поток вызывает функцию ОесСоигйег. Функция ОесСоигйег выполняет в цикле И раз операцию декремента для глобального счетчика д_соип1ег. После возврата из ОесСоигйег первичный поток ждет окончания работы дочер- него потока с помощью функции \Л/а11Еог81пд1еОЬ]'ес11. Вызов функции №ПЕог81пд1еОЬаесШТИгеас1, 1ИЕ1М1ТЕ); обеспечивает приостановку выполнения потока на неограниченное время (1МЕ1М1ТЕ), пока не освободится объект ядра НТКгеад. После этого, вызывая функцию 1пуа11да±еКесЕ, первичный поток заставляет \Упк1о\\ъ сформировать сообщение ММ_РА1МТ. Обрабатывая это сообщение, функ- ция \Л/пс1 Ргос выводит значение переменной д_соип1:ег в главное окно приложения. Нетрудно догадаться, что правильным результатом выполнения этого приложе- ния должно быть значение д_соип1ег = 0. При маленьких значениях И так и происходит. Но вот для значения 50 000 000 эта программа дает самые разные результаты на разных компьютерах. Экспери- менты проводились в основном на компьютерах, имеющих процессор 1п1е1 Се1егоп и 1п1е1 РепНпш. Заметим, что при тактовой частоте 2 ГГц функция ТпсСоигйег так же, как и функция ОесСоигйег, требует для своего выполнения примерно 150 мс. При такой продолжительности выполнения каждый поток будет прерываться не менее пяти раз, отдавая процессор другому потоку. На всех компьютерах, имеющих процессор с технологией НурегТйгеаПп§, был получен ненулевой результат счетчика д_соип1ег. Для компьютеров с процессором без поддержки НурегТйгеаПп§ результат был иногда ненулевой, иногда нулевой. Чем же вызвано искажение результата работы программы? — Прерыванием потока в тот момент, когда процессор не полностью завершил очередную опера- цию! Рассмотрим это чуть подробнее. Допустим, что функция ОесСоигйег первичного потока приступила к очередно- му вычитанию единицы из счетчика. Процессор, реализуя эту операцию, скопи- ровал значение глобальной переменной д_соип1ег в свой регистр. Предположим, что это значение равно 100 000. Далее процессор успел вычесть единицу, но не успел переписать новое значение 99 999 обратно в глобальную переменную. В этот момент система отбирает процессор у первичного потока, сохранив контекст по- тока, и передает его на использование дочернему потоку. Предположим, что функ- ция ТпсСоигйег дочернего потока успела 50 000 раз добавить в счетчик единицу, так что переменная д_соип1ег стала равна 150 000. В этот момент квант дочернего потока истек, и после окончания очередной операции система возвращает процес- сор первичному потоку. При этом система восстанавливает контекст первичного потока с незавершенной операцией. Завершая эту операцию, процессор переписы- вает значение 99 999 из своего регистра в глобальную переменную д_соип1ег. В описанной ситуации квант работы дочернего потока пошел насмарку — его ре- зультаты утеряны! Итак, мы показали, что одновременное использование несколькими потоками общей глобальной переменной без должной синхронизации может быть источни- ком ошибок.
Синхронизация Кроме проблем, связанных с использованием общих глобальных переменных, у потоков могут быть также проблемы доступа к общим системным ресурсам. По- токи должны взаимодействовать друг с другом в следующих ситуациях: □ совместно используя разделяемый ресурс (чтобы не разрушить его); □ когда нужно уведомить другие потоки о завершении каких-либо операций. Примитив синхронизации — это объект, который помогает управлять многопо- точным приложением. Основными типами примитивов синхронизации в АУш- 2000 являются: □ атомарные операции АР1-уровня; □ критические секции; □ события; □ ожидаемые таймеры; □ семафоры; □ мьютексы. Каждый из указанных типов применяется в определенных ситуациях. Атомарный доступ и семейство 1п1ег1оскес1-функций Большая часть синхронизации потоков связана с атомарным доступом (а1опйс ассезз) — монопольным захватом ресурса обращающимся к нему потоком. АУш32 АР1 предоставляет несколько функций для реализации взаимно блокированных операций. Все 1п1ег1оскес1-функции работают корректно только при условии, что их аргументы выровнены по границе двойного слова (О\Л/ОКО). Функция 1п1ег1оскес11псгетеп1:, имеющая прототип 1_(Ж 1ггЬег1 оскесПпсгетегД(1_Р1_(Ж 1рАс1с1епс1); инкрементирует 32-разрядную переменную, адрес которой задается параметром 1рАс1с1епс1. Функция возвращает новое значение указанной переменной. Функция 1п1:ег1оскедОесгетеп1 определена аналогично функции Iп1ег1оскед!псге- теп!:, но она декрементирует 32-разрядную переменную. Пара функций ЕСЖ Шег! оскес1ЕхсИапде( ЕРЕОМС ПрТагдеЦ, ЕСЖ \/а!ие); Р7010 1пРег1 оскесЕхсНапдеРсПпРег(Р7010* рруТагдеР, Р7010 ру\/а1ие); монопольно заменяет текущее значение переменной типа ЮМ6, адрес которой пе- редается в первом параметре, значением, передаваемым во втором параметре. В 32-разрядном приложении обе функции работают с 32-разрядными значения- ми. В 64-разрядной программе первая функция оперирует 32-разрядными значе- ниями, а вторая — 64-разрядными. Обе функции возвращают исходное значение переменной. Следующая функция добавляет к значению переменной, адрес которой пере- дается в первом параметре, значение, передаваемое во втором параметре: ЕСЖ 1ггЬег1 оскес1ЕхсИапдеАс1с1 (ЕРЕОМС 1 рАс1с1епс1, ЕСЖ 1псгетегД);
Еще две функции выполняют операцию сравнения и присваивания по резуль- тату сравнения: ЕСЖ 1п1аег1 оскес1СотрагеЕхсГ|апде( ЕРЕ0М6 1рОез^1 паШп, ЕСЖ ЕхсИапде, ЕСЖ СотрагапсО; Р7010 1Щег1оскесОтрагеЕхсИаг1деРо1гДег(Р\/010* рруОез^1 па^’оп, Р7010 руЕхскапде, Р7010 руСотрагапс!); Если значение переменной, адрес которой передается в первом параметре, со- впадает со значением, передаваемым в третьем параметре, то оно заменяется зна- чением, передаваемым во втором параметре. В 32-разрядном приложении обе функции работают с 32-разрядными значениями. В 64-разрядной программе пер- вая функция оперирует 32-разрядными значениями, а вторая — 64-разрядными. Обе функции возвращают исходное значение переменной, заданной первым па- раметром. Вернемся к нашему приложению Вас1Соип1: (см. листинг 9.3), работающему некорректно из-за одновременного доступа к общей глобальной переменной д_соип1:ег из разных потоков. Имея на вооружении функции с атомарным досту- пом, совсем несложно заставить приложение работать правильно. Для этого нуж- но в реализации функции ТпсСоигйег заменить инструкцию ++д_соиггЕег; вызовом следующей функции: Шег1оске(Ппсгетепи&д_соиЩег); Аналогично, в реализации функции ОесСоигйег необходимо операцию --д_сои1тЕег; заменить следующим вызовом: Шег1оскесЮесгетепи&д_соиЩег); Проверьте, как будет работать на вашем компьютере программа ВадСоипЕ пос- ле указанных изменений в ее исходном тексте. Модифицированное приложение, по-видимому, достойно и нового имени 6оос1Соип1:! Критические секции Критическая секция (спйса1 зесйоп) — это небольшой участок кода, который дол- жен использоваться только одним потоком одновременно. Если в одно время не- сколько потоков попытаются получить доступ к критическому участку, то конт- роль над ним будет предоставлен только одному из потоков, а все остальные будут переведены в состояние ожидания до тех пор, пока участок не освободится. Для использования критической секции необходимо определить переменную типа СКШСА1__5ЕСТ10М: СК1Т1САЕ-8ЕСТ10М С8; Поскольку эта переменная должна находиться в области видимости для каж- дого использующего ее потока, обычно она объявляется как глобальная. Эту пе- ременную следует инициализировать до ее первого применения с помощью функ- ции ТпШаПгеСгШсайесУоп: 1тЧ1а11хеСгП1са18еШоп(&С8); Чтобы завладеть критическим участком, поток должен вызвать функцию Еп^егСпйсаЕЗесйоп: Е1гЕегСгП1са18есЕ1оп(&С8);
Если критический участок не используется в данный момент другим потоком, он обозначается системой как занятый, и поток немедленно продолжает выпол- нение. Если критический участок уже используется, то поток блокируется до тех пор, пока участок не будет освобожден. После вызова ЕпкегСгШсайесУоп следуют инструкции, принадлежащие крити- ческому участку. Конец критического участка обозначается вызовом функции ЕеауеСгШсайесУоп: 1еауеСгП1са18есЫоп(&С8); Как только поток получает контроль над критическим участком, доступ дру- гих потоков к этому участку блокируется. При этом очень важно, чтобы время выполнения критического участка было минимальным. Это позволит добиться наилучших результатов работы приложения. Если критический участок больше не нужен, используемые им ресурсы осво- бождаются вызовом функции 0е1е1:еСп1:1са15ес1:1оп. Кроме перечисленных функций АУтсЕпуз КГ/2000 предоставляет функцию ТгуЕп±егСп±1са15ес!:1ОП, вызывая которую, поток пытается захватить критический участок: ВООЬ ЬАсдьп геб = ТгуЕпШСг1Ша18есШп(&С8); 1Т (ЬАсдсп геб) { // Запись в защищенные переменные } е!зе { // Контроль над критическим участком недопустим } Если критический участок доступен, то поток становится его «владельцем» и осуществляет запись в защищенные переменные. Если участок недоступен, то поток не блокируется, как в случае применения функции ЕпкегСгШсайесУоп, а за- нимается другой работой, за что отвечает ветвь еЕе. Работая с критическими секциями или применяя 1п1ег1оскес1-функции, програм- ма не переключается в режим ядра. Поэтому эти виды синхронизации называют синхронизацией в пользовательском режиме. Она отличается высокой скоростью реализации. Главным недостатком такой синхронизации является невозможность ее применения для потоков, принадлежащих разным процессам. Описываемые ниже примитивы синхронизации основаны на использовании объектов ядра. Но сначала мы должны рассмотреть так называемые жи’Афункции. МаН-функции Многие объекты ядра могут находиться либо в свободном (8Щпа1ес1 зШ1:е\ либо в занятом состоянии (поп8Щпа1е<1 мах'). К таким объектам относятся: □ процессы; □ потоки; □ задания; □ файлы; □ консольный ввод;
□ уведомления об изменении файлов; □ события; □ ожидаемые таймеры; □ семафоры; □ мьютексы. ТЕш’Афункции позволяют потоку в любой момент приостановиться и ждать освобождения какого-либо объекта ядра. Важным свойством функций этого се- мейства является то, что они не тратят процессорное время, пока ждут освобож- дения объекта или наступления тайм-аута. Функция УУа11Рог51пд1еОЬ]ес1 Функция имеет следующий прототип: ОШКО 1л1а11зЕог81 пд!еОЬзесЬ(НАМО1Е ЬНапсПе, ОШКО с1\л/М1111 зесопс!); Когда поток вызывает эту функцию, параметр ННапсНе идентифицирует объект ядра, поддерживающий состояния «свободен-занят». Параметр д^МПНзесопс! за- дает тайм-аут (Ите ои^ — интервал времени, спустя которое функция возвраща- ет управление, даже если объект остается в занятом состоянии. Если параметр дмИтШзесопд имеет нулевое значение, то функция только проверяет состояние объекта и возвращает управление немедленно. Константа 1МЕ1М1ТЕ в качестве зна- чения д\л/М1 Шзесопд задает бесконечное значение тайм-аута. Возвращаемым значением функции чаще всего является одна из следующих констант: Константа Интерпретация \Л/А1Т_ОВЗЕСТ_0 Контролируемый объект ядра перешел в свободное состояние \Л/А1Т_Т1МЕООТ Истек интервал тайм-аута, а контролируемый объект ядра остается в занятом состоянии \Л/А1Т_ЕАИЕО Функция завершилась с ошибкой. Для получения дополнительной информации об ошибке нужно использовать функцию ОеИаз^Еггог Вот пример обработки кода возврата функции МайЕот$1’пд1еОЬ]’ес1:: ОШКО с!\л/ = ШЬЕогЗЩдТеОЬцесШРгосезз, 5000); зт’ЬсЬ (бм) { сазе 1л1А1Т_ОВ5ЕСТ_0: // код обработки для завершившегося процесса Ьгеак; сазе 1л/А1Т_Т1МЕОЬТ: // код обработки, если процесс не завершился в течение 5000 мс Ьгеак; сазе 1л/А1Т_ЕА1ЕЕО: // функция завершилась с ошибкой. Ьгеак; } Будьте осторожны, используя константу 1МЕ1М1ТЕ в качестве второго параметра функции УУа11:Еог51пд1еОЬдес1:. Если объект ННапсНе так и не перейдет в свободное со- стояние, то вызывающий поток никогда не проснется. Единственное «утешение» зак- лючается в том, что тратить драгоценное процессорное время он при этом не будет.
Функция УУаКЕогМи11|р1еОЬ]ес15 Функция имеет следующий прототип: ВШРВ 1л1аПЕогМи1Ь1р1еОЬ0есЬз(ВШРВ пСоыпЬ, С0И8Т НАЖЕ* 1рНапсПез, ВООЕ Т1л1аПА11, ВШРВ О\л/М1111 зесопОз); Она работает так же, как и функция МайЕог$1’пд1еОЬ]’ес1:, но при этом позволяет ждать освобождения сразу нескольких объектов или какого-то одного объекта из заданного списка. Параметр п Со и п! определяет количество интересующих вас объектов ядра. Его значение должно быть в пределах от 1 до МАХ1МиМ_МА1Т_ОВЗЕСТ$. В заголовочных файлах \У1пс1о\\ъ эта константа имеет значение 64. Параметр фНапсНез содержит указатель на массив дескрипторов объектов ядра. В массиве могут содержаться дескрипторы объектов разных типов. Функция МайЕогМи1Вр1еОЬ]’ес1:$ приостанавливает поток и заставляет его ждать ос- вобождения либо всех заданных объектов ядра, либо одного из них. Параметр ЙЛ/айАН определяет поведение функции. Значение ТВОЕ задает режим ожидания освобожде- ния всех указанных объектов, а ЕАЕ5Е — только одного из них. В последнем случае код возврата функции содержит информацию о том, какой именно объект освободился. Возвращаемое функцией значение сообщает, почему возобновилось выполне- ние вызвавшего ее потока. Значения МА1Т_Т1МЕО11Т и МА1Т_ЕА11_ЕО интерпретиру- ются по аналогии с функцией УУайЕог51 пд[еОф'есЕ Если параметр ГМайАИ равен ТВОЕ и все объекты перешли в свободное состояние, то функция возвращает значение МА1Т_ОВЗЕСТ_О. Если же ЙЛ/а11АИ имеет значение ЕА1_8Е, то функция возвращает уп- равление, как только освобождается любой из объектов. При этом ее код возврата лежит в интервале от МА1Т_ОВЗЕСТ_О до МА1Т_ОВЗЕСТ_О + пСоинЕ - 1. Иными слова- ми, если из кода возврата вычесть константу МА1Т_ОВЗЕСТ_О, то вы получите ин- декс освободившегося объекта в массиве ЕрНапсИез. Вот пример обработки кода возврата функции МайЕогМиШф1еОЬ]'ес1:$: НАИВЬЕ ИрЕЗ]; Ьр[О] = ЬРгосеззО; НрЕИ = ИРгосезз1; НрЕ2] = ЬРгосезз2; ВШРВ С1\Л/ = 1л1аПЕогМи1Ыр1еОЬ0есЬз (3, Ер, ЕАЬЗЕ, 4000); змПсИ (бм) { сазе 1л/А1Т_ОВЗЕСТ_0 + 0: // завершился процесс с дескриптором Ьр[О] Ьгеак; сазе 1л1А1Т_ОВЗЕСТ_0 + 1: // завершился процесс с дескриптором Ир[1] Ьгеак; сазе 1л1А1Т_ОВЗЕСТ_0 + 2: // завершился процесс с дескриптором Ьр[2] Ьгеак; сазе 1л1А1Т_Т1МЕОЬТ: // ни один из объектов не освободился в течение 4000 мс Ьгеак; сазе 1л1А1Т_ЕА1ЬЕВ: // функция завершилась с ошибкой. Ьгеак; }
Побочные эффекты успешного ожидания Успешный вызов функции МайЕог$1’пд1еОЬ]'ес1: или МайЕогМи11:ф1еОЬ]'ес1:$ на самом деле меняет состояние некоторых объектов ядра. Успешным считается такой вы- зов, который завершается освобождением соответствующего объекта или объек- тов. При этом функция возвращает значение МА1Т_ОВЗЕСТ_О или значение, являю- щееся смещением относительно МА1Т_ОВЗЕСТ_О. Вызов считается неудачным, если возвращается значение МА1Т_Т1МЕО11Т или МА1Т_ЕА11_ЕО. В этом случае состояние каких-либо объектов не меняется. Пусть, например, поток вызвал функцию \Л/аП:Еог51 пд1еОЬ]ес1 и ждет освобожде- ния объекта «событие с автосбросом» (объекты-события рассматриваются в сле- дующем разделе). Когда объект переходит в свободное состояние, функция обна- руживает это и возвращает значение МА1Т_ОВЗЕСТ_О. Но перед самым возвратом из функции объект-событие будет переведен в занятое состояние. Это и есть по- бочный эффект успешного ожидания. Объекты ядра «событие с автосбросом» ведут себя подобным образом, пото- му что таково одно из правил, определенных МхсгозоЙ для объектов этого типа. Другие объекты дают иные побочные эффекты, а некоторые — вообще никаких. К последним относятся объекты ядра «процесс» и «поток». События Событие (ееепб) — самая простая разновидность объектов ядра. Оно содержит счетчик количества пользователей и две булевы переменные. Одна переменная указывает тип данного объекта-события, а другая — его состояние. События просто уведомляют об окончании какой-либо операции. Объекты- события бывают двух типов: со сбросом вручную (рпаписй-гезеТ: ееепкз) или с авто- сбросом (аико-гезек ееепкз). Первые события позволяют возобновить выполнение сразу нескольких ждущих потоков, а вторые — только одного потока. Объект ядра «событие» создается функцией Сгеа^еЕуепЕ имеющей следующий прототип: НАИОЕЕ СгеабеЕуепП 1Р5ЕСиК1ТУ_АТТК1ВСТЕ5 еуепбАНгэЬыбез, // атрибуты доступа ВООЕ ЬМапыаТВезеб, // тип сброса ВООЕ Ытб1а18бабе, // начальное состояние ЕРСТЗТР рзгИате // имя объекта ); Параметр ЬМапиа1Ке$е1: определяет тип объекта-события. Значение ТК11Е созда- ет событие со сбросом вручную, а значение ЕА1_$Е — событие с автосбросом. Параметр Ыш’1:1’а151:а1:е определяет начальное состояние события — свободное (ТВОЕ) или занятое (ЕА1.5Е). Параметр рзгМате содержит указатель на С-строку, в которой указывается имя объекта. Если рзгМате имеет значение МОП, то создается неименованный объект. Например, следующий вызов функции Сгеа1:еЕ7еп1: из процесса А создает собы- тие с автосбросом и именем ЕуегйМагпе: НАИВЕЕ Юепб = СгеабеЕуепШбЕЕ, ВАЕЗЕ, РАЕЗЕ, "ЕуепШте"); На самом деле все обстоит немножко сложнее. При таком вызове система про- веряет, не существует ли уже объект ядра с таким именем. Если подобный объект существует, то ядро проверяет тип этого объекта. Допустим, что некий процесс В
уже создал ранее объект-событие с таким же именем Еуеп1:Мате. В этом случае система проверяет права доступа процесса А к этому объекту. Если с правами до- ступа все в порядке, то в таблице дескрипторов процесса А создается новая запись с дескриптором Н ЕуепЕ То есть процесс А получает свой дескриптор уже суще- ствующего именованного объекта-события, а счетчик пользователей этого объек- та увеличивается на единицу. Предположим, что имеет место другая ситуация, когда некий процесс В уже создал ранее объект ядра с таким же именем, но другого типа, например семафор. Тогда функция СгеаЕеЕуеп!: вернет значение МОП, а если после этого вызвать функ- цию беФазФггог, то она вернет код ошибки, равный 6 (ЕККОК_1М\/А1_1О_НАМО1_Е). Наконец, самая простая ситуация — когда объект ядра с таким именем ( ЕуепФате) в момент вызова функции СгеаЕеЕуеп!: не существует. Тогда действительно будет создан новый объект ядра «событие» с именем ЕуепФате. Так что будьте аккуратны с именованием объектов ядра и учитывайте, что про- странство имен объектов ядра является общим для объектов всех типов. Объекты-события могут разделяться разными процессами. Допустим, что не- кий процесс А создал новое событие НЕуеп!: с именем ЕуегйМагпе. Потоки из других процессов могут получить доступ к этому объекту несколькими способами: □ вызовом функции СгеаФЕуеп!: с передачей в параметре рзгМате такого же име- ни (эта ситуация только что рассматривалась); □ наследованием дескриптора; □ применением функции ОирПсаЕеНапсНе; □ вызовом функции ОрепЕуегй с передачей в параметре рзгМате такого же имени. Функция ОрепЕуегй имеет следующий прототип: НАИВЕЕ ОрепЕуепП ОШЕО бм0ез1 гебАссезз, // требуемый доступ ВООЕ ЫпИегПНапсПе, // опция наследования ЕРСТ8ТВ рзгИате // имя объекта ); Параметр ПууОезп’гедАссезз определяет требуемый доступ к объекту-событию. Его значением может быть любая комбинация следующих флагов: Флаг доступа Описание ЕУЕМТ_А1_1__АССЕ88 ЕУЕЫТ_МОО11=У_8ТАТЕ Все возможные флаги доступа к объекту-событию Разрешено использование дескриптора объекта в функциях 8е1Еуеп1: и кезе1:Еуеп1: 8У1\1СНкО1\П2Е Разрешено использование дескриптора объекта в любых \л/а^-функциях Функция ОрепЕуегй завершится с ошибкой, если атрибуты доступа, заданные для объекта с именем рзгМате при вызове функции СгеаФЕуепЕ не будут разре- шать доступ, указанный параметром дхл/Оезп’гедАссезз. Параметр ЫпНелЧНапсНе определяет, разрешено ли наследование данного объек- та при создании дочерних процессов. Последний параметр рзгМате указывает на имя существующего объекта-события. Если функция завершается успешно, то возвращаемое значение содержит дес- криптор существующего объекта-события. Если возникла ошибка, то функция возвращает значение МОП. При помощи функции беФаз^Еггог можно уточнить при- чину возникновения ошибки.
Создав событие или получив дескриптор существующего события, вы можете управлять его состоянием с помощью функции ВООЕ 8е-СЕуепЦ(НАМ01_Е ЮегД); которая переводит объект Н Еуеп! в свободное состояние, или с помощью функ- ции ВООЕ ВезебЕуепП НАМОЕЕ НЕуепб); которая переводит его в занятое состояние. Для событий с автосбросом действует следующее правило. Когда ожидание потоком освобождения события успешно завершается, этот объект автоматичес- ки сбрасывается в занятое состояние. Для событий со сбросом вручную автома- тического сбрасывания не происходит, поэтому для возврата в занятое состояние необходимо вызвать функцию Кезе^ЕуепЕ Пример использования объектов-событий приводится в листингах 9.4 и 9.5. Семафоры Объекты ядра «семафор» (зетаркоге) используются для учета ресурсов. Кроме счетчика числа пользователей семафор содержит два 32-битных значения со зна- ком. Одно из них определяет максимальное количество ресурсов, контролируе- мых семафором, а другое используется как счетчик текущего числа ресурсов. Объект ядра «семафор» создается вызовом функции Сгеа1:е5етарНоге: НАМОЕЕ СгеабеЗетарНоге( ЕР5ЕСЫЕ1ТУ—АТТЕ1ВЫТЕ5 зетарНогеАигНЬибез, // атрибуты доступа ЕОИО 11тЧ1а1Соипб, // текущее количество доступных ресурсов Е0М6 1Мах1гмСоипб, // максимальное количество ресурсов ЕРСТЗТЕ рзхМате // имя объекта Например, в результате следующего вызова: НАМОЕЕ НЗет = Сгеабе8етарНоге(М0ЕЕ, 0, 5, "МуЗетарНоге"); будет создан именованный объект-семафор с максимальным числом ресурсов, равным пяти, и изначально нулевым количеством доступных ресурсов. Поток может увеличить значение счетчика текущего числа доступных ресур- сов на величину 1Ке1еа$еСоип1:, вызывая функцию Ке1еа$е5етарНоге: ВООЕ Ве1еа8еЗетарНоге( НАМОЕЕ НЗетарНоге, // дескриптор семафора ЕОМН 1 ВеТеазеСоыпб, // приращение количества доступных ресурсов ЕРЕ0М6 1рРгеу1оы8СоигД // предыдущее значение счетчика ресурсов По адресу, указанному в последнем параметре, функция записывает предыду- щее значение счетчика ресурсов. Если оно вас не интересует, то просто передайте в параметре 1рРгеУ1’ои$Соип1: значение МОП. Как обычно, любой процесс может получить свой («процессозависимый») дес- криптор существующего объекта «семафор», вызвав функцию ОрепЗетарНоге: НАМОЕЕ ОрепЗетарНоге ( ОШВО (Ме81гес1Ассе88, ВООЕ ЫпбегПНапсПе, ЕРСТЗТЕ рзхМате // требуемый доступ // параметр наследования // имя объекта
Параметр дхл/Оезп’гедАссезз определяет требуемый уровень доступа к объекту- семафору. Его значением может быть любая комбинация следующих флагов: Флаг доступа Описание 8ЕМАРНОИЕ_А1_1__АССЕ88 8ЕМАРНОИЕ _МООИ=У_5ТАТЕ Все возможные флаги доступа к объекту-семафору Разрешено использование дескриптора объекта в функции Ие1еа5е8етарНоге 8УМСНИОМ12Е Разрешено использование дескриптора объекта в любых \л/а 11- функциях Если функция завершается успешно, то возвращаемое значение содержит дес- криптор существующего объекта-семафора. Если возникла ошибка, функция воз- вращает значение МОП. При помощи функции СеИазЕЕггог можно уточнить при- чину возникновения ошибки. Поток получает доступ к ресурсу, вызывая одну из \\ай-фу и кци й и передавая ей дескриптор семафора, охраняющего этот ресурс. АУай-функция проверяет у семафора значение счетчика текущего числа ресурсов. Если оно больше нуля (то есть семафор свободен), то значение счетчика уменьшается на единицу, а вы- зывающий поток продолжает выполнение. Очень важно, что эта операция вы- полняется для семафора на уровне атомарного доступа. Иначе говоря, пока \\ ай- функция не вернет управление, операционная система не позволит прервать эту операцию никакому другому потоку. Если май-функция определяет, что счетчик текущего числа ресурсов равен нулю (семафор занят), то система переводит вызывающий поток в состояние ожи- дания. Когда другой поток увеличит значение этого счетчика, система вспомнит о ждущем потоке и снова начнет выделять ему процессорное время. А он, захва- тив ресурс, уменьшит значение счетчика на единицу. Мьютексы Объекты ядра «мьютексы» (тиёех) гарантируют потокам взаимоисключающий доступ к единственному ресурсу. Кроме счетчика пользователей мьютекс содер- жит счетчик рекурсии и переменную, в которой запоминается идентификатор потока-владельца. Мьютексы ведут себя подобно критическим секциям. Однако если критические секции являются объектами пользовательского режима, то мью- тексы — это объекты ядра. Поэтому они позволяют синхронизировать доступ к ресурсу нескольких потоков из разных процессов. Для использования объекта-мьютекса один из процессов должен сначала соз- дать его вызовом функции Сгеа1:еМи1:ех: НАИВЕЕ СгеабеМыбех ( 1_Р5ЕСиК1ТУ_АТТК1ВЫТЕЗ тибехАббнбибез, // атрибуты доступа ВООЕ ЫпШаЮмпег, // флаг наличия потока-владельца ЕРСТЗТР рохИате // имя объекта ); Параметр ЫпйлаЮмпег определяет начальное состояние мьютекса. Если он име- ет значение ЕА1_8Е, то объект-мьютекс не принадлежит ни одному из потоков и поэтому находится в свободном состоянии. При этом его идентификатор пото- ка-владельца и счетчик рекурсии равны нулю. Если же в этом параметре переда- ется значение ТРОЕ, то идентификатор потока-владельца в мьютексе приравнива-
ется идентификатору вызывающего потока, а счетчик рекурсии получает единич- ное значение. Если идентификатор потока-владельца не равен нулю, мьютекс счи- тается занятым. Разумеется, любой процесс может получить свой («процессозависимый») дес- криптор существующего объекта «мьютекс», вызвав функцию ОрепМ[Йех: НАМО1Е 0репМьДех(0ШК0 (М)ез1 гебАссезз, ВООЕ ЫпИегШапсПе, ЕРСТЗТВ рзхМате); Значением параметра дхл/Оезп’гедАссезз может быть любая комбинация флагов: Флаг доступа Описание М11ТЕХ_А1_1__АССЕ88 Все возможные флаги доступа к объекту-мьютексу 8У1\1СНкО1\П2Е Разрешено использование дескриптора объекта в любых мак-функциях, а также при вызове ке1еазеМи1:ех Если функция завершается успешно, то возвращаемое значение содержит дес- криптор существующего объекта-мьютекса. В случае возникновения ошибки функ- ция возвращает значение МОП. Для получения доступа к разделяемому ресурсу поток вызывает одну из \\ ай- функций и передает ей дескриптор мьютекса, охраняющего этот ресурс. АУай-фун- кция проверяет у мьютекса идентификатор потока-владельца. Если идентифика- тор равен нулю, то ресурс свободен и вызывающий поток может продолжить выполнение. В этом случае перед возвратом из \\ ай-функции идентификатор по- тока-владельца в мьютексе становится равным идентификатору вызывающего потока, а счетчику рекурсии присваивается единичное значение. Если май-функция определяет, что идентификатор потока-владельца не ра- вен нулю, то вызывающий поток переходит в состояние ожидания. Система запо- минает это и, когда идентификатор потока-владельца обнуляется (а перед этим обнуляется и счетчик рекурсии), записывает в него идентификатор ожидающего потока, а счетчику рекурсии присваивает единичное значение. После этого ожи- дающий поток вновь становится планируемым. Все проверки и изменения состо- яния объекта-мьютекса выполняются на уровне атомарного доступа. Как только разделяемый ресурс перестает быть нужным, поток, владеющий мьютексом, должен его освободить, вызвав функцию Ке1еа$еМи1:ех. Функция Ке1еа$еМи1:ех уменьшает значение счетчика рекурсии в мьютексе на единицу. Если счетчик рекурсии становится равным нулю, то обнуляется также идентификатор потока-владельца. Система допускает рекурсивный многократный захват мьютекса одним пото- ком. Это происходит, если, уже владея мьютексом и не освободив его, поток вы- зывает \\ ай-фуикцию с дескриптором этого же мьютекса. В этом случае счетчик рекурсии в мьютексе каждый раз увеличивается на единицу. Для освобождения мьютекса количество вызовов потоком функции Ке1еа$еМи1:ех должно совпадать с количеством предыдущих захватов мьютекса. А что будет, если поток, владеющий мьютексом, завершится, не успев его осво- бодить? В таком случае система считает, что произошел отказ от мьютекса, и автоматически переводит его в свободное состояние. Если этот мьютекс ждут другие потоки, то система выбирает один из них и позволяет ему захватить мью- текс. Тогда \\ай-фун кция возвращает потоку значение МА1Т_АВАМ00МЕ0 вместо МА1Т_0ВЗЕСТ_0. Это позволяет понять, что мьютекс освобожден некорректно.
Обычно программы после возврата из луай-функции специально не проверя- ют возвращаемое значение на МА1Т_АВАМООМЕО, поскольку такое завершение по- токов происходит очень редко. Но на стадии отладки приложения может оказать- ся полезным обнаружение ситуаций с некорректным освобождением мьютексов. Обмен данными между процессами Потоки одного процесса не имеют доступа к адресному пространству другого про- цесса. Однако существуют специализированные механизмы для передачи данных между процессами. Обмен данными между процессами (ТМегргосезз Соттитсайоп, 1РС) имеет долгую историю развития вместе с развитием вычис- лительной техники. Последним впечатляющим примером технологической эво- люции в области обмена информацией является создание всемирной паутины (АУогНАУНеАУеЬ). Приведем краткий список механизмов 1РС, встроенных в АУхпйоауз: □ Буфер обмена (сПрЬоагс!) — одна из самых примитивных и хорошо известных форм 1РС. Основная его задача состоит в поддержке обмена данными между программами по желанию и под контролем пользователя. □ Библиотеки динамической компоновки. Когда в рамках ВББ объявляется пе- ременная, то ее можно сделать разделяемой (зйагес!). Все процессы из ВББ, обращающиеся к такой переменной, будут использовать одно и то же место в физической памяти. □ Сообщение ММ_СОРУОАТА, которое применяется для передачи участка памяти другому процессу. □ Разделяемая память (зйагес! шешогу) реализуется при помощи объектов ядра «проекция файла». Этот механизм основан на отображении файлов на опера- тивную память. □ Протокол динамического обмена данными (Вупаппс Ва1а Ехсйап§е, ВОЕ), определяющий все основные функции для обмена данными между приложе- ниями. ВОЕ широко использовался до тех пор, пока МхсгозоЙ не приняла ре- шение использовать ОБЕ, которую затем переименовали в АсПуеХ, в качестве основной технологии взаимодействия программ. □ Удаленный вызов процедур (Кешо1е Ргосебпге Са11, КРС), строго говоря, не является механизмом 1РС. Это скорее, технологическая оболочка, расширяю- щая возможности традиционных механизмов 1РС. Благодаря КРС сеть стано- вится совершенно прозрачной как для сервера, так и для клиента. □ АсПуеХ является универсальной технологией, и одним из ее применений яв- ляется обмен данными между процессами. Специально для этой цели МхсгозоЙ определила стандартный интерфейс Юа1:аОЬ]есЕ А для обмена данными по сети используется ВхзПЬп^ес! Сошропеп! ОЬ)'ес1 Мос1е1 (ВСОМ), которую можно рассматривать как объединение АсПуеХ и КРС. □ Каналы (рхрез) — мощная технология обмена данными. В общем случае ка- нал можно представить в виде трубы, соединяющей два процесса, через кото-
рую идет непрерывный поток данных. Каналы делятся на анонимные (апопутоиз ргрез) и именованные {патеН ргрез). Анонимные каналы используются до- вольно редко. Именованные каналы, которые поддерживаются только в сис- темах АУ1пЫТ/2000, передают произвольные данные и могут работать через сеть. В последнее время вместо именованных каналов все чаще используют сокеты. □ Сокеты (зоске^з) — очень важная технология, так как именно она отвечает за обмен данными в сети Интернет. Сокеты также часто используются в крупных локальных сетях. Сокеты можно рассматривать как «разъемы», представляю- щие собой абстракцию конечных точек коммуникационной линии, которая со- единяет два приложения. АУтскпуз содержит достаточно мощный АР1 для ра- боты с сокетами. □ Почтовые слоты (шахЫо^з) — это механизм однонаправленного 1РС. Если при- ложению известно имя слота, то оно может помещать туда сообщения, а при- ложение, которое является владельцем этого слота, может их оттуда извлекать и обрабатывать. Основное преимущество этого способа заключается в возмож- ности передавать сообщения по локальной сети сразу нескольким компьюте- рам за одну операцию, если, конечно, несколько приемников имеют почтовые слоты с одним и тем же именем. □ МхсгозоЙ Мезза§е Оцепе (М8М0) — обеспечивает посылку сообщений меж- ду приложениями с помощью очереди сообщений. В отличие от других форм 1РС, эта технология позволяет посылать сообщения процессу, который в дан- ное время недоступен, например, если приложение не запущено, сервер вы- шел из строя или сетевой канал связи перегружен. Механизм М8М() ставит сообщение в очередь до тех пор, пока не появится возможность переслать его адресату. В данной книге будут рассмотрены только два механизма обмена данными между процессами: а) обмен с помощью разделяемой памяти (или объекта ядра «проекция файла»); б) обмен при помощи сообщения ММ_СОРУОАТА. Но сначала — несколько слов об архитектуре памяти АУш32. Виртуальная память. Адресное пространство процесса Каждому процессу операционная система выделяет собственное виртуальное ад- ресное пространство. В АУш32 его размер составляет 4 Гбайт, что определяется разрядностью регистра команд. Соответственно, 32-битный указатель может быть любым числом в интервале от 0x00000000 до ОхЕЕЕЕЕЕЕЕ. Таким образом, адре- суется 4 294 967 296 значений, что как раз и перекрывает указанный диапазон памяти. Верхняя половина этого пространства, то есть адреса от 0x80000000 до ОхЕЕЕЕЕЕЕЕ, резервируется за операционной системой, а нижняя половина почти вся доступна процессу. Виртуальное адресное пространство процесса доступно всем потокам этого процесса. С другой стороны, потоки одного процесса не имеют доступа к адресно- му пространству других процессов.
Физическая память и страничный файл Так как на современных компьютерах оперативная память КАМ имеет размеры, по крайней мере, на порядок меньше 4 Гбайт, то система имитирует задеклариро- ванную память за счет дискового пространства. При этом на диске создается стра- ничный файл (раре /Не), который вместе с физической памятью КАМ образует виртуальную память, доступную всем процессам. Другое название страничного файла — файл подкачки (шар /Не). Процессор сам управляет отображением виртуальных адресов из машинных команд в эквивалентные физические адреса в ходе выполнения команды. Осуще- ствляя это отображение, процессор оперирует страницами памяти размером 4 Кбайта. Этот же размер страниц используется АУтсЕпуз для управления вирту- альной памятью. Теперь посмотрим, что происходит, когда поток пытается получить доступ к блоку данных в адресном пространстве своего процесса. В этом случае возмож- ны три ситуации: □ Данные, к которым обращается поток, находятся в оперативной памяти. Тогда процессор проецирует виртуальный адрес данных на физический, и поток по- лучает доступ к этим данным. □ Данные отсутствуют в оперативной памяти, но размещены где-то в странич- ном файле. Попытка доступа к данным генерирует прерывание, называемое Раде Ра и К (Ошибка страницы). Тогда система начинает искать свободную стра- ницу в оперативной памяти. Если такой страницы нет, то система вынуждена освободить одну из занятых страниц. Если занятая страница не модифициро- валась, она просто освобождается. В ином случае она сначала копируется из оперативной памяти в страничный файл. После этого система отыскивает в страничном файле запрошенный блок данных, загружает этот блок на сво- бодную страницу оперативной памяти и, наконец, проецирует виртуальный адрес данных на соответствующий адрес в физической памяти. □ Иногда из-за программной ошибки или сбоя в аппаратной части требуемая страница отсутствует и в оперативной памяти, и в страничном файле. Тогда система генерирует ошибку 1пуаНд Раде ЕаиК, и работающее приложение за- крывается. Архитектура интерфейсов (АР1) управления памятью Диспетчер виртуальной памяти (У1г1па1 Мешогу Мапа§ег — УММ) является со- ставной частью ядра операционной системы. Он отображает виртуальные адреса на физические, используя механизм подкачки страниц памяти (раре шарртрД Кроме того, он предоставляет прикладным программам различные интерфейсы (АР1) для работы с виртуальной памятью: □ ХйгШсй Метогу АР1 — набор функций, позволяющих приложению работать с виртуальным адресным пространством. Например, функции VIг±иа1А11ос и VIгЕиа 1Егее позволяют процессу получать страницы из памяти или возвращать их системе. □ Метогу Маррел! РНе АР1 — набор функций, позволяющих работать с файлами, проецируемыми в память. Это новый механизм, предоставляемый АУтсЬм^з для работы с файлами и взаимодействия процессов.
□ Неар Метогу АР1 — набор функций, позволяющих работать с динамически рас- пределяемыми областями памяти (кучами). Файлы данных, проецируемые в память Проецирование файла данных в адресное пространство процесса предоставляет разработчику мощный механизм работы с файлами. Спроецировав файл на адрес- ное пространство процесса, программа получает возможность работать с ним, как с массивом. Это очень удобно при манипуляциях с большими потоками данных. Для проецирования файла в память необходимо выполнить три операции: 1. Создать объект ядра «файл», идентифицирующий дисковый файл, который вы хотите использовать как проецируемый в память (функция Сгеа±еН1е). 2. Создать объект ядра «проекция файла» при помощи функции Сгеа1еЕ1 1еМарр1 пд. При этом используется дескриптор файла, возвращенный функцией Сгеа1:еН1е. 3. У казать отображение объекта «проекция файла» или его части на адресное про- странство процесса (функция Мар\йедоОГЕ1’1е). Закончив работу с проецируемым в память файлом, тоже следует выполнить три операции: 1. Отменить отображение на адресное пространство процесса объекта «проек- ция файла» (функция 11птарУ1’е\л/ОГЕ1’1е). 2. Закрыть объект ядра «проекция файла». 3. Закрыть объект ядра «файл». Описанную технологию можно проиллюстрировать следующим фрагментом кода: НАМО1Е ИП1е, ИЕ11 еМарр1 пд; Р7010 рАггау: ИП1е = СпеабеП1е("П1е Мате", ... ); ПЕПеМаррЩд = СпеабеП 1 еМаррапд(НЕ11е, ... ); С1 озеНапсПе(МП 1 е) ; рАггау = Мар\/1 е\л/ОГЕ11 е(ИЕ11 еМаррапд, ... ); С1 озеНапсПе(МП 1 еМаррапд) ; // /* Работаем с файлом, как с массивом рАггау */ // Нптар\/1е\л/0ГЕ11е(рАггау); В этом примере «закрывающие» операции выполняются сразу после исполь- зования соответствующего дескриптора объекта. Это уменьшает вероятность утеч- ки ресурсов. Использование проекции файла для реализации разделяемой памяти Самый низкоуровневый механизм совместного использования данных в одной системе — проецирование файла в память. На нем так или иначе базируются все другие механизмы разделения данных. Поэтому, если вы хотите получить макси- мальное быстродействие с минимумом издержек, лучше всего применять именно проецирование.
Совместное использование данных в этом случае происходит по следующей схеме. Несколько процессов проецируют в память представления одного и того же объекта «проекция файла», то есть делят одни и те же страницы физической памяти. Поэтому, когда один процесс записывает данные в представление общего объекта «проекция файла», эти изменения немедленно отражаются в других про- цессах. Но для этого все процессы должны использовать одинаковое имя объекта «проекция файла». В предыдущем разделе рассматривалось проецирование представления фай- ла, размещенного на диске. Но для целей обмена данными между разными про- цессами хранение этих данных на диске было бы очень неудобным. К счастью, АУш32 АР1 предусматривает возможность проецирования файлов непосредствен- но на физическую память из страничного файла, а не из специально создаваемого дискового файла. Этот способ даже проще стандартного, рассмотренного в предыдущем разделе. Во-первых, не нужно вызывать функцию Сгеа1:еН1е. Вы просто вызываете функ- цию Сгеа1еЕ1’1еМарр1’пд и передаете значение 1МУА1_Ю_НАМ01_Е_\/А1_11Е (или константу -1) в параметре НЕйе. Но при вызове функции Сгеа1:еЕ1’1еМарр1’пд следует передать в последнем ее параметре С-строку, содержащую имя этого объекта. Тогда другие процессы, если им понадобится доступ к разделяемой памяти, смогут вызвать функ- цию ОрепЕНеМаррщд и передать ей то же самое имя. Учтите, что если разделяемая память используется в режиме записи данных более чем одним потоком, то вы должны предусмотреть синхронизацию работы этих потоков. Пример такой синхронизации при помощи объектов-событий рас- сматривается чуть ниже. Когда работа с объектом «проекция файла» завершена, процесс должен выз- вать функцию С1озеНапс11е. Как только все дескрипторы объекта будут закрыты, система освободит память, переданную из страничного файла. Модель «клиент-сервер» Чтобы показать применение механизмов обмена между процессами с помощью разделяемой памяти (файла, проецируемого в память) и с помощью сообщения ММ_СОРУОАТА, мы разработаем две программы, имитирующее функции сервера и клиента. Клиентом называется объект, запрашивающий доступ к службе или ресурсу. Сервер — это объект, выполняющий некую службу или обладающий ресурсом. Клиент и сервер могут работать на одной и той же машине, используя локаль- ные механизмы коммуникации, или на разных машинах, применяя для связи се- тевые средства. Поведение клиента и сервера асимметрично. Процесс-сервер инициализиру- ется и переходит в состояние ожидания запросов от возможных клиентов. Как правило, процесс-клиент запускается в интерактивном режиме и посылает зап- росы серверу. Сервер исполняет полученный запрос, причем это может подразу- мевать диалог с клиентом, а может — и нет. Затем сервер вновь переходит в состо- яние ожидания запросов от других клиентов. В рассматриваемом ниже примере клиентом и сервером являются приложе- ния СПегйАрр и 5еп/егАрр. Рисунок 9.3 показывает, как осуществляется взаимосвязь между этими приложениями через локальные механизмы коммуникации.
Рис. 9.3. Взаимодействия клиента и сервера Хотя на рисунке изображен только один клиент, на самом деле их может быть несколько. Максимально возможное число клиентов определяется «мощностью» или пропускной способностью сервера. Сценарий взаимодействия клиента и сервера предусматривает начальную фазу, в которой клиент посылает серверу запрос на связь и получает ответ-уведомле- ние как признак установки связи. После этого начинается рабочая фаза, в кото- рой с некоторой периодичностью клиент записывает свой рабочий запрос в раз- деляемую память и ждет ответа от сервера. Этот ответ тоже записывается в разделяемую память. Синхронизация взаимодействия клиента и сервера осу- ществляется через объекты ядра «события». Таким образом, в рабочей фазе по- вторяются действия, помеченные на рис. 9.3 номерами 3—6. Обмен данными с помощью сообщения УУМ_СОРТОАТА Системное сообщение ММ_СОРУОАТА является, вероятно, самым простым методом обмена данными между процессами. Чтобы послать любое сообщение при помощи функции 5епс1Ме$$аде, необхо- димо знать дескриптор окна, которому оно посылается. Обычно этот дескриптор получают вызовом функции Нпс1\Л/1’пс1о\л/, например: Ыпс18егуег = Ппс11л/1 пс1о\л/(М1Л_1_, "ЗегуегАрр"); С помощью этой инструкции клиентское приложение узнает дескриптор ос- новного окна серверного приложения, имеющего заголовок 5еп/егАрр. Прежде чем посылать сообщение ММ_СОРУОАТА, вы должны определить струк- турную переменную типа С0РУ0АТА5ТК11СТ. Эта структура объявлена следующим образом: 1урес1еГ зШсД ЦадСОРУОАТАЗТКиСТ { 1ЛСЖ_РТР (Ша; ОШРО сЬОа^а; Р7010 ПрОаЦа; } СОРУОАТАЗТРиСТ, ЖОРУОАТАЗТРШ; В поле можно записать 32-битное число, которое будет передано при- ложению-получателю.
Поле 1р0а1а может содержать указатель на данные, передаваемые приложению- получателю. Если [рОаЕа не равно МОП, то поле сЬОаЕа должно содержать размер в байтах данных, на которые указывает [рОаЕа. В зависимости от потребностей приложения вы может использовать оба ука- занных поля (с1\л/Оа1:а и [рОаЕа) или только одно из них. Когда вы посылаете сообщение ММ_СОРУОАТА с помощью функции 5епс1Ме$$аде, адрес переменной типа С0РУ0АТА5ТК11СТ должен передаваться в параметре [Рагат. Например, следующие инструкции в клиентском приложении подготавлива- ют запрос в виде С-строки гериезЕ и посылают его серверному приложению, кото- рое имеет заголовок окна 5еп/егАрр: СОРУ 0АТА8ТЖТ ссС; сбаг гедиезЦЦЗОЦ; зрнпШгедыезб, "СИепбАрр"); ссЕ.1рОаЦа = йгедиезб; ссЕ.сЬОаба = 8бг1еп(гедие8б); ЫпбЗегуег = Ппс11л/1 пс1о\л/(ЖЕ, "ЗегуегАрр"); ЗепбМеззадеСЫпбЗегуег, Ш_С0РУ0АТА, ШРАРАМ)Шпс1, (СРАРАМ)&сбз); Программа, которой адресуется сообщение ММ_СОРУОАТА, должна рассматривать данные, на которые указывает 1р ОаЕа, как данные только для чтения. Указатель 1р ОаЕа является корректным только в процессе обработки сообщения ММ_СОРУОАТА. Если принимающее приложение намерено использовать полученные данные в дальней- шей своей работе, то оно должно скопировать их в локальный буфер. Данные, размещенные по адресу [рОаЕа, не должны содержать никаких указа- телей, поскольку в адресном пространстве принимающего приложения эти ука- затели будут интерпретироваться неверно. Но пора от теории перейти к практике и показать, наконец, нечто работающее. Приложение 5еп/егАрр Исходный код приложения 5еп/егАрр приведен в листинге 9.4. Листинг 9.4. Проект 5еп/егАрр ////////////////////////////////////////////////////////////////////// // ЗегуегАрр.срр #1пс1ыс1е <\л/1 пбомз. Е1> #1пс1ыс1е <8бс11о.Е1> #1пс1ыс1е "К1л1пс1.И" #с1еТ1пе МАХ_8ТЕ_812Е 80 #с!еТ1пе МАХ_И 10 // максимальное количество клиентов 1пб И = 0; // текущее количество клиентов НАМО1Е Е1ЕубЕесТо8егу[МАХ_М]; // массив событий "рабочий запрос от клиента" сбаг еуепбМате[МАХ_М][МАХ_8ТЕ_812Е +8]; // массив имен событий НАИОЕЕ Е1ЕубЗегу18Ех18б; // событие "Сервер уже запущен" НАИОЕЕ ИЕуЕЗегуТзЕгее; // событие "Сервер свободен" НАИОЕЕ ЕОбЗегуТзОопе; // событие "Сервер выполнил рабочий запрос" НАИОЕЕ ИЕПеМар = ШЕЕ; // объект "проекция файла" збгысб ТИгеабМападег { И ЫпбРагепб; }:
ТЬгеасМападег Ьт; // структура для связи с потоком 0И0В0 1л/1МАР1 ТЬгеас1Еипс(ЕР701В); 1РЕЗС1Т САЕЕВАСК ИпбРгос(НШО, С1МТ, НРАВАМ, ЕРАВАМ); //========================================== 1пб ШИАР1 ИппМапп(НIМЗТАМСЕ Шнапсе, Н1М5ТАМСЕ ЬРгеуТпзбапсе, ЕРЗТР 1рСтсИ1пе, 1пЬ пСтс13Ио\л/) { МЗС тзд; Шпс1 тапп1л/пс1 ("ЗегуегАрр", Ыпзбапсе, пСтс18Ьом, ИгИРгос, мин, 100, 100, 450, 100); мЬ11е (СеЬМеззадеС&тзд, МВЕЕ, 0, 0)) { ТгапзШеМеззадеС&тзд); ВтзраЬсИМеззадеС&тзд); } гебигп (тзд.мРагат); } //========================= ЕРЕЗОЕТ САЕЕВАСК ИпбРгос(ШИВ Шпс1, 01ИТ иМзд, НРАВАМ мРагат, ЕРАВАМ 1 Рагат) { НОС ВВС; РА1МТЗТВВСТ рз; СОРУВАТАЗТВВСТ* рСс18; СОРМВАТАЗТВВСТ СС|8; зТаТпс сбаг гедиезТЦМАХ—ЗТВ_317ЕЦ; сбаг зиГПх[8]; НИМО ЫпсЮНегТ; 8ТаТ1 с НАМОЕЕ ЬТЬгеаб; НИМО* рНуупс! = &Шпс1; 1 пЬ 1 ; 8\л/ПсН (иМзд) { сазе ИМ_СВЕАТЕ: НЕуТЗогуТзЕхпзТ = 0репЕуепТ(Е\/ЕМТ_АЕЕ_АССЕЗЗ, ЕАЕЗЕ, "Зегуег"); 1Т (ЬЕуЬЗегу1зЕх1зб) { МеззадеВох(ЬИпс1, "Сервер уже выполняется. 2 экземпляр запрещен.", "ЗегуегАрр", МВ_ОК | МВ_1С0ИЗТ0Р | МВ_ЗТЗТЕММ00АЕ); РозЬОьп ЬМеззаде(О); } е!зе ЬЕуЬ8егу1зЕх1зЬ = СгеабеЕуепШЬЕЕ, ЕАЕЗЕ, ЕАЕЗЕ, "Зегуег"); Ьгеак; сазе ИМ—СОРТОАТА: // прием запроса от клиента на связь с сервером 1Т (И == МАХ_И) { МеззадеВох(НИпс1, "Сервер перегружен. В доступе отказано", "ЗегуегАрр", МВ_ОК | МВ_1С0М8Т0Р | МВ_8У8ТЕММ00АЕ); Ьгеак; } // Извлечение сообщения рСс1з = (СОРУВАТАЗТВЬСТ*)! Рагат; продолжение
Листинг 9.4 {продолжение} зРгпсруСгециезР, (скаг*)рСбз->1 рОаРа, рСбз->сЬОаРа); зРгсру(еуепРМате[М], гециезР): зрг1пРР(зиРР1х, "_%сГ, Ю; зРгсаР(еуепРМате[М], зиРР1х); // Создание события НЕурРесТоЗегу[М] - "запрос И-го клиента" НЕурРесТоЗегу[М] = СгеаРеЕуепР(М1ЕЕ, ГАЕЗЕ, ЕАЕЗЕ, еуепРМатеЕЮ); 1Р (!Ю { // Инициализация массива НЕурРесТоЗегу Рог (1 =0; 1 < МАХ—И; ++1) НЕурРесТоЗегу[1] = НЕурРесТо8егу[0]: // Создание файла, проецируемого в память ЬЕПеМар = СгеаРеЕ11 еМаррппд(IИУАЕ10_НАМ0ЕЕ_УАЕиЕ, ШЕЕ, РА0Е_РЕАШР1ТЕ, 0, 4 * 1024, "ЗЬагесЮаРа"); // Создание событий "Сервер свободен" и "Сервер выполнил рабочий запрос" ИЕуРЗегуТзЕгее = СгеаРеЕуепР(М1ЕЕ, ЕАЕЗЕ, ТЕЕ1Е, "Зегуег1зЕгее"); НЕуРЗегу1з0опе = СгеаРеЕуепР(М1ЕЕ, ЕАЕЗЕ,ЕАЕЗЕ, "Зегуег1з0опе"); // Запуск потока для обработки запросов клиентов Рт.ИмпбРагепР = Шпс1; ЬТНгеаб = СгеаРеТИгеасКМОЕЕ, 0, ТбгеасЕыпс, &Рт, 0, ИЕ1ЕЕ); } // Отправка обратного сообщения клиенту ЫпсЮНепР = Е1 псНд/1 пс1оуу(ИЕ1ЕЕ, гециезР); ссЕ.сЕ/ОаРа = И; сс1з.1 рОаРа = &еуепРМате[М]; ссЕ.сЬОаРа = зРг1еп(еуепРМате[М]); ЗепбМеззадеСЬшбСИепР, ИМ_СОРУОАТА, (ИРАРАМ)Шпс1, (ЕРАЕАМ)&сбз); Ю (И < МАХ-М) И++; 1пуа11с1аРеРесР(Шпс1, ИОЕЕ, ТРОЕ): Ьгеак: сазе 1л/М_РА1МТ: РОС = ВедппРаппР(Шпс1, &рз): 1Р (Ю { сНаг РехР[40]; зрг1 пРТ(РехР, "Количество обслуживаемых клиентов: %сГ, Ю: ТехР0иР(Ь0С, 10, 20, РехР, зРг1еп(РехР)): } Епс1Ра1 пб (Шпс1, &рз); Ьгеак: сазе Ш_0ЕЗТР0У: С1озеНапсП е(ИЕ11еМар): РозЬОыПМеззадеСО): Ьгеак: ЬеСаиИ: геРигп ОеРШпбомРгос(Шпс1, иМзд, мРагат, 1Рагат); } геРигп 0: } //==================================================================== ОМЖ) М^АР1 ТГ|геас1Еипс(1_Р7010 1ру) {
ТИгеабМападег* рТт = (ТИгеас1Мападег*)1ру; Ш ЬРагепб = рТт->Ыпс1РагепЬ; зЬаЫ с Р7010 р71ем; скал 8иГПх[40]; 1пЬ к; скал ЬехШОО]; ОШРО с1\л/: \л/Гп'1е (1) { // Ожидание "рабочего запроса" от клиента Дм = 1л1аПЕолМи1Ыр1еОЬцесб8(МАХ_М, РЕИРесТоЗеп/, ГАЕЗЕ, 1МЕ1М1ТЕ); змПсб (Ом) { сазе 1л1А1Т_ЕА1ЕЕО: МеззадеВохСЬРалепб, "Ошибка вызова 1л1аПЕолМи1Ыр1еОЬцесб8", "ЗелуелАрр", МВ_ОК); лебылп 0; ОеГаиИ: к = Ом - 1л1А1Т_0В0ЕСТ—0; // индекс клиента // Отображаем проекцию файла на адресное пространство процесса р71ем = Мар71емОГЕ11е(ОЕ11еМар, ЕIЬЕ_МАР_ШТЕ, 0, 0, 0); // Извлекаем содержание запроса в буфер Ьехб 8блсру(ЬехЬ, (РТ5ТР)р\/1ем); // Добавляем к нему "суффикс", содержащий имя клиента зрл1пСТ(зыГПх, " - %з\0", еуепЬМате[к]); зблсаШехб, зиТТлх): // Помещаем сформированную запись обратно в проекцию файла збгсру((РТЗТР)р\/1 ем, Ьехб); ОптарVI емОГП 1 е(р\/1 ем); II Освобождаем событие ЬЕуЬЗелуТзОопе ЗеЬЕуепШЕуЬЗелуТзОопе); Ьгеак; } } лебилп 0; } ////////////////////////////////////////////////////////////////////// После старта сервер ждет первого запроса на связь от возможного клиента. Этот запрос поступает в виде сообщения ММ_СОРУОАТА. В коде программы СИегтЬАрр, который приводится ниже, можно увидеть, что клиент посылает в этом сообще- нии С-строку, содержащую его имя (которое совпадает с заголовком главного окна приложения). Сервер извлекает эту строку гедиез! и добавляет к ней суффикс, содержащий символ подчеркивания и номер запроса М. Модифицированная строка запоминается в элементе массива еуеп1:Мате[М]. Затем сервер создает объект-со- бытие с дескриптором НЕу1:КесТо5еп/[М] и именем еуеп1:Мате[М]. Если поступивший запрос является первым и И имеет нулевое значение, то сервер выполняет ряд инициализирующих действий: □ Все элементы массива НЕу1:КесТо5еп/ заполняются значениями дескриптора НЕу1:КесТо5еп/[0]. Если этого не сделать, то функция МайЕогМи1Вр1еОЬ]'ес1:$, вы- зываемая позже для массива событий НЕу1:КесТо5еп/, работать не будет. □ Создается объект «проекция файла» с дескриптором НЕйеМар и именем 5НагесЮа1:а. При этом размер файла, задаваемый четвертым и пятым параметрами функции СтеаЕеЕПеМаррп’пд, равен 4 Кбайт. Эта величина должна быть кратна размеру
страницы памяти. А третий параметр определяет, что проекция файла будет ис- пользоваться и для записи, и для чтения. □ Создаются объекты-события НЕу1:5егу1$Егее и НЕу1:5егу1$0опе. □ Запускается вторичный поток с входной функцией ТНгеас1Еипс. Этот поток пред- назначен для обработки «рабочих запросов» клиентов. Завершая обработку принятого сообщения ММ_СОРУОАТА, сервер отправляет имя еуеп1:Мате[М] обратно клиенту, используя для этого все то же сообщение ММ_СОРУ- ОАТА. Это сообщение является ответом-уведомлением для клиента (см. рис. 9.3). Алгоритм функции потока ТНгеас1Еипс очень прост. Как только сервер обнару- живает освобождение какого-либо события из массива НЕу1:КесТо5егу, он опреде- ляет индекс клиента и приступает к обработке «рабочего запроса». Обратите вни- мание на то, что после записи «ответа» в разделяемую память поток вызывает функцию Зе^ЕуепС чтобы перевести событие НЕу1:5егу1$0опе в свободное состоя- ние. Как мы увидим ниже, клиентское приложение будет ждать этого события, чтобы извлечь ответ сервера из разделяемой памяти. Приложение СПепЗДрр Исходный код приложения СИепЬКрр приведен в листинге 9.5. Создав приложение, добавьте к нему ресурс меню с идентификатором ЮК_МЕМ1И. Меню должно содержать один пункт с именем Ипк ±о зегуег и иденти- фикатором ЮМ_1_1МК. Листинг 9.5. Проект СКегПАрр ////////////////////////////////////////////////////////////////////// // СИепРАрр.срр #1пс1ис1е <\л/1 пбомз. И> #1пс1ис1е <зРс11о.И> #1пс1ис1е "гезоигсе.И" #1пс1ис1е "КМпсНН" #беМпе МАХ_1_ЕМ_^Е0иЕ8Т 80 #беМпе МАХ_ЕЕМ_ЕУТМАМЕ 88 #беМпе Т1МЕВ_Ю 1 #беМпе Т1МЕВ_РЕВЮ0 1000 НАМОЕЕ ИЕурЕесТоЗегу; // событие "Запрос к серверу от клиента" НАМОЕЕ ИЕуРЗегМзЕгее; // событие "Сервер свободен" НАМОЕЕ ИЕубЗегМзОопе; // событие "Сервер выполнил запрос" ЕКЕ80ЕТ САЕЕВАСК ШбРгос(НИМО, Н1ИТ, ПРАВАМ, ЕРАВАМ); //============================================ Ш ШИАР1 ШпМапп(Н1МЗТАМСЕ Щпзбапсе, Н1МЗТАИСЕ КРгеуШбапсе, ЕРЗТВ 1рСтс1Е1пе, Ш пСтсЕбом) { М86 тзд; КИпс! та1п1л1пб("С11епРАрр", бТпзбапсе, пСтсЕбом, ОпбРгос, МАКЕIМТ0Е30ЕЮСЕ(100_МЕМ01), 100, 100, 450, 150); \л/М1е (СебМеззадеС&тзд, МОЕЕ, 0, 0)) { ТгапзШеМеззадеС&тзд); ОтзрабсбМеззадеС&тзд); }
геТигп (тзд.мРагат); } //========================= ЬВЕЗОЕТ САЕЕВАСК Ипс1Ргос(НШВ Шпс1, 1ИМТ иМзд, НРАВАМ мРагат, ЕРАВАМ 1Рагат) { НОС НОС; РА1МТЗТВ0СТ рз; СОРХВАТАЗТВОСТ* рСбз; СОРХВАТАЗТВОСТ сбз; зТаЫс сНаг гедиезТ[МАХ_ЕЕМ_ВЕ00Е8Т]; зТаЫ с сНаг еуепТМате[МАХ_ЬЕМ_Е\/ТМАМЕ]; зТаЫс сНаг ТехШОО]; зТаЫ с сНаг т8дЗепбебЕ256Ц; зТаЫ с сНаг тздВесе] уес1[256]; НШО ЫпОЗегуег; зТаЫс ВООЕ 1зЫпкТоЗегуег; зТаЫс ВООЕ ЬЗегуег1зВопе = ГАЕЗЕ; зТаЫ с НАИОЕЕ НЕПеМар; // объект "проекция файла" зТаЫс Р\/01В р\Ле\л/; ВИОВВ сЕ/О, с1\л/1; зТаЫс 1пТ соипТ = 0; 8\л/ПсН (иМзд) { сазе ИМ_СВЕАТЕ: 1зЕ1пкТоЗегуег = ЕАЕЗЕ; тздЗепбебЕО] = 0; тздВесе] уес1[0] = 0; Ьгеак; сазе ИМ_СОММАМВ: зт’ТсЬ (ЬОИОВВЫРагат)) { сазе ЮМ-ЫИК: // Отправка запроса на связь с сервером зрнпШгедиезТ, "СИепТАрр"); сбз.1рОаба = йгедиезб; ссЕ.сЬОаба = зЬг1еп(гедиезЬ); Ыпс18егуег = Е1 пс11л11 пс1ом(МОЕЕ, "ЗегуегАрр"); 1Г (! Г|\л/пс13егуег) { МеззадеВох(Ы/\1пс1, "Ошибка связи!", "СИепЬАрр", МВ_ОК); Ьгеак; } ЗепбМеззаде(НшбЗегуег, Ш_С0РХВАТА, (АРАРАМ)Ыл!пб, (ЬРАВАМ)&сбз); Ьгеак; } Ьгеак; сазе 1л!М_С0РХВАТА: // Получение ответа от сервера рСбз = (СОРХВАТАЗТВОСТ*)]Рагат; // Имя разделяемого объекта-события зТгпсру(еуепТМате, (сЬаг*)рСбз->1рВаТа, рСбз->сЬВаТа); продолжение
Листинг 9.5 {продолжение} II Дескрипторы разделяемых объектов-событий ЬЕуЬВесТоЗегу = ОрепЕуепб(Е\/ЕМТ_А1_1__АССЕ88, ГАЕ8Е, еуепЬМате); ИЕубЗегМзЕгее = ОрепЕуепб(Е\/ЕМТ_АЕЕ_АССЕ88, ГАЕ8Е, "8егуег1зЕгее"); ЬЕуЬ8егу1з0опе = ОрепЕуепб(Е\/ЕМТ_АЕЕ_АССЕ88, ГАЕ8Е, "8егуег1з0опе"); // Открыть файл, проецируемый в память ЬЕПеМар = ОрепЕп1 еМаррппд(ЕIЕЕ_МАР_АЕЕ_АССЕ88, ЕАЕ8Е, "ЗЬагебОаба"); 8еШпсЫТехб(Шпс1, еуепЬМате); 1зЫпкТо8егуег = ТРОЕ: 1пуа11с1аЬеВесЬ(Шпс1, МОЕЕ, ТРОЕ); ЗеШтег(Шпс1, Т1МЕЕ_Ю, Т1МЕЕ_РЕЕЮ0, МОЕЕ); Ьгеак; сазе 1л/М_Т1МЕР: // Отправка "рабочих запросов" и ожидание ответа от сервера // Ожидание освобождения сервера скл/0 = ИаПЕог81пд1еОЬ)есШЕуЬ8егу1зЕгее, Т1МЕЕ_РЕЕЮ0 / 2); з\л/1ТсН (с!\л/0) { сазе ОА1Т_ОВОЕСТ_0: // Отображаем проекцию файла на адресное пространство процесса рУпе\л/ = Мар71емОСЕ11е(ЬЕ11еМар, Е1 ЕЕ_МАР_ОР1ТЕ, 0, 0, 0); // Записываем содержание "рабочего запроса" зргппТТ((РТ8ТР)р\/1 е\л/, "Ш", ++соипб); // Сообщение для вывода в окно приложения зрг1пШтзд8епс1ес1, "Запрос к серверу: ШШ", (РТ8ТР)р\/1 е\л/); ОптарУпе\л/ОТЕ11 е(р\/1 е\л/); 1пуа11(ШеЕесШШс1, МОЕЕ, ЕАЕ8Е); 8ебЕуепб(ЬЕубРесТо8егу); // освобождаем событие ЬЕубРесТоЗегу // Ожидание события "Сервер выполнил запрос" сЕ/1 = 1л1аПЕог81пд1е0Ь)есШЕуЬ8егу1з0опе, Т1МЕР_РЕРЮ0 / 2); змПсЬ (сЕ/1) { сазе ОА1Т_0В0ЕСТ_0: // Опять отображаем проекцию файла рУпе\л/ = МарУпе\л/ОТЕп1е(ИЕп 1еМар, Е1 ЕЕ_МАР_ВЕАО, 0, 0, 0); // Извлекаем содержание ответа сервера зрг1пШтздРесе1 уес1, "Ответ от сервера: ИШз", (РТ8ТЮрУпе\л/); ОптарУп е\л/ОТЕ11 е(рУ1 е\л/); Ь8егуег1з0опе = ТВОЕ: // освобождаем событие "Сервер свободен" 8ебЕуепб(ЬЕуб8егу1зЕгее); 1пуа110аТеВесТ(Шпс1, МОЕЕ, ЕАЕ8Е): Ьгеак: сазе ОА1Т_Т1МЕ00Т: гебигп 0; сазе ОА1Т_ЕА1ЕЕО: МеззадеВох(Ы/\1пс1, "Ошибка ожидания ЬЕуЬ8егу1з0опе", "СИепбАрр", МВ_ОК); гебигп 0; } сазе ИА1Т_Т1МЕОиТ: геЕигп 0;
сазе ИА1Т_ЕА1ЬЕВ: МеззадеВох(Шпс!, "Ошибка ожидания ЬЕуЬ8егу1зЕгее", "СПепЬАрр", МВ_ОК); гебыгп 0; } Ьгеак; сазе 1л/М_РА1МТ: ЬОС = ВедапРаапЬ(Шпб, &рз); 1Г (1зЫпкТо8егуег) { зрг! пШЬехЬ, "Установлена связь с сервером через событие %з", еуегОате); ТехЬОыШОС, 20, 20, ЬехЬ, збг1еп(ЬехЬ)); ТаЬЬесГГехЬОыШОС, 20, 40, тзд8епс1ес1, збг!еп(тзд8епс1ес1), 0, МОЮ, 20); } 1Г (Ь8егуег1зВопе) { Ь8егуег1зВопе = РАВЗЕ; ТаЬЬесГГехЬОыШОС, 20, 60, тздВесетуеб, зЬг!еп(тздВесе1 уес1), 0, МОЮ, 20); } Епс1Ра1 пЬ(Шпс1, &рз); Ьгеак; сазе 1л/М_0ЕЗТР0У: ОптарVIемОГП 1 е(р\/1 ем); 01 озеНапсПе(ИЕ11 еМар); РозЬОьП ЬМеззаде(О); Ьгеак; ОеГаиЮ: гебигп ОеГШпбомРгос(Шпс!, иМзд, мРагат, 1 Рагат); } гебигп 0; } ////////////////////////////////////////////////////////////////////// Когда приложение запущено, работа клиента начинается после того, как пользователь выполнит команду меню Ипк 1:о зегуег. Обрабатывая эту команду (сазе ЮМ_ПМК), программа посылает серверу сообщение ММ_СОРУОАТА, содержа- щее указатель на строку гедиезВ Получив ответ-уведомление от сервера также через сообщение ММ_СОРУОАТА, клиент выполняет следующие действия: □ извлекает имя разделяемого объекта-события еуегйМате; □ открывает разделяемые объекты-события, получая дескрипторы НЕу1:КесТо5егу, НЕуТЗегуТзЕгее, НЕуТЗегуТзОопе (событие НЕу1:КесТо5егу в дальнейшем клиент ис- пользует, чтобы информировать сервер о готовности «рабочего запроса»); □ открывает существующий объект «проекция файла» с именем 5НагесЮа1:а (сер- вер должен быть запущен раньше клиента); □ заменяет текст заголовка своего окна строкой еуегйМате; □ запускает стандартный таймер с периодом Т1МЕК_РЕК1ОО.
Далее работа программы управляется прерываниями от таймера. Обрабатывая сообщения ММ_Т1МЕК, приложение проверяет с помощью функции \Л/айЕог51пд1еОЬ]ес1, свободен ли сервер. Если да, то в разделяемую память записыва- ется «рабочий запрос», представляющий собой С-строку с номером запроса. Если нет, то приложение ждет освобождения сервера. После успешной записи «рабочего запроса» в разделяемую память программа переводит в свободное состояние объект-событие НЕу1:КесТо5еп/. Именно это собы- тие ожидает сервер. Затем клиент переходит к ожиданию события НЕу^егуЕОопе, которое свидетельствует о том, что сервер выполнил запрос. Как только указанное событие освобождается, полученный от сервера ответ извлекается из разделяемой памяти и выводится в окно приложения. На рис. 9.4 показаны в работе сервер 5еп/егАрр и три клиента С11’еп1:Арр. г ЗегуегАрр Количество обслуживаемых клиентов: 3 Рис. 9.4. Сервер 8еп/егАрр обслуживает трех клиентов СНеп1Арр Заметим, что с целью сокращения объема текста в приведенных программах опущены те проверки кодов возврата, которые должны производиться после вы- зова функций СгеаЕе... и Ореп... . В реальных приложениях вы, конечно, должны предусмотреть подобные проверки. Не забывайте освобождать ресурсы В рассмотренном выше примере сервер обрабатывает запросы клиентов по очень упрощенной технологии. На практике могут применяться более сложные методы обслуживания клиентов. Пусть, например, ваше приложение спроектировано в рас-
чете на предоставление сервисных услуг И клиентам, а реализация каждой услуги требует создания отдельного процесса. По всей видимости, вы объявите массивы структур 8ТАКТ11Р1М ГО и РКОСЕ55_1МЕОКМАТ1ОМ: # беГте И 1000 8ТАШ1Р1МЕ0 81 ЕМ]; РЕ0СЕ88_1МЕ0ЕМАТЮМ рЮТ; 1пб 1Ргос = -1; // индекс обслуживающего процесса Теперь, получив запрос от клиента, сервер может создать отдельный процесс для его обработки: 1РГОС++; 2егоМетогу( &р1[1Ргос], 81хеоГ(р1[1Ргос]) ); 2егоМетогу( &81[1Ргос], 81хеоГ(81[1Ргос]) ); 81[1Ргос].сб = 81геоГ(81); СгеабеРгосе88( М1Л_1_, "8егуРгосе88.ехе", М1Л_1_, М1Л_1_, ЕАЕ8Е, 0, ШЕЕ, ШЕЕ, &81Е1РгосО, &р1[1Ргос]); Здесь 1 Ргос — текущий индекс для создаваемого процесса. Мы опускаем под- робности, связанные с управлением этим индексом. Например, при достижении максимально допустимого значения М-1 с ним надо что-то делать (в простейшем алгоритме следующим значением 1 Ргос должен быть 0). Но в дальнейшем сервер должен проверять состояние запущенных ранее про- цессов и в случае их завершения сразу же освобождать задействованные ресурсы. Такую проверку можно осуществить, например, с помощью следующего кода, ко- торый вызывается по прерыванию от таймера: 0ШЕ0 (Е/Ех1 бСобе; Гог (1 =0; 1 < М; ++1) { 1Г (р1Е11.ИРгосе88) { 6ебЕх1бСос1еРгосе88 (р1 [1] ,МРгосе88, &сЕ/ЕхПСос1е); 1Г (сЕ/ЕхПСобе != ЗТ1 ЕЕ_АСТ1 УЕ) { С1озеНапб!е(р1[1].МТМгеаб); С1 оаеНапб!е(р1Е11.ИРгосе88); р1Е11.ИРгосе88 = 0; } } } Обработка ошибок здесь опущена. Вызов функции СеЕЕхИСодеРгосезз позволя- ет определить, завершился ли процесс с дескриптором р1 [1] .Н Ргосезз? Если да, то корректное освобождение ресурсов обеспечивается вызовами функции С1о$еНапсНе для дескриптора основного потока и дескриптора завершившегося процесса. Когда многопоточность реально полезна? «Потоки — вещь невероятно полезная, когда ими пользуются с умом» — это цита- та из Джеффри Рихтера [5]. В то же время бездумное применение многопоточно- сти в любой программе может привести к получению совершенно неожиданных результатов. Например, вы решили повысить производительность приложения за счет распараллеливания вычислений и сделали для этого несколько потоков. Возможно, что на многопроцессорной машине программа действительно станет работать быстрее, но на обычном однопроцессорном персональном компьютере
ее характеристики ухудшатся, так как система будет тратить процессорное время на переключение между потоками. Приведем несколько примеров известных приложений, работающих в много- поточном режиме: □ Текстовые процессоры принимают ввод от пользователя, проверяют его на ор- фографические ошибки и печатают в фоновом режиме. □ \Л/1’пс1о\л/8 Ехр1огег может копировать файлы из одной папки в другую и одновре- менно предоставляет пользователю другие функции. Например, можно просмат- ривать содержимое других папок или запускать любое другое приложение. □ Веб-браузеры способны взаимодействовать с серверами в фоновом режиме. Благодаря этому пользователь может перейти на другой сайт, не дожидаясь, когда будет получена вся информация с текущего ресурса. Во всех этих приложениях имеется разделение на несколько относительно независимых задач, которые могут выполняться разными потоками, имеющими различный приоритет. В примере с веб-браузером выделение ввода-вывода в от- дельный поток обеспечивает «отзывчивость» пользовательского интерфейса приложения даже при интенсивной передаче данных. Потоки, используемые в приложениях АУш32, могут быть двух типов: □ потоки пользовательского интерфейса (изег-т^егфасе Лгеабз)} □ рабочие потоки (хоогкег бъгеабз). Потоки первого типа позволяют работать с ними при помощи механизма сооб- щений и имеют в своем составе оконную процедуру. Типичным примером такого потока является первичный поток АУшбом^-приложения, содержащий кроме вход- ной функции \Л/1пМа1П еще и оконную функцию \Л/пс1 Ргос. Потоки второго типа, напротив, сообщения обрабатывать не могут и применя- ются обычно для фонового выполнения задач. В качестве примера можно привести поток с входной функцией ТНгеадРипс в рассмотренном выше приложении 5еп/егАрр. Чаще всего приложению достаточно иметь один поток пользовательского ин- терфейса для взаимодействия с пользователем. Необходимость использования вторичных рабочих потоков определяется, исходя из решаемой задачи. Напри- мер, если ваша программа должна принимать в фоновом режиме данные из двух СОМ-портов, стоит организовать два рабочих потока для решения этих задач. Несколько потоков пользовательского интерфейса в одном процессе можно обнаружить в таких приложениях, как \ЛНпс1о\л/8 Ехр1огег. Оно создает отдельный поток для каждого окна папки. Кроме параллельного решения различных задач, о котором говорилось выше, такая архитектура повышает надежность приложе- ния. Если какая-то ошибка в Ехр1огег приводит к краху одного из его потоков, то прочие потоки остаются работоспособны. Не забывайте, что если вы решили сделать ваше приложение многопоточным, то необходимо очень тщательно продумать вопросы синхронизации работы потоков. В заключение следует привести еще одну цитату из Джеффри Рихтера: «...Мно- гопоточность следует использовать разумно».
Таймеры и время При решении некоторых задач программа должна отслеживать текущее время или выполнять какие-либо действия с определенной периодичностью. Например, эта проблема возникает в приложениях, имитирующих аппаратуру, работающую в реальном масштабе времени, в игровых и мультимедийных приложениях, а так- же при проведении различных тестов. Кроме того, иногда требуется отладить кри- тичные ко времени исполнения фрагменты кода, для чего нужен «хронометр» с высокой разрешающей способностью. \Уш32 АР1 содержит как функции для измерения текущего времени, так и функ- ции для создания виртуальных таймеров — устройств, извещающих приложение об истечении заданного интервала времени. Для успешного применения этих про- граммных средств необходимо учитывать их разрешающую способность и потен- циальную точность измерения. Важно понимать, что многозадачная операционная система АУхшАом^з не являет- ся системой реального времени, поэтому любой виртуальный таймер в У/тскпуз не может гарантировать какой бы то ни было фактической точности отсчета временно- го интервала. Ведь в любой момент времени система может прервать выполнение вашего приложения, чтобы дать возможность поработать другому приложению (про- стой, вызванный прерыванием, чаще всего длится от 1 до 30 мс1). Вероятность та- ких прерываний тем ниже, чем меньше рассматриваемый временной интервал и чем меньше других программ работает одновременно с вашим приложением. В то же время, как показывают эксперименты, мультимедийный таймер У/тскпуз обес- печивает вполне приемлемую фактическую точность отсчета временных интерва- лов для многих задач. В этой главе кроме функций \Уш32 АР1 мы рассмотрим также использование ассемблерной команды гсЙзс для реализации «хронометра» с высокой разрешаю- щей способностью. Время УУтс1ош$ Время \\йпс1о1С8 — это количество миллисекунд, прошедших с момента старта опе- рационной системы. Этот формат времени поддерживается для обратной совме- 1 Указанная здесь нижняя граница весьма приблизительна и относится к тому случаю, когда систем- ный диспетчер потоков выделяет очередной квант этому же приложению.
стимости с 16-разрядными версиями АУшболуз. Время АУтскпуз хранится в виде 32-разрядного целого числа без знака, которое сбрасывается в нулевое значение после того, как АУшсЬауз проработает примерно 49,7 дней. Операционная система управляет временем АУшсЬауз через прерывания сис- темного таймера, добавляя к текущему значению времени \Ушс1оауз приращение, равное периоду работы системного таймера. Кроме того, система периодически синхронизирует время АУшсклуз с показаниями часов реального времени, то есть с системным временем, рассматриваемым ниже. Системный таймер АУшсклуз — это программное устройство, находящееся под управлением операционной системы (в отличие от аппаратного таймера, с кото- рым работали программы под управлением М8-ВО8). Обычно период прерываний системного таймера составляет 10 или около 15 мс в зависимости от аппаратной платформы. Точное значение этого периода, называе- мое также разрешением системного таймера, можно получить с помощью функции 6е15у$1етТ1’теАф’и$1:теп1:, имеющей следующий прототип: В001 6е^8у8^етТ1теАс1аи8Шеп^(Р0ШЕ0 1рТ1теА(Ш8ШегД, Р0ШЕ0 1рТ1те1псгетегД, РВ001 1 рТ 1 теАбэ изЦпепШ 8аЫ еб); Эта функция предоставляет информацию, относящуюся к синхронизации сис- темного времени и времени АУшсЬауз. При этом значение, возвращаемое через второй параметр, как раз равно периоду прерывания системного таймера, выра- женному в 100-наносекундных единицах. Например, для компьютера, на котором тестировались программы, приводи- мые в данной книге (с процессором 1п1е1 Се1егоп СРП 2,0 ГГц и операционной системой МхсгозоЙ АУшсЬауз 2000), разрешение системного таймера, полученное с помощью функции 6е15у$1етТ1’теАф'и$1:теп1:, равно 15,625 мс. Для получения текущего значения времени АУшсЬауз предназначена функция БеГПскСоипГ Функция возвращает число миллисекунд, прошедших с момента стар- та системы. Точность этого измерения определяется разрешающей способностью системного таймера. Системное время Системное время в АУшсЬауз содержит информацию о текущих дате и времени и представляет собой так называемое ПТС-время (Птуегза! Тхте Соогсйпа1ес1). Время в формате ПТС основывается на среднем времени по Гринвичу. Системное время может быть получено при помощи функции 6е15у$1:етТ1те: 7010 0е08у80етТ1те(0Р8У8ТЕМТ1МЕ 1р8у80етТ1те); Функция записывает результат в структуру типа $У$ТЕМТ1МЕ, адрес которой задается параметром 1р5уз1:етТ1’те. Структура типа $У$ТЕМТ1МЕ содержит поля для года, месяца, дня недели, дня, часов, минут, секунд и миллисекунд. Так как системное время отсчитывается по Гринвичу, то, скорее всего, оно не совпадает с местным временем, которое отображается на панели задач. Получить значение местного времени можно при помощи функции СеИосаШте, которая воз- вращает информацию в том же формате, что и функция 6е15у$1:етТ1те. Если вы считаете, что ваше приложение может изменять системное время, то это можно осуществить при помощи вызова функции 5е15уз1:етТ1’те или 5еПоса1Т1’те. В неко-
торых случаях может оказаться полезной пара функций, работающих с информа- цией о часовом поясе, — беШте/опеТпГогтаУоп и 5е1Т1’те/опе1пГогта1:1’оп. Системное время считывается с часов реального времени, встроенных в компь- ютер и имеющих автономное питание, в момент запуска \У1пс1оау8. Затем опера- ционная система обеспечивает приращения системного времени, используя пре- рывания системного таймера, аналогично управлению временем АУтскпуз. Таким образом, точность измерения времени с помощью беЙуз^етПте также определя- ется разрешением системного таймера. Измерение малых временных интервалов Для оценки разрешающей способности программных средств работы со време- нем, предоставляемых операционной системой, очень важно иметь какой-то ин- струмент, позволяющий измерять время выполнения некоторого участка кода (в пределе — одной команды ассемблера или одной инструкции языка С++). Функции считывания текущего времени беГПскСоип!: и беЙуз^етПте, с кото- рыми мы уже познакомились, теоретически, могут быть использованы для про- филировки1 некоторого фрагмента программы. Например, можно использовать следующий фрагмент кода: Ш Н = 6е1зТ1 скСоиггЬ(); // ... профилируемый участок кода Ш 12 = Се-ЬТп скСоип1о(); Ш е! арзесГГпте = 12 - Н: Но разрешение этих функций определяется разрешением системного таймера и для нашего компьютера, например, равно 15,625 мс. Это очень большая величи- на, так как при тактовой частоте процессора порядка 2 ГГц за это время могут быть выполнены сотни тысяч машинных команд. Для более точных измерений малых интервалов следует использовать либо счетчик монитора производительности (ОиегуРегГогтапсеСоигйег), либо счетчик меток реального времени, доступ к которому реализован при помощи ассемб- лерной команды гсЙзс. Использование счетчика монитора производительности АУЙ132 АР1 содержит две функции для работы с высокоточным счетчиком мони- тора производительности. Функция ОиегуРегГогтапсеЕгедиепсу возвращает коли- чество приращений в секунду для этого счетчика: В001_ ОиегуРегГогтапсеЕгериепсу(ЕАЕ6Е_1МТЕ6ЕЕ* 1рЕгериепсу); Если счетчик монитора производительности не поддерживается системой, то функция вернет нулевое значение. Если счетчик все же поддерживается, то его частота записывается в 64-разрядную переменную типа 1_АК6Е_1МТЕ6ЕК по адресу 1 Профилировкой называют измерение производительности как всей программы в целом, так и от- дельных ее фрагментов.
[рЕгедиепсу. Тип 1_АК6Е_1МТЕ6ЕК определен как объединение структуры из двух 32-разрядных полей Еохл/РагТ, Н1’дНРагТ и 64-разрядного поля ОиадРаН:: 1урес1еТ итоп _ЕАЕ6Е_1 МТЕСЕК { зиисД { ОШЕО ЕомРагА; ЕСЖ ЖдИРагА; }; ЕСЖЕСЖ ОиабРаг-Е; } ЕАР6Е_1МТЕ6ЕР; Если компилятор не имеет встроенной поддержки 64-разрядных знаковых це- лых величин, то можно использовать поля Еохл/РагТ (младшие 32 разряда) и Н1 дН Раг± (старшие 32 разряда). Вторая функция, ОиегуРегЕогтапсеСоигйег, возвращает текущее значение счет- чика монитора производительности: ВООЕ 0иегуРегТогтапсеСоип0ег(ЕАВ6Е_1МТЕ6ЕВ* ТрРегЕогтапсеСоиггЕ); записывая его по адресу [рРегГогтапсеСоигй. Для профилировки некоторого участка кода необходимо определить пере- менные ЕАВ6Е_1МТЕ6ЕВ Тгед; ЕАВ6Е_1МТЕ6ЕВ соыгД_и: ЕАВ6Е_1МТЕ6ЕВ соигД_02: После этого следует получить значение частоты счетчика Егер: ОиегуРегЕогтапсеЕгедиепсу(&Егед); Сама профилировка выглядит следующим образом: ОиегуРегЕогтапсеСоиггЕег (&соипО_Ш; II... профилируемый участок кода 0иегуРегТогтапсеСоип0ег(&соип0_02); ОоиЫе с10 = соип0_02.0иас1Раг0 - соыЩ_Н.ОиабРагЦ; ОоиЫе е!арзес1Т1те = 1000 * сПз / Тгер. ОиабРагЦ: // время выполнения в миллисекундах Какова разрешающая способность счетчика монитора производительности? Она определяется его частотой Ггед.Оиас1 Раг1. Наши эксперименты на разных ком- пьютерах показали, что независимо от тактовой частоты процессора часто- та Ггед.Оиас!РагЕ равна 3 579 545 тиков в секунду. Таким образом, промежуток времени между двумя тиками равен 1/3579545 с, что составляет 0,279 мкс или 279 нс. Сравните эту разрешающую способность (0,279 мкс) с разрешающей способ- ностью функции 6е1Т1’скСоип1: (15 625 мкс), чтобы почувствовать разницу. Однако в процессорах семейства 1п1е1 РепПпш1 появилась возможность еще более точного измерения малых интервалов времени, основанная на использова- нии счетчика меток реального времени. Использование команды НОТ5С Начиная с РепПпш III, процессоры этого семейства содержат доступный для про- граммистов счетчик меток реального времени Т8С (Тхше 81ашр Сопп1ег). Это 1 А также его клонов, таких, как, например, 1нЕе1 Се1егон, АМБ, У1А и т. д.
64-разрядный регистр, содержимое которого инкрементируется с каждым тактом процессорного ядра. Каждый раз при аппаратном сбросе (сигналом КЕ8ЕТ) от- счет в счетчике Т8С начинается с нуля. Разрядность регистра обеспечивает от- счет времени без переполнения в течение сотен лет. Команда гсЙзс (геас! Нте з!ашр сопп1ег) возвращает количество тактов с мо- мента запуска процессора, помещая результат в пару регистров общего назначе- ния ЕОХ:ЕАХ. Функция на языке С++ может использовать эту команду следую- щим образом: ыпз1дпес1 __1 гД64 бе^Сус! еСоигД(уо1 б) { -38171 Г(Д8С } Если ваш компилятор не «понимает» команды гсЙзс, используйте ее машинное представление: —38171 _етП 0x0Е -88171 _етП 0x31 Так как частота работы у разных процессоров может различаться и колеблется в настоящее время от 60 МГц до 3 ГГц, то для измерения временных интервалов счетчик нужно отградуировать с помощью стандартных функций измерения вре- мени ОС, например при помощи функции 51еер. Эта функция будет рассматри- ваться в разделе «Программирование задержек в исполнении кода». Следующий фрагмент кода иллюстрирует механизм градуировки счетчика Т8С: ип81’дпес1 _1 гД64 8ЦагЦ; ип81дпес! __1 гД64 8Цор; ^_8^зг^ = беКус1ебоигД(); 81еер(1000); ^_8^ор = бе^Сус1еСоипП) - Р_8^згР - оуегПезс!; боиЫе пСус1еРег1т1сго8ес = ^_8^ор / 1000000.; Здесь функция 51еер приостанавливает выполнение потока на 1000 мс. Пока- зания счетчика Т8С фиксируются определенной выше функцией 6е1Сус1еСоип1: перед вызовом функции 51еер и после возврата из нее. Разница этих показаний, запоминаемая в переменной 1_81ор, показывает количество машинных тактов, про- шедших за одну секунду. Переменная оуегНеас! учитывает «накладные расходы», связанные с выполнением функции 6е1Сус1еСоип1, и должна быть вычислена заб- лаговременно. Получив величину 1_81ор, можно вычислять различные калибро- вочные коэффициенты, например коэффициент пСус1еРег1пйсго5ес, определяющий, сколько тактов содержится в одной микросекунде. После вычисления калибровочных коэффициентов профилировка некоторо- го участка программы осуществляется следующим образом: ^_8^зг^ = (беЦСус! еСоипЦ(); // ... профилируемый учзсток кодл ^_8^ор = бе^Сус1ебоипШ - б_8бзг± - оуегНеас!; боиЫе еТарзесГИте = ^_8^ор / пСус1еРег1гп1сго8ес; // время выполнения в микросекундзх Однако мы до сих пор не пояснили, что скрывается за таинственной поправоч- ной величиной оуегНеас!. Прежде чем сделать это, обратим ваше внимание на то, что профилировка не- больших фрагментов программы сопряжена с рядом серьезных и не всегда оче- видных проблем, незнание которых может привести к грубым ошибкам. Подробно
эти вопросы рассматриваются в книге К. Касперски [8]. Дело в том, что основу архитектуры любого процессора составляет конвейер, на котором выполняются микрооперации. Конвейер позволяет существенно повысить быстродействие про- цессора. Но для конвейерной системы такое понятие, как «время выполнения одной команды», весьма условно. Конвейер принято характеризовать двумя па- раметрами: «пропускной способностью» и «латентностью». Пропускная способность — это количество инструкций, выполняемых за один такт при условии их непрерывного продвижения по конвейеру. Латентность — это полное время прохождения одной команды по конвейеру. Продвижение ма- шинных инструкций по конвейеру сопряжено с рядом принципиальных трудно- стей: то не готовы операнды, то занято исполнительное устройство, — и в каждом таком случае конвейер простаивает. При выполнении команд ветвления ситуа- ция еще более неопределенная, так как конвейер заполняется микрооперациями в предположении, что вычисления пойдут по одной из ветвей. Впоследствии это предположение может оказаться ложным, и тогда процессор вынужден вернуть- ся к точке ветвления. В лучшем случае время выполнения одной инструкции определяется пропуск- ной способностью конвейера, а в худшем — его латентностью. Длина конвейера в современных процессорах колеблется от 12 до 36 стадий. Опустив другие под- робности, обсуждаемые в издании [8], приведем заключительную рекомендацию. Минимальный промежуток времени, которому еще можно верить при использо- вании команды гсНзс, составляет по меньше мере от пятидесяти до ста тактов. Другая тонкость, которую надо учитывать при профилировке, — это режим компиляции. Напомним, что в среде Ухзпа! 81псйо 6.0 этот режим определяется конфигурацией проекта. Возможны две конфигурации: отладочная (АУт32 ВеЬп§) и выпускная (АУш32 Ке1еазе). Так вот, если вы занимаетесь профилиров- кой, то используйте только выпускную конфигурацию! Иначе результаты будут искажены, поскольку в код отладочной конфигурации компилятор вставляет до- полнительные отладочные инструкции. Вернемся к вопросу определения поправки оуегНеаф с помощью которой учи- тываются «накладные расходы» в реализации функции 6е1Сус1еСоип1:. Дело в том, что если выполнить фрагмент кода = 6е^Сус1еСоыпШ; // профилируемый участок кода отсутствует ^_з^ор = 6е^Сус1еСоипШ - то мы получим ненулевое значение 1_81ор. Казалось бы, полученное значение и надо присвоить переменной оуегНеас!. Но при многократных испытаниях при- веденного фрагмента обнаруживается, что результаты его выполнения почти всег- да будут разными. Так, для нашего процессора были получены значения 88, 100, 84, 320. Такое непостоянство, по-видимому, связано с теми особенностями функ- ционирования конвейера, о которых говорилось выше. При этом ясно, что мини- мальное значение, в данном случае 84, и есть истинное значение, так как никакие побочные эффекты не могут привести к ускорению работы программы. Таким образом, для правильного определения величины оуегНеас! необходимо повторить указанный выше фрагмент несколько раз и выбрать наименьшее зна- чение. Эксперименты показали, что достаточно сделать десять таких замеров для достижения устойчивого результата.
На точность градуировки счетчика Т8С влияет еще один фактор. Это погреш- ность отсчета эталонного интервала времени с помощью функции 51еер. Как бу- дет показано в следующем разделе1, наибольшей точности отсчета можно достичь, если предварительно выполнить две инструкции: теВед1 пРег1 ос1 (1); 81еер(20); а уже после этого выполнять код для градуировки счетчика, приведенный выше. Функция НтеВед1’пРелос1(1), согласно спецификации в М8ВН устанавлива- ет разрешение системного мультимедийного таймера, равное 1 мс. И как пока- зали эксперименты, этот вызов влияет также и на работу функции 51еер. После окончания градуировки в указанном режиме необходимо вызвать функцию НтеЕпс1Релос1(1), чтобы освободить системные ресурсы, используемые для под- держания высокоточного разрешения. Для применения функций НтеВедтпРелос! и НтеЕпс1Релос1 необходимо добавить в начало файла следующую директиву: #1пс1ис1е <Мт8у8Рет.Р> а также подключить к проекту мультимедийную библиотеку мптт.НЬ2. Изложенные выше соображения учтены при разработке класса КТп’тег, объек- ты которого могут использоваться как высокоточные «хронометры». Исходный текст класса КП тег приведен в листинге 10.1. За основу взят код одноименного класса из [6], но здесь он доработан, чтобы повысить точность калибровки и пре- доставить разработчику более удобный сервис. В файле татп.срр рассматриваемого приложения осуществляется тестирова- ние класса КП тег. Листинг 10.1. Проект КПтег ////////////////////////////////////////////////////////////////////// // КПтег. И #1Рпс1еР КТ1МЕК-Н #с1еПпе КТ1МЕЕ_Н #ргадта магтпдСсПзаЫе : 4035) Сурес1еТ епит { Маес, Саес, М8_100, М8_10, Илес } ПтеЮтР: 8РаР1с 1пР еР[10]; 1л11пе ил81длес1 _1 пР64 беРСус! ебоипР(уо1 б) { _а8ГТ1 ГС1Р8С } с1а88 КПтег { __1 пР64 Р_8РагР; __1 пР64 Р_8Рор; 1 пр оуегНеаб; // коэффициенты-делители боиЫе пСус1еРег1папо8ес; боиЫе пСус1еРег10папо8ес; боиЫе пСус1еРег100папо8ес; продолжение & 1 «Программирование задержек в исполнении кода». 2 Для У18на1 81псНо 6.0 нужно использовать команду меню РгодесР ► ЗеНп'пдз..., перейти на вкладку 1лпк, после чего ввести имя в текстовое поле ОЬз’есР/НЬгагу тос!и1.е$.
Листинг 10.1. {продолжение} боыЫе пСус1еРег1ш1сго8ес: боыЫе пСус! еРег1т1111бес; боыЫе Ыуыог; // текущий делитель рыЫ 1с: КТ1тег(уо16) { // Калибровка оуегбеаб оуегбеаб = 0: Тог (1пб 1 =0: 1 <10: ++1) { б_збагб = бебСус!ебоыпбО: еб[1] = бебТ1ск(): } оуегбеаб = еб[0]: Тог (1 =1: 1 <10: ++1) 16 (еб[1] < оуегбеаб) оуегбеаб = еб[1]: // Калибровка делителей 61теВед1пРег1об(1): 31еер(20): 8багб(): 81еер(1000); б_збор = бебСус!еСоыпб() - б_збагб - оуегбеаб: пСус!еРег1папо8ес = б_збор / 1000000000.: пСус!еРег10папо8ес = б_збор / 100000000.: пСус!еРег100папо8ес = б_збор / 10000000.: пСус1еРег1т1сгоЗес = б_збор / 1000000.: пСус!еРег1гп1111 Вес = б_збор / 1000.: 61теЕпбРег1об(1): 61у120г = пСус1 еРег1гп11113ес: // делитель по умолчанию } уо16 Зе6бп16 (Т1 тебп16 ытб) { 8\а/16сИ (ып16) { сазе Мзес: б1у1гог = пСус1еРег1т11113ес: Ьгеак: сазе бзес: 61 уыог = пСус!еРег1т1сго8ес; Ьгеак: сазе Мзес: 61у1гог = пСус1еРег1папо8ес: Ьгеак: сазе ИЗ—10: 61у1гог = пСус1еРег10папо8ес: Ьгеак: сазе ИЗ—100: 61у1гог = пСус1еРег100папо8ес: Ьгеак: } } 1п11пе уо16 86аг6(уо16) { 6_з6аг6 = бебСус!еСоыпб(): } 1п11пе 1пб бебТ1ск(уо16) { гебыгп (1пб)(бебСус!еСоыпб() - 6_з6аг6 - оуегбеаб): } 1п11 пе боыЫе бебТ1те(уо16) { 6_з6ор = бебСус!еСоыпбО - б_збагб - оуегбеаб: гебыгп б_8бор / 61у12ОГ; } 1п11пе 1пб бебТ1скРег6зес(уо16) { гебыгп (1пб)пСус1еРег1т1сго8ес: } 1п11пе уо16 ы0е!ау(1пб ы8ес) { 1пб 6Е1арзеб = 0: 8еббп16(бзес):
ЗбагбО; \л/Гп1е ( ТЕПарзеб < и8ес) ТЕПарзеб = (1пТ)6еТТ1те(); } 1п1: беШегНеасК) { ге^игп оуегНеас!: } }; #епсПГ /* КТ1МЕк_Н */ ////////////////////////////////////////////////////////////////////// // Ма1п.срр #1пс1ибе <\л/1 пбо\л/з. б> #1пс1ибе <МтзузТет. 1а> #1пс1ибе <бзбгеат> #1пс1ибе <1отатр> изапд патезрасе зТб: #1пс1ибе "К1л/пб.б" #1пс1ибе "КТ1тег.И" КТ1тег 61тег1; КТ1тег 61тег2; КТ1тег 61тегЗ; #беТ1пе И 5000000 1пб бТ1скз[М]; // число тактов счетчика ТЗС при профилировке нулевого участка боиЫе дгозз1л1огк0иапб[М]; // кванты времени работы приложения (брутто) обзбгеат 61од; ЕЕЕЗбЕТ САЕЕВАСК ШбРгос(НШ0, 611\1Т, АРАРАМ, ЕРАЕАМ); //=============================== 1пб ШМАР1 ШпМапп(Н1М8ТАМСЕ Шзбапсе, Н1М5ТАМСЕ ТРгеуТпзбапсе, ЕРЗТЕ 1рСтбЕ1пе, 1пб пСгткОом) { МЗС тзд; КМпб та1п1л!пб("Тестирование класса КТ1тег", Мпзбапсе, пСтсЕбом, МпбРгос); \л/М1е (бебМеззадеС&тзд, М1)1_1_, 0, 0)) { ТгапзШеМеззадеС&тзд); ОтзраТсИМеззадеС&тзд); } геТигп тзд.мРагат; } /========================= ЕЕЕЗбЕТ САЕЕВАСК ШбРгос(НШВ Шпб, 611\1Т иМзд, ПРАВАМ мРагат, ЕРАВАМ 1 Рагат) { НОС ВВС; РА1МТЗТЕ6СТ рз; 1пТ 1, Ио = 0; сбаг Техб[200]; боиЫе пеТ1л1огкбиапТ; // квант времени работы приложения (нетто) боиЫе з1еер0иапб; // квант времени "отдыха" приложения боиЫе ТоТаП1те; // общее время выполнения цикла Тог з\л/1Тсб (иМзд) { сазе Ш_СВЕАТЕ: Т1од.ореп("Т1од.бхб"); 61 тегЗ. Збагб(); продолжение
Листинг 10.1. {продолжение} Т1 гпег2. ЗТагТ (); Гог (1 =0; 1 < М; ++1) { Т1 тег!.ЗГагГ(); // ... ноль инструкций на профилируемом участке сГИскз[1] = Т1тег1.6еТТ1ск(); 1Т (сГИскзШ > 0) { дго881л/огк0иапГЕ1 ] = Т1тег2.6еТТ1те(); Г1тег2.ЗГагГ(); } } ТоТа1Т1те = Т1тег3.6еТТ1те(); Т1од « "Отклонения измерений сГИскз от ожидаемого значения 0\п"; Т1 од « "----------------------------------------------------------\п"; Т1од « " Ио 1 с1Т1ск8 81еер0иапТ могкОиапТХп"; Т1од « " (мс) (мс)\п"; Т1 од « "----------------------------------------------------------\п"; Т1 од. Т1 адз (108:: Г1 хес1); Тог (1 =0; 1 < М; ++1) { 1Т (с1Т1скз[1]) { Ио++; 81еер0иапТ = (с1оиЫе)сГИскз[1 ] / Т1тег1.6еТТ1скРег0зес() / 1000.; пеТМогкОиапТ = дгозз1л1огк0иапТ[1 ] - 81еер0иапТ; Г1 од « зеШ8) « Ио « зеШЮ) « 1 « зеШЮ) « сГИскз[1] « зеТм(12) « зеТргес1з1оп(5) « 81еер0иапТ « зеШЮ) « зеТргес181 оп(3) « пеТМогкОиапТ « епсП ; } } Т1 од « "---------------------------------------------------------\п"; Т1 од « "Общее время выполнения: " « ТоТа1Т1те « " мс" « епсП; Т1од.с!озе(); 1пуа11с1аТеЕесТ(Шпс1, МОП, ТРОЕ): Ьгеак; сазе 1л/М_РА1МТ: ЬОС = ВедипРаппТ(Шпс1, &рз); зрг1 пШТехТ, "оуегбеас! = Жс1, ОРО зрееб: %с1 МИЛОХп", Ытег1.6еТ0уегЬеас1(), ЫтегкОеШскРегОзесО); ТехТОыШОС, 20, 10, ТехТ, 8Тг1еп(ТехТ)); Епс1Ра1 пТ( ШпО, &рз); Ьгеак; сазе Ш_0Е8ТР0У: РозЩпТМеззадеСО): Ьгеак; ОеТаиЮ: геТигп ОеСШпбомРгос(ШпО, иМзд, мРагат, 1Рагат); } геТигп 0; } ////////////////////////////////////////////////////////////////////// После ознакомления с текстом класса КПтег, размещенного в файле КПтег.Н, обратите внимание на использование объектов этого класса. Вы можете объявить объект Втег: КТ1тег Т1тег;
а затем осуществлять профилировку участка кода следующим образом: Шег. 8ТагТ (); // ... профилируемый участок боиЫе еТарзесГИте = Шег.беШтеО; По умолчанию объект Втег настроен на измерение времени в миллисекундах. Вы можете изменить разрешение хронометра, вызвав предварительно (до вызова метода 51аП:) метод 5е111 пй. Этот метод принимает в качестве параметра одну из кон- стант перечисляемого типа Т1те11пй, которые соответствуют единицам измерения 1 мс, 1 мкс, 100 нс, 10 нс, 1 нс. Заметим, что при тактовой частоте процессора поряд- ка 2 ГГц устанавливать единицы измерения менее 100 нс нецелесообразно (см. из- ложенные выше замечания о профилировке программ на конвейерных системах). Если вместо метода БеЕПте применить метод БеЕПск, то будет реализована про- филировка, результаты измерения которой выражены в тактах счетчика Т8С. Этот метод может оказаться полезным для проведения экспериментальных исследова- ний взаимодействия АУтсЕпуз и пользовательского приложения. Класс КТ1’тег содержит также метод и0е1ау, реализующий задержку в исполне- нии кода, заданную в микросекундах. Но эта тема будет обсуждаться в следую- щем разделе. А теперь обратимся к коду файла таш.срр, написанному для тестирования клас- са КПтег. В глобальной области видимости определены три таймера класса КП тег: Птег1,Нтег2 и НтегЗ. Работа с таймерами происходит в оконной процедуре при обработке сообщения ММ_СКЕАТЕ. Попутно здесь же осуществляется эксперимент по наблюдению за многозадачностью, реализуемой операционной системой АУтсклуз. Идея эксперимента довольно проста. В теле цикла Гог, выполняемом И раз, нахо- дятся инструкции для профилировки программного участка нулевой длины. Профи- лировка выполняется вызовом методов Втег1.5Га1±() и Птег1.6еГПск(). Если БеГПск возвращает нулевое значение, значит, «все нормально» и приложению никто не ме- шает спокойно работать. Если же дТ1скз[1] > 0, это свидетельствует о вмешательстве \У1пс1о\\ъ. Понятно, что система переключилась на обслуживание других задач. Ве- личина дТ1скз[1] в этом случае характеризует квант времени, в течение которого наше приложение «отдыхает». С помощью таймера Нтег2 в этот момент фиксируется гру- бая оценка дго85\Л/огк0иап1:[1’] для предыдущего кванта времени, в течение которого приложение проработало. Оценка считается грубой из-за того, что эта величина на самом деле представляет собой сумму «рабочего» кванта и кванта «отдыха». Общее время ГоГаГПте выполнения цикла Гог фиксируется с помощью таймера НтегЗ. После завершения цикла результаты измерений, сохраненные в массивах дТ1 с кз и дгоззМогкОиагй, обрабатываются, чтобы их можно было сохранить в файле про- токола Под.ГхГ. В файл выводятся только те измерения, в которых значение дТ1 скз[1 ] отличается от нуля. Каждая строка протокола содержит порядковый номер стро- ки, номер прохода в цикле Гог, количество тактов счетчика Т8С для профилиров- ки «нулевого» участка, продолжительность «кванта отдыха» приложения и про- должительность «кванта работы» пеГМогкОиап!:. Эксперименты с приложением КПтег на нашем компьютере показали, что при значениях М, не превышающих 100 000 (при общем времени 1:о1:а1Т1 те около 9 мс), \Упк1о\\ъ не прерывает работу приложения и в массиве с1Пск$ не фиксируется ни одного ненулевого значения1. 1 Учтите, что для чистоты эксперимента во время работы приложения КТтег рекомендуется воздер- жаться от действий с клавиатурой или мышью, а также не запускать других программ.
Результаты испытаний программы для И = 5 000 000 (пять миллионов) выгля- дят примерно так, как показано в листинге 10.2. Листинг 10.2. Содержимое файла ПодЛх! после прогона программы с Ы = 5 000 000 Отклонения измерений с1Тлск8 от ожидаемого значения 0 Ио 1 сГИ скз 81еер0иапб (мс) могкОиапб (мс) 1 82410 709236 0.35128 7.302 2 435817 3977324 1.96995 30.884 3 589957 46776 0.02317 13.646 4 770080 772092 0.38241 15.588 5 770977 352 0.00017 0.078 6 1299726 39176 0.01940 46.395 7 1479784 793520 0.39303 15.590 8 2357842 128812 0.06380 77.717 9 2471843 16792 0.00832 9.917 10 3068285 328824 0.16286 52.490 11 3772097 39672 0.01965 62.321 12 3952201 691372 0.34243 15.589 13 4128673 280 0.00014 15.379 14 4480734 37272 0.01846 31.130 15 4660754 6874012 3.40466 15.591 16 4801332 481832 0.23865 12.205 17 Общее время 4978235 460724 выполнения: 446. 0.22819 .926 мс 15.370 Конечно, от прогона к прогону получаются разные результаты измерений, но общая качественная картина, представленная в листинге 10.2, сохраняется. Представленный класс КПтег можно использовать в качестве измерительно- го инструмента в любом приложении. Для этого достаточно скопировать и под- ключить к вашему проекту файл КПтег.Н, после чего нужно добавить директиву #1’пс1ис1е "КПтег.Н" в исходный текст тех файлов, где используются объекты это- го класса. Также необходимо подключить к проекту мультимедийную библио- теку мптт.НЬ. Программирование задержек в исполнении кода При создании реальных программ часто возникает необходимость «притормозить» исполнение кода в том или ином месте программы. В качестве простейшего при- мера можно привести игровую программу, перемещающую некий графический объект по экрану с помощью цикла \л/Ь1Те. Предположим, что вы отладили вашу программу на компьютере с процессором, имеющим тактовую частоту 500 МГц. Что произойдет, если эта программа будет исполняться на более мощном компь- ютере с тактовой частотой процессора 2 ГГц? Если не предусмотреть специаль- ных мер, то объект будет двигаться гораздо быстрее, и не исключено, что играть станет совершенно невозможно. Типичное решение проблемы заключается в определении характеристик процессора и добавлении программируемых задер- жек в таких циклах. Другой пример, с которым мы столкнулись совсем недавно, —
использование программируемой задержки для калибровки счетчика меток ре- ального времени в классе КПтег. Использование функции §1еер Самый простой способ обеспечения задержки реализуется вызовом функции 51еер: 7010 81еер(ВШВВ с1\л/М11118есопс1з); Функция приостанавливает выполнение потока на то количество миллисекунд, которое указано в качестве параметра. Вопрос точности, с которой функция обеспечит затребованную задержку, в М8^^ не освещается. Чтобы разобраться с этим, создадим тестовую программу, код которой приведен в листинге 10.3. Листинг 10.3. Проект 51еерТе8В ////////////////////////////////////////////////////////////////////// // 81еерТез^.срр #1пс1ис1е пб пс1о\л/8. В> #1пс1ис1е <816 о. В> #1пс1ис1е <Мтау8^ет.В> #1пс1ыбе "К1л/пс1.ГГ #1пс1ыбе "КТттег.П" КТ1тег 1лтег; #беТ1пе 51_ЕЕР_Т1МЕ 1 #беТ1пе И 100 боиЫе геаТИтеШеп/а! [И]; ЕРЕЗОЕТ САИВАСК ОпбРгос(НОИВ, 01МТ, ПРАВАМ, ОРАВАМ); //================================= Ш 1л/1МАР1 01 пМа1 п(Н1М8ТА1\1СЕ ИТпзТапсе, Н1М8ТАМ1СЕ ВРгеу1п8^апсе, ЕР8ТР 1рСтсИ1пе, Ш пСтс181по\л/) { М86 тзд; КОпб та1 пОпсК"81 еерТезТ", Шпе^апсе, пСтб8Ио\л/, ОпбРгос); \л/М1е (6е^Ме88аде(&т8д, МВ1_1_, 0, 0)) { Тгап8ШеМе88аде(&т8д); В18раТсГ|Ме88аде(&т8д); } ге^игп тзд.\л/Рапат; } //================================= ЕРЕ86ЕТ САЕЕВАСК ОпбРгос(НОМВ Шпс1, 6П\1Т иМед, ПРАВАМ мРагат, ЕРАРАМ 1 Рагат) { НОС ВВС; РА1МТ8ТР6СТ р8; 8ТаТ1с Ш х, у; л продолжение & 1 Для нормальной компиляции и сборки проекта не забудьте подключить к проекту мультимедийную библиотеку мптт.ПЬ (см. предыдущий раздел).
Листинг 10.3. {продолжение} сЬаг ТехТ[200]; 1 пТ 1; ЬоиЫе гп1пТ, тахТ, атоипТ; зуупТсИ (иМзд) { сазе Ш-СЖАТЕ: //ЫтеВедапРегаос!( 1); // #1 81еер(20); //#2 Тог (1 =0; 1 < М; ++1) { Т1 тег.8ТагТ(); 81еер (8ЕЕЕР_Т1МЕ); геаП1те1пТегуа! [1 ] = Т1тег.0еТТ1те() ; } //Т1теЕпс1Рег1ос1(1); // #3 1пуа11(ШеРесТ(Шпс1, МОЕЕ, ТРЕЕ): Ьгеак; сазе 1л/М_РА1МТ: НОС = Вед1 пРаапТ(Шпс1, &рз); зрг1 пШТехТ, "Фактическая величина задержки для 8ЕЕЕР_Т1МЕ = %сГ, 8ЕЕЕР_Т1МЕ); ТехТОиШОС, 0, 20, ТехТ, зТг!еп(ТехТ)); х = 15; у = 40; Фог (1 =0; 1 <20; ++1) { зрг1пТТ(ТехТ, "Г2Т\0", геа1Т1те1пТегуа! Ш); ТехТОиШОС, х, у, ТехТ, зТг!еп(ТехТ)); у += 16; } // Определяем пгт и тах значения ттТ = тахТ = атоипТ = геаП1те1пТегуа! [0]; Фог (1 =1; 1 < М; ++1) { 1Т (геаП1те1пТегуа! [1] < ттТ) ттТ = геа1Т1те1пТегуа![1]; 1Т (геаП1те1пТегуа! [1] > тахТ) тахТ = геа1Т1те1пТегуа![1]; атоипТ += геа1Т1те1пТегуа![1]; } зрг1пТТ(ТехТ, "ттТ = Г2Т, тахТ = Г2Т, ауегаде = Г2Т\0", ттТ, тахТ, атоипТ / И); ТехТОиШОС, х, у, ТехТ, зТг1еп(ТехТ)); Епс1Ра1пТ(Шпс1, &рз); Ьгеак; сазе Ш_0Е8ТР0У: РозТОшТМеззаде(0); Ьгеак; с1еТаи!Т: геТигп ОеТШпс1омРгос(Шпс1, иМзд, мРагат, 1 Рагат); } геТигп 0; } //////////////////////////////////////////////////////////////////////
В программе производится профилировка функции 51еер с помощью объекта В тег класса КП тег. Профилирующий код находится в блоке обработки сообще- ния ММ_СКЕАТЕ. Испытания повторяются И раз с помощью цикла Гог. Результаты измерений в миллисекундах сохраняются в массиве геаГПте1п1:еп/а1. В блоке обработки сообщения ММ_РА1МТ определяются минимальное, макси- мальное и среднее значения для реальной задержки. Эти величины выводятся в окно программы вместе с результатами первых двадцати измерений. Величина заказанной задержки задается с помощью макроса $1_ЕЕР_Т1МЕ. В табл. 10.1 приведены результаты тестирования для разных значений $1_ЕЕР_ Т1МЕ. Таблица 10.1. Реальная задержка, обеспечиваемая функцией 51еер 8ЬЕЕР_Т1МЕ, мс Реальная задержка, мс Ближайшее большее значение, кратное разрешению системно- го таймера, мс Минимальная Максимальная Средняя 1 9,6 21,64 15,63 15,625 5 11,74 21,81 15,62 15,625 10 9,7 21,54 15,68 15,625 16 25,28 37,16 31,25 31,25 20 25,26 37,24 31,25 31,25 40 40,94 52,71 46,87 46,875 100 103,43 115,30 109,36 109,375 1000 1000,63 1002,32 1000,76 1000 Из приведенных данных видно, что реальная задержка, обеспечиваемая функци- ей 51еер, находится в некоторой области ближайшего большего значения, кратно- го разрешению системного таймера (15,625 мс). Теперь обратите внимание на инструкцию 51еер(20), помеченную в програм- ме номером #2. Если ее убрать, то самый первый вызов функции $1еер(51_ЕЕР_Т1МЕ) даст результат геаГПте1п1:еп/а1[0], значительно отличающийся от последующих вызовов. При малых величинах $1_ЕЕР_Т1МЕ отклонения от среднего значения до- стигают 80 %, а для задержки в 1000 мс — порядка 10 %. Трудно дать внятное объяснение этому факту, пользуясь информацией из справочной системы М8ВК Но, конечно, следует принять его во внимание, когда вы используете функцию 51еер для калибровки измерительного инструмента (см. исходный текст класса КПтег). Наши дальнейшие эксперименты показали, что разрешение функции 51еер можно существенно повысить, если предварительно вызвать функцию НтеВедш Релос1(1), которая относится к мультимедийной библиотеке АУтскпуз. Эта функция устанавливает разрешение системного мультимедийного таймера, рав- ное 1 мс. Когда повышенное разрешение системного таймера становится ненужным в даль- нейшей работе приложения, рекомендуется вызвать функцию НтеЕпс1Репос1(1) для освобождения задействованных системных ресурсов.
Чтобы убедиться в этом, раскомментируйте в листинге 10.3 инструкции, по- меченные номерами #1 и #3, и проведите испытания повторно. Скорее всего, вы получите результаты, близкие к тем, которые показаны в табл. 10.2. Таблица 10.2. Реальная задержка, обеспечиваемая функцией 51еер после вызова йтеВед1пРепос1(1) 8ЬЕЕР_Т1МЕ, мс Реальная задержка, мс Минимальная Максимальная Средняя 1 1,66 4,33 1,98 2 2,78 3,07 2,93 3 3,75 4,06 3,91 5 5,38 7,11 5,87 10 10,26 12,93 10,78 20 20,21 20,80 20,51 100 100,20 100,95 100,57 1000 999,85 1001,81 1000,34 Хотя в области малых значений (менее 10 мс) реальная задержка получается с существенной относительной погрешностью, все же эти результаты намного предпочтительнее тех, которые были показаны в табл. 10.1. Использование метода иОе1ау класса КТ!тег Если вам нужна задержка в микросекундном диапазоне, то функция 51еер ста- новится бесполезной. Возможны разные варианты решения этой проблемы, один из них — вызов метода иОе1ау класса КПтег. Чтобы посмотреть, как он работает, можно модифицировать программу 51еерТез1: (см. листинг 10.3) следующим об- разом. Добавьте макрос #с1еТ1 пе Ы_5ЕС 1 // задержка в микросекундах и определение еще одного объекта класса КПтег: КТ1тег Р1тег1; Затем замените код профилировки, содержащийся в блоке обработки сообще- ния ДОМ_СКЕАТЕ, на следующий фрагмент: Р1тег1.8е^ипШ1]8ес); 11 тег .Зе^итРШзес); Тог (1 =0; 1 < М; ++1) { ^1тег,8РагР(); Р1тег!.иОе!ау(Ы_5ЕС); геаП1те1пРегуа1 [1 ] = ^1тег,6еП1те(); } После этого проведите испытания программы для разных значений 11_5ЕС. На нашем компьютере были получены результаты, показанные в табл. 10.3. СОВЕТ--------------------------------------------------------------------------- Не забывайте, что реализация класса К"Птег основана на использовании ассемблерной команды гсИбс, которая поддерживается процессорами модельных рядов не ниже Репйит III. Если вам нуж- но обеспечить работоспособность приложения на более старых аппаратных платформах, реко- мендуем для реализации малых задержек использовать приведенный ниже класс ОПгпег.
Таблица 10.3. Реальная задержка, обеспечиваемая методом иОе1ау класса КТ!тег 11_8ЕС, мкс Реальная задержка, мкс Минимальная Максимальная Средняя 1 1,20 1,25 1,21 2 2,19 2,22 2,20 5 5,14 5,18 5,15 10 10,14 10,16 10,15 50 50,20 50,29 50,27 100 100,15 100,24 100,23 1000 1000,15 1679,37 1007,03 Класс <2Т!тег Класс СГПтег имеет интерфейс, совпадающий с интерфейсом класса КТ1’тег. Но ре- ализация его методов основана на использовании счетчика монитора производи- тельности. В листинге 10.4 приведено определение данного класса. Листинг 10.4. Класс СГПтег ////////////////////////////////////////////////////////////////////// // СГПтег.И #1Тпс1еГ 0Т1МЕК_Н #беТ1пе 0Т1МЕК_Н #ргадта магтпдСсНзаЫе : 4035) Турес1еТ епит { Мзес, бзес, Мз_100, Мз_10, Мзес } ТлтеОтТ: с1азз С)Т1тег { ЕАК6Е_1МТЕ6ЕК Ггед; ЕАК6Е_1МТЕ6ЕК соипПэед: ЕАЕ6Е_1МТЕ6ЕЕ соипб_епс1; ЕСЖЕСЖ 11_с_Ьед; ЕСЖЕСЖ 11_с_епс1; 1опд с1_Т1 с: боиЫе с1Г; ОШКО 1омРагТ; ЕСЖ МдНРагТ; // коэффициенты-делители боиЫе пСус1еРегТпапо8ес; боиЫе пСус1еРегТ0папо8ес; боиЫе пСус1еРег100папо8ес; боиЫе пСус1еРег1т1сго8ес; боиЫе пСус1еРег1т11П8ес; боиЫе сПуыог; // текущий делитель риЬПс: 0Т1тег(уо1с1) { // Калибровка теВедл пРег 1 ос1 (1); 81еер(20); ВООЕ зиссезз = ОиегуРегГогтапсеЕгедиепсуС&Ггед); 1Г (!зиссезз) продолжение &
Листинг 10.4. {продолжение} Ме88адеВох(МЫ_1_, "Счетчик ОнегуРегСогтапсе не поддерживается", О, МВ-ОК I МВ-1С0М5Т0Р); 1омРагЬ = 6гед.1_о\л/Раг6; МдЬРагЬ = бгед. Н1 дЬРагЬ; // Калибровка делителей пСус1еРег1папо$ес = Сгед.ЬомРагЬ / 1000000000.; пСус1еРег10папо$ес = Сгед.ЬомРагЬ / 100000000.; пСус1еРег100папо$ес = Сгед.ЬомРагЬ / 10000000.; пСус1еРег1т1сго8ес = Сгед.ЬомРагЬ / 1000000.; пСус1еРег1т11118ес = Сгед.ЬомРагЬ / 1000.; 61 теЕпс1Рег1 об (1); 61у1гог = пСус1еРег1т11118ес; // делитель по умолчанию } уо16 8еЬбпШТ1тебп1Ь ып16) { 8м1ЬсЬ (ып16) { сале Мсес: сНуп'гог = пСус1еРег1т11118ес; Ьгеак; сале боес: 61 уыог = пСус1еРег1т1сго8ес; Ьгеак; сале Моес: сП уп'гог = пСус1еРег1папо8ес; Ьгеак; сале И8_10: сП VI гог = пСус1еРег10папо8ес; Ьгеак; сале И8-100: сП VI гог = пСус1еРег100папо8ес; Ьгеак; } } 1п11пе уо16 86агЬ(уо1б) { ОнегуРегСогтапсеСоипЬег(&соипЬ_Ьед); } 1п11пе 1опд 6еЬТ1ск(уо1б) { ОнегуРегСогтапсеСонпбег(&сонпЬ_епб); 11_с_Ьед = сонпЬ-Ьед.ОнабРагЬ; 11_с_епб = сонпЬ_еп6.0иа6РагЬ; 6_Ыс = 11_с_епб - 11_с_Ьед; гебнгп 6_Ыс; } 1п11 пе бонЫе 6еЬТ1те(уо1б) { ОнегуРегСогтапсеСонпбег(&сонпЬ_епб); 11_с_Ьед = сонпЬ-Ьед.ОиабРагЬ; 11_с_епб = соипЬ_епб.0иа0РагЬ; б_Ыс = 11_с_епб - 11_с_Ьед; гебигп б_Ыс / Ыуыог; } 1п11пе 1пЬ 6еЬТ1скРегб8ес(уо1б) { гебигп (1п6)пСус1еРег1т1сго8ес; } 1п11пе уоЮ н0е1ау(1пб и8ес) { Ш Ш арсеО = 0; 8еббп16(боес); ЗЬагЬО; \л/Ы1е ( Ш арсеО < и8ес) Ш арсеО = (1пЬ)6еШте();
}; #епсИТ /* 0Т1МЕЕ_Н */ ////////////////////////////////////////////////////////////////////// Поскольку класс СГПтег имеет такой же интерфейс, что и класс КПтег, исполь- зование его объектов не отличается от использования объектов класса КПтег. Стандартный таймер Стандартный таймер в АУтскпуз — это устройство, периодически уведомляющее приложение о завершении заданного интервала времени. Вы можете присоеди- нить стандартный таймер к своей программе с помощью функции 5е1Птег: ШТ-РТК 8еШтег( НШО Шпс1, 01ИТ-РТР пЮЕуепТ, 1ЛИТ иЕ1арзе, Т1МЕЕРЕ0С 1рТ1теРгос // дескриптор окна // идентификатор таймера // интервал в миллисекундах // адрес функции - обработчика сообщения Ш_Т1МЕЕ Первый параметр, Н\Л/пс1, содержит дескриптор окна, ассоциированного с дан- ным таймером. Если этот параметр равен МОП, то с таймером не связывается ни- какое окно и сообщения от таймера будут приходить в специально созданную для этого функцию. Второй параметр, пЮЕуеп!, позволяет указывать идентификатор таймера, ко- торым может быть произвольное целое число (но не нуль). Если программа ис- пользует более одного таймера, то рекомендуется определить идентификаторы таймеров в виде именованных констант, например, с помощью типа епит или ди- рективы #с1еГ1 пе. Это улучшает читаемость кода программы. Если параметр Н\Л/пс1 равен ИIIИ, то параметр пЮЕуеп! игнорируется. Третий параметр, иЕ1ар$е, задает интервал, который может находиться в преде- лах (теоретически) от 1 до 4 294 967 295 мс, что составляет около 50 дней. Это значение определяет темп, с которым АУтсЕпуз будет посылать вашей программе сообщения ММ_Т1МЕК. Сообщения ММ_Т1МЕК направляются либо оконной проце- дуре для окна Н\Л/пс1, если параметр 1рТ1’теРгос равен МОП, либо функции обратного вызова с адресом 1рТ1’теРгос — в противном случае. Если на месте НУУпс! указано значение МОП, то возвращаемое функцией значе- ние является идентификатором созданного таймера. В любом случае функция 5е1Т1’тег возвращает нулевое значение, если она не смогла создать таймер. Если таймер по истечении некоторого времени больше не нужен, то рекомен- дуется его уничтожить, вызвав функцию КШТттег: ВООЕ К11 П1тег(НШ0 Шпс1, 01МТ_РТЕ иЮЕуепб); В параметре НУУп с1 указывается дескриптор окна, с которым был связан таймер. Это значение должно совпадать со значением Н\Л/пс1, указанным при вызове функ- ции 5е1Т1’тег. Второй параметр содержит идентификатор уничтожаемого таймера. На его месте используется либо идентификатор, указанный при создании таймера, если он создавался для окна, либо значение, возвращенное функцией 5е1Т1’тег (для тай- мера, имеющего собственную функцию обработки сообщений).
Первый способ использования стандартных таймеров В первом способе таймер подключается к окну, заданному параметром Н\Л/пс1 при вызове функции 5е±Т1тег, а параметр 1рТ1’теРгос получает значение МОП. В этом случае функция окна, к которому подключен таймер, будет получать сообщения от таймера с кодом ММ_Т1МЕК. Допустим, что вашей программе требуется два таймера. Один таймер имеет период срабатывания в 1 с, а другой обладает периодом срабатывания в 1 мин. Вы могли бы сначала определить идентификаторы таймеров с помощью инструкций #с1еГ1’пе: #с1еГ1 пе Т1МЕК_8ЕС 1 #(М1пе Т1МЕЕ_М1М 2 Затем либо в теле функции УУ1пМа1П, либо в блоке обработки сообщения ММ_СКЕАТЕ оконной процедуры нужно вызвать функции для создания двух тай- меров: 5еШтег(Шпс1, Т1МЕЕ_8ЕС, 1000, ШЕ); 5еШтег(Шпс1, Т1МЕЕ_М11\1, 60000, ШЕ); Поскольку параметр мРагат сообщения ММ_Т1МЕК содержит идентификатор таймера, то логика обработки сообщения ММ_Т1МЕК в оконной процедуре обычно выглядит примерно так: сазе Ш_Т1МЕК: змПсЬ (\л/Рагаш) { сазе Т1МЕЕ-8ЕС: ... // обработка одного сообщения в секунду Ьгеак; сазе Т1МЕЕ_М11\1: ... // обработка одного сообщения в минуту Ьгеак; } Ьгеак; После того как эти таймеры перестанут быть нужными для работы приложе- ния, рекомендуется их уничтожить, вызвав следующие инструкции: К11 П1тег(Шпс1, Т1МЕР_8ЕС); К11 П1тег(Шпс1, Т1МЕР_М1М); Отметим, что на практике реальный интервал генерации сообщений ММ_Т1МЕК может значительно отличаться от заданного вами значения для параметра и ЕЕарзе, особенно для малых значений. Здесь необходимо учитывать следующие недоку- ментированные особенности функционирования стандартного таймера. Во-пер- вых, реальный интервал не может быть меньше разрешения системного таймера. Во-вторых, реальный интервал всегда является некоторым приближением к бли- жайшему большему значению, кратному разрешению системного таймера. Напри- мер, если системный таймер имеет разрешение 15,625 мс, то ряд допустимых зна- чений, в окрестностях которых реализуются значения реального интервала, имеет вид 15,625 мс, 31,25 мс, 46,875 мс, 62,5 мс и т. д. Приведем результаты экспериментов по измерению реального интервала ге- нерации сообщений ММ_Т1МЕК, проведенных на компьютере с процессором 1п1е1
Се1егоп СРП 2,0 ГГц и операционной системой МхсгозоЙ АУтйомга 2000 (разре- шение системного таймера 15,625 мс). Измерения осуществлялись путем фикса- ции времени поступления в оконную процедуру сообщения ММ_Т1МЕК для тыся- чи последовательных тиков стандартного таймера. Тестовая программа приведена в листинге 10.5. Листинг 10.5. Проект ЗеГПтеНТезР ////////////////////////////////////////////////////////////////////// // 8еРТ1тег1ТезР.срр #1пс1ис1е <м1 пс1омз. Ь> #1пс1ис1е <8Тс11 о. Н> #1пс1ис1е "К1л/пс1.Г|" #1пс1ис1е "КТ1тег.И" КТ1тег Р1тег; #беТ1 пе Ю_Т1МЕВ 1 #беТ1 пе Т1МЕ_РЕВЮ0 1 #с1еГ1пе И 1000 с1оиЫе геаП1те1пРеп/а1 [И]; ЕКЕ8ЕЕТ САЕЕВАСК ШбРгос(НШО, 1Л1\1Т, ПРАВАМ, ЕРАВАМ); //=============================== 1 пр М1МАР1 1л11 пМа1 п(Н1М8ТАМСЕ ЫпзРапсе, Н1МЗТАМ1СЕ ЬРгеу1пзРапсе, ЕРЗТВ 1рСтс1Е1пе, 1 пр пСтбЗГюУл/) { М86 тзд; К1л1пс1 та1 п1л/пс1 ("ЗеТТ1 теп1 - РезР", ЫпзРапсе, пСтОом, ОпбРгос); \л/Гп'1е (СеРМеззадеС&тзд, МЫЕЕ, 0, 0)) { ТгапзТаРеМеззадеС&тзд); ОтзраРсИМеззадеС&тзд); } геРигп тзд.мРагат; } /========================= ЕВЕЗСЕТ САЕЕВАСК МРгос(НШВ Шпс1, 1Л1\1Т иМзд, ПРАВАМ мРагат, ЕРАВАМ 1 Рагат) { НОС ВВС; РА1МТЗТВССТ рз; зТаТас 1 пр х, у; сЬаг РехР[200]; 1 пр 1; с1оиЫе гтипТ, тахТ, атоипР; зРаР1с 1 пр соипР = -1; 8\л/1РсИ (иМзд) { сазе Ш_СВЕАТЕ: 8еРТ1тег(Шпс1, Ю_Т1МЕВ, Т1МЕ_РЕВЮ0, ШЕЕ); Ьгеак; сазе Ш_Т1МЕВ: 1Р (соипР >= 0 && соипР < Ю геаП1те1пРегуа! ЕсоипР] = Р1тег ,6еРТ1те(); Р1тег.ЗРагР(); соипР++; продолжение
Листинг 10.5. {продолжение} 1Т (соипФ == Ю 1пуа11 (ШеЕеШШпс1, М1Л_1_, ТРОЕ); Ьгеак; сазе Ш-РА1ИТ: НОС = Вед1 пРаапЬ(Шпс1, &рз); зргШШехФ, "Фактическая величина задержки для Т1МЕ_РЕЕЮ0 = %сГ, Т1МЕ_РЕЕЮ0); ТехФОиШОС, 0, 20, ФехФ, зФг!еп(ФехФ)); /* код, аналогичный приведенному в листинге 10.3 */ Епс1Ра1 пФ( Шпс1, &рз); Ьгеак; сазе Ш_0Е8ТЕ0У: К11 П1тег(Шпс1, Ю_Т1МЕР); РозФ0и1ФМеззаде(0); Ьгеак; с1еФаи1Ф: геФигп ОеФШпбомРгос(Шпс1, иМзд, мРагат, 1 Рагат); } геФигп 0; } ////////////////////////////////////////////////////////////////////// Результаты тестирования представлены в табл. 10.4. Таблица 10.4. Замеры реального интервала срабатывания стандартного таймера Т1МЕ_РЕК1ОО, мс Реальный интервал, мс Ближайшее большее значение, кратное разрешению систем- ного таймера, мс Минимальный Максимальный Средний 1 14,79 16,16 15,62 15,625 10 15,09 16,17 15,62 15,625 20 30,47 32,07 31,26 31,25 40 46,18 47,56 46,88 46,875 100 108,90 110,10 109,38 109,375 1000 1000,07 1015,70 1000,86 1000 Отметим, что указанные результаты получены для работы приложения как бы в «стерильных условиях». Иными словами, во время эксперимента на компьюте- ре не было других работающих ресурсоемких приложений. Это раз. Кроме того, пользователь не прикасался к окну тестируемого приложения. Это два. Если же во время эксперимента многократно перемещать с помощью мыши окно работающего приложения, то разброс значений реального интервала сра- батывания таймера1 будет совсем другим. Например, для заказанного интерва- ла 100 мс был получен разброс 0,52-503,38 мс. Этот факт требует некоторых объяснений, но сначала нужно разобраться, каков механизм продвижения сооб- щения ММ_Т1МЕК от виртуального таймера к оконной процедуре. 1 Разница между минимальным и максимальным значениями.
Таймерные прерывания по своей природе являются асинхронными, так как по отношению к выполняемой программе их появление предсказать нельзя. Они ста- вятся в обычную очередь сообщений приложения и обрабатываются, как все остальные сообщения. Но это еще не все. Сообщение ММ_Т1МЕК обладает самым низким приоритетом по отношению к другим сообщениям. Единственным иск- лючением является сообщение ММ_РА1МТ, обладающее еще более низким приори- тетом. Поэтому функция беВИеззаде отправляет сообщение ММ_Т1МЕК на обработ- ку только тогда, когда в очереди сообщений не осталось более приоритетных сообщений. Таким образом, когда пользователь перемещает окно или изменяет его разме- ры, соответствующие сообщения устремляются в очередь сообщений и, обладая более высоким приоритетом, оттесняют сообщение ММ_Т1МЕК в конец очереди. Сообщения ММ_Т1МЕК похожи на сообщения ММ_РА1МТ еще в одном аспекте. Когда приложение чем-то занято и не успевает обработать предыдущее сообще- ние ММ_Т1МЕК, то \У1пс1о\\ъ не накапливает в очереди сообщений несколько со- общений ММ_Т1МЕК. Вместо этого АУшсЬауз объединяет несколько сообщений ММ_Т1МЕК из очереди в одно сообщение. В такой ситуации приложение не способ- но определить количество потерянных сообщений ММ_Т1МЕК. Теперь мы можем объяснить наблюдаемый разброс 0,52-503,38 мс для реаль- ного интервала срабатывания таймера при ожидаемом интервале 109,375 мс. Зна- чение 503,38 мс отражает ситуацию, когда четыре последовательных сообщения ММ_Т1МЕК были потеряны и только пятое из них было обработано. Причем оно попало в очередь сообщений не сразу, а с задержкой примерно 43,5 мс. Значение 0,52 мс могло получиться в результате аналогичной задержки примерно на 108,855 мс при постановке в очередь предыдущего сообщения ММ_Т1МЕК (из-за обработки сообщения ММ_М0\/1М6 с более высоким приоритетом), поэтому от те- кущего интервала осталось примерно 0,52 мс. Пример первого способа использования стандартного таймера вы можете най- ти в листинге 12.1 (глава 12). Второй способ использования стандартных таймеров При первом способе установки таймера сообщения ММ_Т1МЕК посылаются в обычную оконную процедуру. Второй способ предписывает АУшсклуз пересы- лать сообщения таймера другой функции из вашей программы. Это функция об- ратного вызова (са11-Ьаск), и она должна быть определена в глобальной области видимости следующим образом1: 7010 САИВАСК Т1тегРгос(НШ0 Ыпс1, 011Х1Т иМзд, 01МТ_РТК 1сОепО, 0Ш00 с1\л/Т1те) { ... // обработка сообщения Ш_Т1МЕ0 } Перед установкой таймера вы должны определить переменную для сохране- ния идентификатора таймера, например: Ш 1сГИгпег; 1 Вместо имени ВтеРгос можно использовать любое другое имя.
Сама установка таймера выглядит следующим образом: ЩТ1тег = 8еШтег(ШЕЕ, ШЕЕ, Т1МЕ_РЕЕЮ0, (Т1МЕКРШОТ1 тегРгос); где Т1МЕ_РЕКЮ0 — именованная константа, задающая интервал в миллисекундах. После окончания работы с таймером он уничтожается вызовом функции <111Т1 тег (МС1ЕЕ, 1сГИтег): В заключение следует отметить, что принципиальных различий в функциони- ровании стандартных таймеров, созданных первым или вторым способом, не на- блюдается. И в том, и в другом случаях таймеры генерируют сообщения ММ_Т1МЕК, поступающие в очередь сообщений приложения, а проблемы, связанные с обра- боткой этого сообщения, уже рассматривались выше. Хотя стандартный таймер АУшсЬауз не отличается высокой точностью и на- дежностью, тем не менее, приложения могут использовать его сервис, если от- клонения в периоде генерации сообщений таймера не очень важны для решаемой задачи. В противном случае рекомендуется прибегать к услугам мультимедийного таймера. Мультимедийный таймер Для использования в вашем приложении мультимедийного таймера необходимо подключить к проекту мультимедийную библиотеку мптт.НЬ1 и добавить в на- чало файла, в котором вызываются функции библиотеки, следующую директиву: #1пс1ис1е <Мтзу8^ет.И> Содержащаяся в библиотеке функция ВтебеЮеуСарз позволяет узнать поддер- живаемое системой разрешение мультимедийного таймера. Для этого определите переменную структурного типа Т1МЕСАР8 Ш и вызовите функцию Итебе^ОеуСарзСЗДс, 81 геоТ(Т1МЕСАР8)); В результате этого вызова поля 1:с.\л/Рег10с1М1П и Т:с.мРелос1Мах будут содержать минимальное и максимальное разрешения в миллисекундах, поддерживаемые для мультимедийного таймера. Для нашего компьютера, например, были получены значения 1 мс и 1 000 000 мс соответственно. Мультимедийная библиотека содержит функции ВтеВедтпРелос! и ВтеЕпс1Релос1, предназначенные для установки и отмены конкретного разрешения мультимедий- ного таймера. Величина разрешения в миллисекундах передается в виде парамет- ра в обеих функциях. М8ВХ рекомендует вызывать функцию ВтеВед1пРепод(1х.ууРеподМ1п) непос- редственно перед тем, как обратиться к сервису мультимедийного таймера. По- вышенное разрешение таймера, по-видимому, реализуется в системе при помощи создания отдельного потока с высоким приоритетом выполнения. Поэтому реко- мендуется отменять режим повышенного разрешения таймера (ВтеЕпс1Релос1), как только он перестает быть нужным. 1 При работе с У18па1 ЗЕибю б.нужно выполнить команду меню Рго]ес1: ► ЗеШпдз..., перейти на вкладку 1лпк и в текстовом поле ОЬдесТ/ИЬгагу тосЫез указать имя библиотеки мптт.КЬ.
Ранее мы уже использовали функции йтеВед1 пРелос! и В’теЕпс1Релос1 для повы- шения точности работы функции 51еер. Степень влияния этих функций на работу собственно мультимедийного таймера мы выясним, когда проведем эксперимен- ты с тестовой программой. Функции йте$е1Еуеп1 и йтеК!11Еуеп1 Мультимедийный таймер выполняется в своем собственном потоке. Он отслежи- вает заданный временной интервал и активизирует по истечении этого интервала таймерное событие. После этого АУшбом^ либо вызывает заданную функцию об- ратного вызова, либо устанавливает заданное событие. Сервис мультимедийного таймера вызывается с помощью функции Нте5е1: ЕуепР ММКЕ81ЛТ ШтеЗеЦЕуеп-ЬСиТМТ иОе!ау, 1Л1Х1Т иКезоТШопЛРТЖСАШАСК 1рТ1теРгос, ОШРО (М]8ег, 01 ИТ ГиЕуегД); В параметре иОе1ау указывается задержка активизации таймерного события. В качестве единиц измерения используются миллисекунды. Если значение пара- метра выходит за пределы задержек, поддерживаемых таймером, то функция вер- нет код ошибки. Параметр иКезсйиВоп содержит разрешение таймерного события в миллисекун- дах. Чем меньше значение этого параметра, тем выше разрешение. Нулевое зна- чение параметра означает, что периодические события таймера должны появляться с максимально возможной точностью. Однако чем выше разрешение, тем больше будут накладные расходы операционной системы, поэтому желательно выбирать максимальное значение параметра иКезсйиВ’оп, при котором удовлетворяются по- требности приложения. Параметр [рП’теРгос содержит адрес функции обратного вызова, которая вы- зывается после установки таймерного события. Если параметр ГиЕуегй содер- жит флаг Т1МЕ_САПВАСК_Е7ЕМТ_5ЕТ или Т1МЕ_САПВАСК_Е7ЕМТ_РиЕ5Е, то пара- метр [рП’теРгос интерпретируется как дескриптор объекта «событие». Параметр с1до11$ег может содержать данные, передаваемые в функцию [рП’теРгос, интерпретация которых определяется программистом. В частности, эти данные иногда используются для организации обратной связи с вызывающей функцией. И наконец, в параметре ГиЕуеп!: задается тип таймерного события. Этот пара- метр может содержать один из следующих флагов: Значение Интерпретация Т1МЕ.ОЫЕ8НОТ Т1МЕ_РЕИЮО1С Событие происходит один раз после истечения иОе1ау миллисекунд Событие происходит каждые иОе1ау миллисекунд Также параметр ГиЕуегй может содержать один из флагов, определяющих дей- ствия АУ1пс1оау8 по истечении заданного интервала времени: Значение Интерпретация Т1МЕ_СА1_1_ВАСК_РС11\1СТЮЫ Вызывается функция с адресом 1р"ПтеРгос (значение по умолчанию) Т1МЕ_СА1_1_ВАСК_Е7Е1\1Т_8ЕТ Вызывается функция 8е1Еуеп1: для установки события с де- скриптором 1рТ1теРгос Т1МЕ_СА1_1_ВАСК_ЕУЕ1\1Т_Р111_8Е Вызывается функция Ри1зеЕуеп1: для установки (с последующим сбросом) события с дескриптором 1рТ1теРгос
В случае успешного завершения функция Вте5е1:Еуеп1: возвращает идентифи- катор таймерного события. Если при выполнении функции произошла ошибка, то функция возвращает значение МОП. Как только работа с таймером будет завершена, следует вызвать функцию ВтеК1’НЕуеп! для освобождения задействованных системных ресурсов. В качестве параметра функции ВтеКН1Еуеп1: передается тот идентификатор таймерного со- бытия, который был получен от функции ВтеЗе^ЕуепЕ Тестирование мультимедийного таймера Чтобы сравнить поведение мультимедийного таймера с поведением стандартного таймера, проведем эксперименты по измерению реального интервала вызова фун- кции 1рТ1’теРгос. Тестовая программа приведена в листинге 10.6. Листинг 10.6. Проект МпгГПтегТез! ////////////////////////////////////////////////////////////////////// // МтТ1телТе8^.срр #1лс1ис1е <м1 пс1о\л/8. И> #1лс1ис1е <81x11 о. И> #1лс1ис1е <Мт8у8^ет.В> #1лс1ис1е <Т8^леат> □81 пд латеерасе 8^с1; #1пс1ис1е "ШлО" #1лс1ис1е "КЛтег.П" оТе^леат Т1од; КТ1тел 'Итег; елит ВеелМед { Ш_КЕАОУ = 1л1М_В8ЕВ+1 }; #с1еТ1 ле Т1МЕ_РЕЕ100 1 #с1еТ1 ле И 1000 боиЫе леаП1те1лРелуа1 [И]; уо1с1 САЕЕВАСК Т1 теРлос(01ИТ, 1Л1\1Т, ВШВВ, ВШВВ, ВШВВ); ЕРЕЗЮЕТ САЕЕВАСК МРлос(НШВ, ШТ, ПРАВАМ, ЕРАЕАМ); //=====================—^ 1лР М1МАР1 1л/1 лМа1 л(Н1М8ТАИСЕ КТлеТалсе, Н1МЗТАМСЕ КРлеуТлеТалсе, ЕРЗТЕ 1рСтсИ1ле, 1лР лСтбЗНо\л/) { Под.орел('41 од.Ш"); М86 тод; К1л1лс1 та1 л1л/лс1 ("ММ Т1тел - ТееТ", КТлеТалсе, лСтсЗНом, МлбРгос); \л/М1е (6е^Ме88аде(&т8д, М1Л_1_, 0, 0)) { Тлал81аТеМе88аде(&т8д); В18раШМе88аде(&т8д); } Т1од.ргеса 81ол(3); Тол (1лР 1 =0; 1 < М; ++1) Т1 од « леаП1те!лТелуа1 [1] « елсП ; Т1од.с!О8е(); леТилл тод.мРалат; } //====================================================================
1КЕ51ЛТ САЕЕВАСК ОпОРпос(НШО Шпс1, 1Л1Х1Т иМзд, ОРАВАМ мРапат, ЕРАВАМ 1 Рагат) { НОС НОС; РА1МТ8ТВ0СТ рз; сИап ТехТЦ200Ц; 1 пТ 1; сопзТ 1пТ 10 = 30; // номер первого обрабатывемого измерения ОоиЫе т1пТ, тахТ, атоипТ; зТаТ1 с ММЕЕ300Т ттВези1Т; 8\л/1ТсН (иМзд) { сазе ОМ_СВЕАТЕ: //Т1теВед1пРеп1ос1(1); //#1 ттВези1Т = Т1 теЗеТЕуепТ(Т1МЕ_РЕВЮ0, 0, Т1теРпос, пе1пТепрпеТ_сазТ<000В0>(И0пс1), Т1МЕ-РЕЕ1001С); 1Т (!ттВези1Т) МеззадеВох(Н1/\1пс1, "Мультимедийный таймер не создан", "Еппоп", МВ_ОК); Ьпеак; сазе ОМ_РА1МТ: НОС = Вед1 пРа1 пТ(ИОпс1, &рз); // Определяем т1п и тах значения т1пТ = тахТ = атоипТ = пеаП1те1пТепуа1 [10]; Топ (1 =10+1; 1 < М; ++1) { 1Т (пеаП1те1пТепуа1 [1 ] < т1пТ) т1пТ = пеаП1те1пТепуа1 [1 ]; 1Т (пеаП1те1пТепуа1 [1 ] > тахТ) тахТ = пеаП1те1пТепуа1 [1 ]; атоипТ += пеаП1те1пТепуа1 [1 ]; } зрп1 пТТ(ТехТ, "т1пТ = Ж.ЗТ, тахТ = Ж.ЗТ, ауепаде = ГЗТ\0", т1пТ, тахТ, атоипТ / (И - 10)); ТехТОиШОС, 10, 10, ТехТ, зТп1еп(ТехТ)); ЕпбРа1 пТ(Н1л!пб, &рз); Ьпеак; сазе ОМ_ВЕАОУ: МеззадеВох(И0пс1, "Готово", "Завершение задания", МВ_0К); 8епс1Меззаде(И0пс1, 0М_0Е8ТВ0У, 0, 0); Ьпеак; сазе 0М_0Е8ТВ0У: Т1теК111ЕуепТ(ттВези1Т); //Т1теЕпс1Реп1ос1(1); // #2 РозТ0и1ТМеззаде(0); Ьпеак; с1еТаи1Т: пеТипп ОеТШпбомРпос(И1л1пс1, иМзд, мРапат, 1Рапат); } пеТипп 0; } //================================= уоЮ САЕЕВАСК Т1 теРпос(01 ИТ ШтепЮ, 01 ИТ тзд, 0ШР0 ОмОзеп, 0ШР0 (М, 0ШР0 сШ продолжение
Листинг 10.6 {продолжение} { Ш соигтЬ = -1; НШО Ыпс1 = геШегрге^_сазЬ<НШ0>(бми8ег); 1Т (соигтЬ >= 0 && соигтЬ < Ю геа!Т1теШегуа1 [соиггЕ] = 'Шег ,6еШте(); Цтег.З-ЬагШ; соиггЕ++: 1Т (соиггЬ == Ю { 1пуа11 (ШеЕесШмпс!, М1Л_1_, ТЕСЕ); ЗепбМеззаде(Ыпс1, Ш_ЕЕАОУ, О, 0); } } ////////////////////////////////////////////////////////////////////// Обратите внимание на то, что при вызове функции ВтеЗе^Еуеп!: в качестве четвертого параметра передается дескриптор окна приложения с преобразованием его к типу ОУУОКО. Внутри функции Т1 теРгос этот дескриптор используется для обратной связи с оконной процедурой при вызове функций 1пуа11’с1а1:еКес1: и 5епс1- Ме$$аде. Период срабатывания мультимедийного таймера задается макросом Т1М Е_РЕК100. Вычисляя минимальное, максимальное и среднее значения периода срабатывания таймера, мы отбрасываем первые 1’0 измерений. В программе константа 1’0 определе- на со значением 30. Это сделано, чтобы устранить погрешность измерений, обус- ловленную особенностями функционирования конвейерной системы процессора. Выполнение программы для периода, равного 1 мс, дает следующие итоговые результаты: гп1 пТ = 0.649, тахТ = 1.973, ауегаде = 1.00. Ну что ж, значение для средней величины периода срабатывания можно оце- нить на «отлично». Но почему такой большой разброс между минимальным и мак- симальным значениями? Чтобы выяснить это, заглянем в файл протокола Под.1x1, в котором сохранены данные всех ТУ измерений. Изучение его содержимого показывает, что значения в диапазоне 1,94-1,966, превышающие заданный период почти на 1 мс, встречаются с завидным постоян- ством — примерно один раз в 40 мс. Этот факт нетрудно объяснить. Система \У1пс1о\\ъ каждые 40 мс отдает ресурсы процессора примерно на 1 мс системному диспетчеру потоков. Другое крайнее значение, 0,649, встретилось только один раз на 1000 измерений, причем оно идет сразу вслед за значением 1,3. Такое впечатле- ние, что интервал 1,949, принадлежащий указанному выше диапазону, разбился на две неравные части: 1,3 и 0,649. В то же время подавляющее число измерений находится в диапазоне 0,971-0,999. Итоговые результаты для этого и других значений Т1МЕ_РЕК100 приведены в табл. 10.5. Таблица 10.5. Замеры реального интервала срабатывания мультимедийного таймера Т1МЕ-РЕН1ОО, мс Реальный интервал, мс Минимальный Максимальный Средний 1 0,649 1,973 1,000 2 1,177 3,424 2,000
Т1МЕ_РЕК1ОО, мс Реальный интервал, мс Минимальный Максимальный Средний 5 4,061 6,289 5,000 10 9,220 11,278 10,000 50 49,165 51,288 50,001 100 99,065 100,589 99,999 1000 999,747 1000,256 1000,001 Теперь обратим внимание на закомментированные инструкции с номерами #1 и #2. Их можно раскомментировать, чтобы выяснить, как влияет вызов функции ВтеВед1’пРелос1(1) на работу мультимедийного таймера. Проведение повторных ис- пытаний дает удивительный результат. Вызов функции 1лтеВед1пРепос1(1) никак не повлиял на работу мультимедийного таймера! Следовательно, с учетом сказан- ного выше о влиянии ВтеВедтпРелос! на загрузку ресурсов системы, лучше обой- тись без вызова этой функции. Проверим теперь работу мультимедийного таймера в жестких условиях «реаль- ного мира». Под жесткими условиями здесь понимаются интенсивные действия пользователя, многократно перемещающего с помощью мыши окно тестового прило- жения. Напомним, что стандартный таймер повел себя очень плохо в такой ситуа- ции. Например, для заказанного интервала 100 мс был получен разброс значений реального интервала срабатывания от 0,52 до 503,38 мс. Аналогичный эксперимент с приложением МтИтегТез! дал следующий ре- зультат: гтп’пТ = 99.57, тахТ = 100.62, ауегаде = 100.00. Таким образом, мультимедийный таймер позволяет создавать программы, фун- кционирующие почти в режиме реального времени. «Почти» — потому что \Упк1()\\ъ не является операционной системой реального времени, о чем уже гово- рилось в начале этой главы. Осталось сделать два заключительных замечания. Когда вы будете применять мультимедийный таймер для решения реальных задач, очевидно, что вы замените код профилировки в теле функции Т1 теРгос на некий реальный код. Не забудьте при этом учесть, что если время выполнения этого кода будет превышать период таймера Т1МЕ_РЕКЮО, то периодичность вызова функции ИтеРгос будет определяться уже не таймером, а тормозными характеристиками вашего кода. Прежде чем использовать мультимедийный таймер, хорошо подумайте, дей- ствительно ли это вам нужно? В очень многих случаях характеристики стандарт- ного таймера, несмотря на все его недостатки, позволяют решать те проблемы, которые должны быть решены в вашем приложении. Во всех таких случаях пред- почтение следует отдавать стандартному таймеру.
11 Библиотеки динамической компоновки □!_!_ Библиотеки динамической компоновки (бупаттс Нпк ИЪгапез (ВЕЕ)) являются ис- полняемыми файлами особого формата, которые содержат функции, данные или ресурсы, доступные для других приложений. Как следует из названия, библиоте- ки ВЬЕ загружаются при необходимости либо на стадии загрузки, либо на стадии выполнения приложения. Очевидно, что в рамках модели «клиент-сервер» ВЬЕ исполняет роль сервера, предлагающего дополнительную функциональность вашему приложению. При- ложения, использующие данную возможность, являются клиентами ВЕЕ. Особый формат модулей ВЕЕ предполагает наличие в них так называемых раз- делов импорта и экспорта. Раздел экспорта указывает те идентификаторы объек- тов (функций, классов, переменных), доступ к которым разрешен для клиентов. Подавляющее большинство ВЕЕ (за исключением ВЕЕ, содержащих только ресурсы) импортирует функции из системных ВЕЕ — кегпе132.с1 И, изегЗ2.с1 И, дсйЗ2.дII и других библиотек. Если ваш проект создается в среде Ухзпа! 81псйо, то его опции автоматически включают стандартный набор таких библиотек. Иногда в этот спи- сок требуется добавить дополнительные ВЕЕ, содержащие функции, необходи- мые вашему приложению. Например, в проектах, приведенных в главе 8, мы все- гда подключали дополнительно библиотеку сотс1:132.с111, чтобы использовать элементы управления общего пользования. В этой главе мы рассмотрим как можно создавать и использовать ВЕЕ-модули в ваших приложениях. Применение ВЕЕ может дать ряд преимуществ: □ Расширение функциональности приложения. ВЕЕ можно загружать в адрес- ное пространство процесса на этапе выполнения, что позволит программе, оп- ределив, какие действия от нее требуются, подгружать нужный код. Поэтому, разрабатывая приложение, можно предусмотреть расширение его функциональ- ности за счет ВЕЕ от других производителей программного обеспечения. □ Более простое управление проектом. Обычно большие проекты разбиваются на модули, разрабатываемые разными группами. Использование ВЕЕ упроща- ет отладку, тестирование и сопровождение проекта. □ Экономия памяти. Если одну и ту же ВЕЕ используют несколько приложений, то в оперативной памяти хранится только один ее экземпляр, доступный этим приложениям. Например, использование ВЕЕ-версии библиотеки С/С++
позволяет избежать многократного дублирования в памяти кода таких функ- ций, как зрп’гйГ, з1:г1епЛ Гореп и других. □ Разделение ресурсов. ВББ могут содержать такие ресурсы, как строки, рас- тровые изображения, шаблоны диалоговых окон. Этими ресурсами может вос- пользоваться любое приложение. □ Упрощение локализации. ВББ идеально подходит для локализации приложе- ний. Например, программа, содержащая только код без компонентов пользова- тельского интерфейса, может загружать ВЕЕ, реализующую компоненты лока- лизованного интерфейса. □ Возможность использования разных языков программирования. Например, пользовательский интерфейс приложения вы можете реализовать на МтсгозоЙ Ухзпа! Вазхс, а прикладную логику — на С++. Программа на УНпа! Ваыс может загружать ВЬЕ, написанные на С++, Фортране и других языках. Однако если из ВЬЕ экспортируются классы, это может привести к дополнительным про- блемам (например, из-за декорирования имен компилятором), которые вы дол- жны будете решать. Добавим, что без применения ВЕЕ было бы невозможным построение прило- жений по технологии СОМ1. В то же время, решение о том, нужно ли создавать собственные ВЕБ-библио- теки, должно приниматься с учетом всех плюсов и минусов технологии ВЕЕ. К ми- нусам относятся: □ Увеличение количества поставляемых компонент: кроме основного ЕХЕ-фай- ла потребуется отслеживать и все необходимые ВЕЕ-модули. □ Бо«лыпее время загрузки приложения (особенно при неявном способе загрузки и большом количестве ВЕБ-модулей). □ Необходимость синхронизации версий библиотек и версий использующих их клиентов. Пожалуй, это самая неприятная проблема в этой технологии. Очевидно, что при разработке небольших проектов (утилиты, тестовые прило- жения) вряд ли имеет смысл создавать ВББ для реализации отдельных функций. ОЬЬ и адресное пространство процесса Исполняемый код в ВББ не предполагает автономного использования. Содержи- мое каждого ВББ-файла загружается приложением и проецируется на адресное пространство вызывающего процесса. Это достигается либо за счет неявного свя- зывания при загрузке, либо за счет явного связывания в период выполнения. Только после этого для вызывающего потока становятся доступными функции или другие ресурсы библиотеки. В свою очередь, ВББ получает доступ ко всем ресурсам потока. Когда поток вызывает из ВББ какую-либо функцию, та считыва- ет свои параметры из стека потока и размещает в нем же собственные локальные переменные. Кроме того, любые созданные кодом ВББ объекты принадлежат вы- зывающему потоку или процессу — ВББ ничем не владеет. 1 Сотпропеп!; ОЬ]есТ Мос1е1 (СОМ) — спецификация для создания приложений с компонентной архитек- турой.
Другое приложение также может воспользоваться уже загруженной ВЕЕ, свя- зав ее с адресным пространством своего процесса. Важно понимать, что процесс, загрузивший ВЬЕ, получает собственную копию глобальных данных, используемых этой библиотекой. Это защищает ВЬЕ от оши- бок приложений, а процессы, использующие ВЕЕ, от взаимного влияния друг на друга. В тех случаях, когда реализация ВЕЕ требует работы с динамической памятью, будьте особенно внимательны к корректному освобождению ресурсов. Например, если ВЕЕ-функция вызывает У11±иа1АИос, система резервирует область в адресном пространстве того процесса, которому принадлежит поток, обратившийся к ВЕЕ- функции. ЕЕредположим, что ВЕЕ выгружается из адресного пространства про- цесса. В этом случае автоматического освобождения зарезервированной области не произойдет, так как система не фиксирует того, что она зарезервирована ВЕЕ- функцией. Считается, что эта область принадлежит процессу и освободится толь- ко если поток процесса вызовет \йП:иа1Егее или завершится сам процесс. Чтобы отслеживать подобные ситуации, ВЕЕ должна содержать особую функ- цию — 0ИМа1п, которая рассматривается ниже. Создание собственной О1_1_ Во многих случаях создать библиотеку ВЕЕ проще, чем приложение, потому что она является лишь набором автономных функций, пригодных для использования любой программой или другой ВЕЕ. Зачастую в ВЕЕ нет кода, предназначенного для создания окон или для обработки сообщений. Рассмотрим создание ВЕЕ на примере проектирования библиотеки МуИЬ.сШ, содержащей единственную функцию \/о1с1 Рг1 ггЬ( 1РСТ8ТК ^ехВ ; которая является оболочкой для вызова функции МеззадеВох. Сомнительно, что эта библиотека найдет какое-либо практическое применение, но для объяснения рассматриваемой технологии она вполне подходит. Однако если функция Рпп! будет объявлена с прототипом записанным выше, то поток, загрузивший библиотеку МуИЬ.сШ, не сможет вызвать данную функцию, так как она окажется для него невидимой. Чтобы «открыть» какие-то из своих объектов для внешнего мира, ВЕЕ должна объявить их экспортируемыми с помо- щью модификатора___дес1_8рес(с111ехрогЕ). Кроме этого, в объявление функции сле- дует добавить модификатор ехЕегп "С", запрещающий компилятору С++ осуществ- лять так называемое декорирование имен. ПРИМЕЧАНИЕ--------------------------------------------------------------- Декорирование {тапдПпд} имен — специфическое явление, присущее компиляторам языка С++. Дело в том, что компилятор С++ всегда добавляет к имени функции сокращенный список фор- мальных параметров и тип возвращаемого значения. Компилятор делает это, чтобы решить про- блему идентификации перегруженных функций и правильно связать вызовы функций в программе. Таким образом, если вы забудете добавить модификатор ехЕегп "С", то в отком- пилированном модуле МуИЬ.сШ имя нашей функции Рпп! превратится в нечто по- хожее на ?РппЕ@@УАХРВВ@7. В этом случае при явном связывании ВЕЕ с клиент- ским приложением возникнут проблемы.
В листинге 11.1 приведен текст проекта для создания библиотеки МуИЬ.сШ. Листинг 11.1. Проект МуЫЬ ////////////////////////////////////////////////////////////////////// // МуЕйэ.П - заголовочный файл ОИ-модуля #1 Тпс1еТ _МУЕ1В_Н #с1еГ1 пе _МУЕ1В_Н #1пс1ис1е <\л/1 пс!о\л/8 . Н> #с1еГ1 пе ЕХРОВТ ехРегп "С" _с1ес!зрес(сП 1 ехрог^) // Здесь определяются прототипы экспортируемых функций ЕХРОЕТ уопб РгппР(ЕРСТ8ТР РехР); #епсИТ // -МУЕ1В-Н ////////////////////////////////////////////////////////////////////// // МуЕпЬ.срр - исходный файл ОЕЕ-модуля #1пс1ис1е <зРсП о. Н> #1пс1ис1е "МуШэ.1э" 1 пр соипР = 0; ЕХРОРТ уо1с1 РптР(ЕРСТ8Тк РехР) { соипР++; сИаг Р1Р1е[40]; зрп’ пРР(Р1 Р1 е, "МуШэ: Ргппр: вызов %сГ, соипР); МеззадеВохСШЕЕ, РехР, Р1Р1е, МВ_ОК); } ////////////////////////////////////////////////////////////////////// В заголовочном файле использованы «стражи включения» — директивы услов- ной компиляции #1’Гпс1еГ и #епсйГ. Благодаря им исключается повторное включение этого файла в одной единице трансляции, что важно при построении многофайло- вых проектов. Директивой #с1еР1 пе ЕХРОРТ ехРепп "С" _с1ес1 зрес(611 ехрогР) определяется макрос ЕХРОКТ, используемый в дальнейшем коде в качестве допол- нительного спецификатора заголовков экспортируемых функций. А теперь — внимание! Чтобы скомпилировать ВББ-модуль в среде Ухзпа! С++ 6.0, вы должны создать проект типа «АУш32 Вупаппс-Бшк ЫЬгагу»1. Для этого выберите команду меню ЕНе ► Мем и в появившемся диалоговом окне Мем выберите вкладку Рго]’есР. Последующие действия таковы: □ введите нужное имя проекта, например МуИЬ; □ укажите папку для размещения проекта; □ выберите опцию \Л/1’п32 Оупатк-Ипк ИЬгагу; □ нажмите кнопку ОК. Далее в появившемся окне \Л/1п32 Оупагтс-Ипк ИЬгагу - 51ер 1 оГ 1 выберите оп- цию Ап етрру ОН рго]ес1: и нажмите кнопку Нт$Ь. 1 До сих пор мы работали с проектами типа «АУт32 АррИсаЕюп».
Когда проект создан, добавьте в его состав файлы МуИЬ.Н и МуИЬ.срр и откомпи- лируйте (Р7). Что обеспечивает среда при построении проекта этого типа? Чтобы получить ответ на этот вопрос, загляните в папку ОеЬид. В ней вы найдете два новых файла: □ МуИЬ.сИ1 — ВББ-файл, содержащий собственно библиотеку; □ МуИЬ.НЬ — ЫВ-файл, содержащий список идентификаторов, импортируемых из ВЬЕ. Этот Е1В-файл, называемый также библиотекой импорта, нужен при компоновке любого ЕХЕ-модуля, ссылающегося на такие идентификаторы. Компоновщик также вставляет в конечный ВЕЕ-файл раздел экспорта, содер- жащий список экспортируемых идентификаторов. В раздел экспорта помещаются также относительные виртуальные адреса этих идентификаторов. Вызов функций из ОЬЬ Существует три способа загрузки ВЕЕ: а) неявная, б) явная, в) отложенная. Неявная загрузка ОЬЬ Для построения приложения, рассчитанного на неявную загрузку ВЕЕ, необходи- мо иметь: □ Библиотечный Н-файл с описаниями используемых объектов из ВББ (прото- типы функций, объявления классов и типов). Этот файл используется компи- лятором. □ Е1В-файл со списком импортируемых идентификаторов. Этот файл нужно до- бавить в настройки проекта (в список библиотек, используемых компонов- щиком). Компиляция проекта осуществляется обычным образом. Используя объект- ные модули и Б1В-файл, а также учитывая ссылки на импортируемые идентифика- торы, компоновщик собирает загрузочный ЕХЕ-модуль. В этом модуле компо- новщик помещает также раздел импорта, где перечисляются имена всех необходимых ВББ-модулей. Для каждой ВББ в разделе импорта указывается, на какие символьные имена функций и переменных встречаются ссылки в коде ис- полняемого файла. Эти сведения будет использовать загрузчик операционной системы. Что же происходит на этапе выполнения клиентского приложения? После за- пуска ЕХЕ-модуля загрузчик операционной системы выполняет следующие опе- рации: □ Создает виртуальное адресное пространство для нового процесса и проецирует на него исполняемый модуль. □ Анализирует раздел импорта, определяя все необходимые ВЕЕ-модули и тоже проецируя их на адресное пространство процесса. Заметим, что ВББ может им- портировать функции и переменные из другой ВББ. А значит, у нее может быть собственный раздел импорта, для которого необходимо повторить те же дей- ствия. В результате на инициализацию процесса может уйти довольно длитель- ное время.
После отображения ЕХЕ-модуля и всех ВЕЕ-модулей на адресное простран- ство процесса его первичный поток готов к выполнению, и приложение начинает работу. Еще следует отметить, что загрузчик ищет ВЕЕ на дисковых устройствах в сле- дующей последовательности: □ Каталог, содержащий ЕХЕ-файл. □ Текущий каталог процесса. □ Системный каталог □ Основной каталог АУтскпуз. □ Каталоги, указанные в переменной окружения РАТН. Чтобы проверить, как работает неявная загрузка ВЕЕ, создадим клиентское приложение 1тр1СИеп1, использующее функцию Рпп1: из библиотеки МуИЬ.сШ. Текст тестовой программы приведен в листинге 11.2. Листинг 11.2. Проект 1тр1С1!еп1 ////////////////////////////////////////////////////////////////////// // 1тр1С11егД.срр #1пс1ис1е <м1 пс1омз. И> #1пс1ис1е "МуШэ.И" Ш 1лПМАР1 пМа1 п(Н1М8ТАМСЕ И1пзШсе, Н1М5ТАМСЕ ИРгеу1пзШсе, ЕР8ТР 1рСтс1Е1пе, Ш пСтбЗГюУл/) { Рг1гД("Не11о!"); РгШСТНе ехрентегД шз а зиссезз!"); РгШС'Ву!"); ге^игп 0; } ////////////////////////////////////////////////////////////////////// Создайте проект типа \Л/1’п32 АррИсаИоп и добавьте в его состав файлы МуНЬ.Н и 1тр1СИеп1.срр. Скопируйте в папку проекта библиотеку импорта МуИЬ.НЬ, которая была пост- роена в предыдущем проекте. Добавьте в настройках проекта ссылку на нужную нам библиотеку импорта. Для этого выберите команду меню РгсдесЕ ► БеМппдз и в появившемся диалоговом окне Рго]’ес1: БеМппдз выберите вкладку Нпк. Последующие действия таковы: □ в списке Са1едогу выберите опцию 6епега1; □ в конце списка 0Ь]есЕ/НЬгагу тос1и1е$ добавьте имя нашей библиотеки — МуИЬ.НЬ; □ нажмите кнопку ОК. Откомпилируйте проект (Е7). Для того чтобы ЕХЕ-файл смог выполняться, в одной папке с ним должен на- ходиться используемый ВЕЕ-файл. Так как мы будем запускать приложение из среды Ухзпа! 81псйо, скопируйте файл МуИЬ.сШ в папку ОеЬид. Запустите клиентское приложение. На экране должно появиться диалоговое окно, показанное на рис. 11.1 слева. Если вы будете продолжать нажимать кноп- ку ОК, то должны увидеть еще два окна, (рис. 11.1 по центру и справа). Это означа- ет, что клиентское приложение успешно выполняет свою работу, пользуясь серви- сом (функцией Рип!), предоставляемым сервером МуИЬ.сШ.
МуЫЬ: РпгЛ: вызов 2 ТИе ехрептеп1 шаз а зиссегз! □ К Рис. 11.1. Результаты трех вызовов функции Рпп1 из библиотеки МуЫЬ.сШ Завершая эксперимент, давайте посмотрим с помощью отладчика Ухзпа! 81псйо, в какой момент происходит загрузка нашей библиотеки? Информацию, выводи- мую отладчиком, следует смотреть в окне Ои±ри1:. Нажмите клавишу ЕЮ для запус- ка отладчика в пошаговом режиме. Появившаяся на левом поле окна редактирова- ния желтая стрелка показывает строчку кода, которая будет выполнена после очередного нажатия клавиши ЕЮ. После первого нажатия (когда еще не выполни- лось ни одной строчки кода) в окно Ои^ри! выводится следующая информация: Коабеб 'С:\ШММТ\8у8^ет32\гДб11.611', по гШсМпд зутЬоПс 1пТогжД1оп Тоыпб. боабеб 8утЬо18 Тог 'Е:ХРгодШпАр1 \ 1тр1 С11 епШеЬидХМуЫЬ.611' боабеб 'С:\ШММТ\8у8^ет32\и8ег32.с11Г , по гШсМпд зутЬоПс 1пТоггШ1оп Тоипб. боабеб 'С:\М1 ММТ\8у818ет32\кете132.611', по гШсМпд зутЬоПс 1пТоггтМ1оп Тоипб. боабеб 'С:\ШММТ\8у8Тет32\60132.0И', по таТсМпд зутЬоПс 1пТоптаТ1оп Тоипб. Таким образом, мы видим, что библиотека МуИЬ.с1 П загружается действительно на этапе загрузки приложения — после загрузки системной библиотеки п±д11.дН. В заключение отметим, что способ неявной загрузки ВЬЕ используется доволь- но широко из-за своей простоты. Однако этому способу присущи определенные недостатки и ограничения: □ Все подключенные ВЬЕ загружаются всегда, даже если в течение всего сеанса работы программа ни разу не обратится ни к одной из них. □ Если хотя бы одна из требуемых ВЕЕ отсутствует, то загрузка исполняемого файла прекращается, а система выдает сообщение наподобие следующего: «А гершгес! ВЕЕ Ейе МуЫЪЯИ \\а8 по! Гоппе!» — даже если отсутствие этой ВЕЕ некритично для исполнения программы в нужном для вас режиме. Явная загрузка ОЬЬ Явная загрузка устраняет отмеченные выше недостатки ценой некоторого услож- нения кода. Программисту приходится самому заботиться о загрузке ВЕЕ и под- ключении экспортируемых функций. Зато явная загрузка позволяет подгружать ВЕЕ по мере необходимости и дает возможность программе обрабатывать ситуа- ции, возникающие при отсутствии ВЕЕ. В случае явной загрузки процесс работы с ВЕЕ происходит в три этапа: Е Загрузка ВЕЕ с помощью функции ЕоасШЬгагу (или ее расширенного аналога 1_оас11лЬгагуЕх). В случае успешной загрузки функция возвращает дескриптор НИЬ типа НМОО1ЛЕ, что позволяет в дальнейшем обращаться к этой ВЕЕ. 2. Вызовы функции СеЕРгосАс1с1ге55 для получения указателей на требуемые функ- ции или другие объекты. В качестве первого параметра функция СеЕРгосАс1с1ге55 получает дескриптор НИЬ, в качестве второго параметра — С-строку с иденти-
фикатором импортируемого объекта. Далее полученный указатель использу- ется клиентом. Например, если это указатель на функцию, то осуществляется вызов нужной функции. 3. Когда загруженная динамическая библиотека больше не нужна, рекомендуется ее освободить, вызвав функцию ЕгееПЬгагу. Освобождение библиотеки не озна- чает, что операционная система немедленно удалит ее из памяти. Задержка выгрузки предусмотрена на тот случай, когда эта же ВЬЕ через некоторое вре- мя вновь понадобится какому-то процессу. Но если возникнут проблемы с опе- ративной памятью, \У1пс1о\\ъ в первую очередь удаляет из памяти освобожден- ные библиотеки. Чтобы продемонстрировать, как работает явная загрузка ВЬЕ, создадим кли- ентское приложение Ехр1С11епЕ, использующее уже знакомую нам функцию Рпп1: из библиотеки МуПЬ.сНЕ Текст тестовой программы приведен в листинге 1ЕЗ. Листинг 11.3. Проект Ехр1СКеп1: ////////////////////////////////////////////////////////////////////// // Ехр! СИегД.срр #1пс1ис1е <м1 пбомз. И> Ш 1л/1МАР1 Ш пМа1 п(Н1М5ТАМСЕ Щпзбапсе, Н1М5ТАМСЕ ПРгеу1пзбапсе, ЕРЗТР 1рСтс1Е1пе, Ш пСтбЗГюУл/) { // Дескриптор загружаемой ОЕЕ НМООСЕЕ НЕИэ; // Загружаем библиотеку НЕНэ = Еоас1Е1 Ьгагу("МуЕл Ь. сП 1"): 1Г (!Г|Е1Ь) { МеззадеВохШОН, "Библиотека МуЕЧэ.сП! не найдена", "Еггог", МЕНЮ; гебигп 1; } // МУРРОС - тип указателя на функцию, совместимый с прототипом функции, // вызываемой из ОЕЕ буребеГ уо1с1 (*МУРРОС) (ЕРСТЗТР збг); // Объявляем указатель на функцию типа МУРРОС МУРРОС рМуРгос; // Получаем адрес функции РгпгтЕ рМуРгос = (МУРРОС)(БебРгосАс1с1гезз(М1Ь, "Рг1пб")); Н (!рМуРгос) { МеззадеВохШОН, "В ОБЕ отсутствует функция РгШ", "Еггог", МВ_ОК); гебыгп 1; } // Используем функцию РгШ из ОН рМуРгос("Не1По!"); рМуРгосС'ТИе ехрег1тегтЕ маз а зиссезз!"); рМуРгос("Ву!"); // Выгружаем библиотеку из памяти ЕгееИЬгагуСИИЬ); гебигп 0; } //////////////////////////////////////////////////////////////////////
Создайте проект типа \Л/1 п32 АррНсаБоп и добавьте в его состав файл ЕхрЕСИепЕсрр. Обратите внимание на то, что заголовочный файл МуНЬ.Н в клиентском прило- жении, использующем явную загрузку, подключать не надо. Также нет нужды ука- зывать библиотеку импорта в настройках проекта. После компиляции приступим к тестированию программы. Библиотечный мо- дуль Му 1_1’Ь.с111 должен находиться в одном каталоге с ЕХЕ-модулем. Если програм- ма запускается из среды Ухзпа! 81исйо, то следует скопировать МуИЬ.сШ в папку ОеЬид. Проверка выполнения приложения показывает, что его поведение не отли- чается от поведения программы 1тр1С11’епЕехе. Отложенная загрузка О1_1_ Этот вариант загрузки появился значительно позже первых двух видов, описан- ных выше. Например, в среде Ухзпа! 81псйо поддержка данной возможности реа- лизована, начиная с шестой версии. ВЕБ отложенной загрузки {йе1ау-1оай ОБЕ) — это неявно связываемая ВЕЕ, которая не загружается до тех пор, пока ваш код не обратится к какому-нибудь экспортируемому из нее идентификатору. Такие ВЕБ могут быть полезны в следу- ющих ситуациях: □ Если ваше приложение использует несколько ВЕБ, его инициализация может занимать длительное время, требуемое загрузчику для проецирования всех ВЕЕ на адресное пространство процесса. ВЕБ отложенной загрузки позволяют ре- шить эту проблему, распределяя загрузку ВЕБ в ходе выполнения приложения. □ Если приложение предназначено для работы в различных версиях ОС, то часть функций может появиться лишь в поздних версиях ОС и не использоваться в текущей версии. Но если программа не вызывает конкретной функции, то ВЕЕ ей не нужна, и она может спокойно продолжать работу. При обращении же к не- существующей функции можно предусмотреть выдачу пользователю соответ- ствующего предупреждения. Использовать отложенную загрузку ВЕБ достаточно просто, так как большую часть работы берут на себя компилятор и компоновщик, вам необходимо только задать требуемые настройки в проекте клиентского приложения. Напомним, что для реализации метода неявной загрузки ВЕБ мы добавляли в список библиотек, используемых компоновщиком, требуемую библиотеку им- порта (в нашем примере — МуИЬ.НЬ). Для реализации метода отложенной загрузки требуется также повторить это действие, но дополнительно в указанный список нужно добавить еще и системную библиотеку импорта с1е1ау1’тр.11’Ь. Кроме этого, требуется добавить в опциях компоновщика флаг /с1е1ау1оас1:Му НЬ.сШ. Перечисленные настройки заставляют компоновщик выполнить следующие операции: □ внедрить в ЕХЕ-модуль специальную функцию _с1е1ау1_оас1Не1рег; □ удалить МуИЬ.сШ из раздела импорта исполняемого модуля, чтобы загрузчик операционной системы не пытался выполнить неявную загрузку этой библио- теки на этапе загрузки приложения; □ добавить в ЕХЕ-файл новый раздел отложенного импорта со списком функ- ций, импортируемых из МуИЬ.сШ.; □ преобразовать вызовы функций из ВЬЬ к вызовам _с1е1ау1оас1Не1рег.
На этапе выполнения приложения вызов функции из ВЕЕ реализуется обра- щением к _с1е1ау1_оас1Не1рег. Эта функция, используя информацию из раздела отло- женного импорта, вызывает сначала ГоасШЬгагу, а затем Се^РгосАддгезз. Получив адрес ВЕЕ-функции, _с1е1ау1_оас1Не1рег делает так, чтобы в дальнейшем эта ВЕЕ- функция вызывалась напрямую. Отметим, что каждая функция в ВЕЕ настраива- ется индивидуально при первом ее вызове. Проверим описанную технологию на примере клиентского приложения Ое1ау- 1_оас1С11еп±, исходный текст которого, приведенный в листинге 11.4, по существу не отличается от текста программы 1тр1С11’епЕ Листинг 11.4. Проект Ое1ау1_оас1СНепк ////////////////////////////////////////////////////////////////////// // 0е1 ау1_оас1С11 епГ.срр #1пс1ыс1е <\л/1 пс!о\л/8 . Н> #1пс1ыс1е "МуЕТэТ" 1 пТ ШМАР1 Ш пМа!п(Н1М8ТАМСЕ МпзГапсе, Н1М5ТАМСЕ ПРгеу1пзГапсе, ЕР8ТР 1рСтс1Е1пе, 1 пТ пСтс!5Но\л/) { Рг1пГ("Не!1о!"); РгШС'ТПе ехрептепГ \л/аз а зиссезз!"); РгШС'Ву!"); геГигп 0; } ////////////////////////////////////////////////////////////////////// Создайте проект типа «АУш32 АррНсаПоп» и добавьте в его состав файлы МуИЬ.Н и Ое1ауЕоаПСНепЕ.срр. Скопируйте в папку проекта библиотеку импорта МуПЬ.ИЬ. Добавьте в настройки компоновщика ссылки на две библиотеки импорта: МуПЬ.ИЬ и де1ау1тр.11Ь. Как обычно, действуйте через Рго]ес1 ► БеМппдз ► Ипк ► ОЬ^есЕ/ НЬгагу тос1и1е$. Затем, оставаясь на вкладке Ипк, добавьте в окне Рго]ес1 ОрЕ’опз флаг для компоновщика /с1е1ау1оас1:Му11’Ь.с111. После компиляции проекта скопируйте в папку ОеЬид файл Му И Ь. с111 и запусти- те ЕХЕ-файл на выполнение. Результаты будут такими же, как и при запуске при- ложения 1тр1СПеп1: (см. рис. 11.1). Различия в выполнении этих приложений можно оценить только при помощи отладчика. Нажмите клавишу ЕЮ для запуска отладчика в пошаговом режиме. После первого нажатия, когда еще не выполнилось ни одной строчки кода, в окне ОиГриГ появляется следующая информация: Еоас1ес1 Т:\ШММТ\зузГет32\пГс111 ,с!1Г , по таГсМпд зутЬоПс 1пГоппаГ1оп Толпе!. Еоас!ес1 'С:\И1ММТ\зузГет32\кеппе132.с11Г , по таГсМ’пд зутЬоПс 1пГоптаГ1оп Толпе!. Мы видим, что библиотека МуИЬ.сШ на этапе загрузки приложения не загружа- ется. И только после вызова ВЕЕ-функции Рпп1("Не11о!") — еще два нажатия кла- виши ЕЮ — в окне ОиЕриЕ появляется: Еоас!ес1 зутЬо!з Гог 'Е:\Ргод1л/1 пАр!\0е!ауЕоасЮ! 1 епГ\0еЬлд\МуЕ1Ь.сП 1' Еоас!ес1 'С:\1л!1ММТ\зузГет32\лзег32.с11Г , по таГсМпд зутЬоПс 1пГогтаГ1оп Толпе!. Еоас!ес1 'С:\ШММТ\зузГет32\60132.0ЕЕ', по таГсМпд зутЬоПс 1пГогтаГ1оп Толпе!. При последующих вызовах функции Рип! повторной загрузки библиотеки МуЕ1’Ь.с111 не происходит. Таким образом, все работает так, как и было обещано для метода отложенной загрузки!
Загрузка ресурсов из О1± Помимо функций, динамические библиотеки могут содержать и ресурсы — строки, пиктограммы, рисунки, и т. д. Хранение ресурсов в ВЕЕ особенно удобно при со- здании приложений с многоязычным интерфейсом. В этом случае, заменив одну ВЬЕ на другую, мы заменяем все надписи в программе, скажем, с русского языка на английский, не меняя при этом кода приложения! Аналогично можно менять пиктограммы, внешний вид диалогов и т. д. Создание ВЬЕ, содержащей только ресурсы, осуществляется аналогично со- зданию оьь с исполняемым кодом, но вместо файла *.срр в проект добавляется файл *.гс. Технология добавления в проект ресурсов различных типов изложена в главе 5. Однако следует учесть некоторые тонкости. Предположим, что нам нужно со- здать библиотеку МуОН.сШ, содержащую только ресурсы, при помощи проекта МуОИ. Напомним, что после вызова редактора ресурсов соответствующего типа и подго- товки самого ресурса (например, таблицы строк) необходимо сохранить создан- ный ресурс в файле МуОИ.гс. При этом редактор ресурсов автоматически создает заголовочный файл гезоигсе.Н, содержащий все идентификаторы, встречающиеся в файле МуОИ.гс. Впоследствии файл гезоигсе.Н должен быть подключен директи- вой #1’пс1ис1е к тем файлам клиентского приложения, в которых используются ре- сурсы из библиотеки МуОИ.сШ. До сих пор нас устраивало имя заголовочного файла гезоигсе.Н, так как он ис- пользовался только в одном проекте. Теперь же, создавая ВЕЕ, предназначенную для экспорта в другие проекты, мы должны побеспокоиться об уникальности име- ни ее заголовочного файла. Поэтому в технологической цепочке подготовки про- екта оьь к компиляции должна появиться операция «изменить имя файла гезоигсе.Н на имя МуОЦ.Н». Кроме этого, в настройках компоновщика в проекте ВЕЕ необходимо добавить флаг /поегйгу. Это флаг означает, что ВЕЕ содержит только ресурсы, и поэтому ей не нужна входная функция 0ИМа1п. Рассмотрим в качестве примера, как можно расширить функциональность про- граммы 1тр1С11’еп1: (листинг 1Е2), если все строки, которые она использует, вынес- ти в отдельную ВЕЕ. Но сначала создадим две библиотеки: МуКезЕпд.сШ — с англо- язычным текстом, и МуКезКиз.сШ — с русскоязычным текстом. Библиотека МуКезЕп^.сШ. Для ее создания выполните следующие шаги: Е Создайте проект типа \Лйп32 Оупагтс-Ипк ИЬгагу с именем МуКезЕпд. 2. Вызовите окно редактора таблицы строк, действуя через ТпзегЕ ► Кезоигсе ► 5Ет’пд ТаЫе ► Мем. 3. Введите в таблицу три строки: Идентификатор Строка Ю8_1 Не11о! Ю8_2 ТНе ехрептеп!: \л/аз а зиссезз! Ю8_3 Ву! 4. Сохраните таблицу строк (ЕНе ► 5ауе Аз) в файле МуКезЕпд.гс. 5. Откройте файл МуКезЕпд.гс в текстовом режиме и замените ссылку на заголо- вочный файл гезоигсе.Н ссылкой на МуКезЕпд.Н.
6. Откройте папку проекта и переименуйте файл гезоигсе.Н на МуКезЕпд.Н. 7. Добавьте в состав проекта (вкладка Н1е\/1е\л/) файл ресурсов МуКезЕпд.гс и заго- ловочный файл МуКезЕпд.Н. 8. В настройках компоновщика Рго]ес1: ► БеЬБпдз ► Ипк ► Рго]ес1: ОрВопз добавьте флаг/поегйгу. 9. Откомпилируйте (Е7). В папке ОеЬид вы найдете библиотечный модуль МуКезЕпд.сШ. Библиотека им- порта *.ПЬ в данном случае не нужна, поэтому она и не создается компоновщиком. Библиотека МуКезКиз.сШ. создается аналогично при помощи проекта МуКезКиз. Единственное отличие — содержание строк, которые вы введете в таблицу строк: Идентификатор Строка Ю8_1 Привет! Ю8_2 Опыт удался! Ю8_3 Пока! Ну и конечно, соответствующие файлы должны иметь имена МуКезКиз.гс и МуКезКиз.Н. После компиляции будет создан модуль МуКе$Ки$.с111. Клиентское приложение ВПКезСНепС 1. Создайте проект типа \Лйп32 АррПсаБоп с именем ОИКезСИепЕ 2. Добавьте в его состав файл ОПКезСНепЕ.срр с текстом, приведенным в листинге 11 .5, а также файлы МуНЬ.Н, МуКезЕпд.Н и МуКезКиз.Н. Листинг 11.5. Проект ОНРезСКеп! ////////////////////////////////////////////////////////////////////// // ОШезСПепЬ.срр // // Запуск программы из командной строки с одним параметром: // ОПВезСПепЬ.ехе Епд - для англоязычного пользователя // ОПВезСПепЬ.ехе Виз - для русскоязычного пользователя // #1пс1ис1е <\л/1 пс!о\л/8 . Н> #1пс1ис1е "МуНЬ.Н" #1пс1ис1е "МуВезЕпд.Н" Ш 1л/11\1АР1 Ш пМапп(Н1М5ТАМСЕ ПТпзЬапсе, Н1М5ТАМСЕ НРгеу1пзЬапсе, ЕРЗТЕ 1рСтс1Ипе, 1пЬ пСтсЕНом) { сНаг стс1Ипе[100]: зЬгсру(стс1Ипе, 1рСтсИ1пе): НМОООЬЕ НИЬ; 1Г (!зЬгстр(стс1Е1пе, "Епд")) НИЬ = Еоас1ЕпЬгагуЕх("МуРезЕпд.сП 1", О, Е0А0_ЫВВАВУ_А8_0АТАЕ1ЕЕ); е!зе 1Г (!зЬгстр(стс1Е1пе, "Риз")) НИЬ = Еоас1ЕпЬгагуЕх( "МуРезРиз.сП 1", О, Е0А0_ЫВВАВУ_А8_0АТАЕ1ЕЕ); е!зе { МеззадеВохШОЕЕ, "Отсутствует или неверно задан параметр в командной строке", "Еггог", МВ_ОК); геЬыгп 0: } сНаг ЬыГЕЮО]: Ьоас18Ьг1пд(ННЬ, Ю8_1, ЬыГ, 99): РгШ(ЬиГ): продолжение &
Листинг 11.5. {продолжение} 1_оас181зг1 пд(ИНЬ, Ю8_2, ЬиТ, 99); Рп’пЬ(ЬиТ); Ьоас18Ьг1 пд(Ы_1Ь, Ю8_3, ЬиТ, 99); Рп’пЬ(ЬиТ); геЬигп 0; } ////////////////////////////////////////////////////////////////////// Как следует из комментариев в тексте программы, она должна запускаться в ре- жиме командной строки с параметром, задающим тип интерфейса. Если значени- ем параметра является Епд, программа выводит сообщения на английском языке, если задано значение Киз — на русском. Обратите внимание на то, что загрузка библиотеки ВЕЕ осуществляется вызо- вом функции 1_оас11лЬгагуЕх, хотя можно было бы использовать и 1_оас11лЬгагу. Вызов функции 1_оас11лЬгагуЕх здесь предпочтительней, так как благодаря значению ее тре- тьего параметра 1_0А0_1_1ВКАКУ_А5_0АТАЕ11_Е система узнает, что ВЕЕ содержит только данные. Это позволяет ускорить загрузку, так как системе достаточно лишь спрое- цировать ВЕЕ на адресное пространство процесса. После компиляции проекта не забудьте скопировать в папку ОеЬид библиотеч- ные модули МуКезЕпд.сШ, Му Кез К из. с! П и МуИЬ.сШ. Запустите программу В ПКезСН е пЕ. ехе на выполнение из командной строки сначала с параметром Епд, а затем — с пара- метром Виз. Очевидно, что на рынке программных продуктов приложение ОИКезСПепЕехе, обладая многоязычным интерфейсом, будет иметь явные преимущества перед при- ложением 1тр1СПеп1:! Функция входа/выхода Предположим, что вашей библиотеке динамической компоновки требуется неко- торая инициализация и деинициализация. Например, если в ВЕЕ при ее загрузке выделяются какие-то ресурсы, то при ее освобождении эти ресурсы также должны освобождаться. Особое значение имеет деинициализация: поскольку при отключении ВЕЕ от адресного пространства процесса выделенная ею память сама собой не освобожда- ется, а открытые файлы — не закрываются, ВЕЕ должна самостоятельно обеспе- чивать «уборку мусора». Для решения указанных проблем вы можете включить в состав ВЕЕ специаль- ную функцию точки входа ОИМа1п. Эта функция вызывается операционной систе- мой в следующих случаях: □ когда ВЕЕ проецируется на адресное пространство процесса (подключение ВЕЕ); □ когда процессом, загрузившим ВЕЕ, вызывается новый поток; □ когда завершается поток, принадлежащий процессу, который связан с ВЕЕ; □ когда процесс освобождает ВЕЕ (отключение ВЕЕ). Функция точки входа ОИМа1п имеет следующий прототип: В001_ 1л/1МАР1 011Ма1п( Н1М8ТАК1СЕ ЫпзЬОЫ, // дескриптор ОЫ-модуля
ОШЕО ЦШеазоп, // флаг причины вызова функции 1Р\/0Ю 1руЕезегуеб // дополнительная информация В момент вызова функция получает информацию от операционной системы через свои параметры. Первый параметр, ЫпзЮП, принимает значение дескриптора модуля ВЬЕ, яв- ляющееся, по сути, виртуальным адресом загрузки ВЬЕ. Если в библиотеке име- ются вызовы функций, которым нужен данный дескриптор, необходимо сохранить значение ЫпзЮП в глобальной переменной. Второй параметр, Гдхл/Кеазоп, может принимать одно из следующих значений: Значение Интерпретация О1_1__РкОСЕ55_АТТАСН Уведомление о том, что О1_1_ загружена в адресное пространство процесса либо в результате его старта, либо в результате вызова функции 1_оас11_|Ьгагу. ОИ_ТНкЕАО_АТТАСН Уведомление о том, что текущий процесс создал новый поток. Это уведомление посылается всем ЭИ, подключенным к процессу. Вызов ЭН Маю происходит в контексте нового потока. О1_1__ТНкЕАО_ОЕТАСН Уведомление о том, что поток корректно завершается. Вызов ОН Маю происходит в контексте завершающегося потока. О1_1__РкОСЕ88_ОЕТАСН Уведомление о том, что О1_1_ отключается от адресного пространства процесса в результате одного из трех событий: а) неудачное завершение загрузки О1_1_; б) вызов функции РгееЫЬгагу; в) завершение процесса. Если при вызове функции используется первый параметр со значением 01_1__РК0СЕ55_АТТАСН, то по значению третьего параметра можно выяснить, каким способом загружается ВЕЕ. При явной загрузке параметр 1руКе$еп/ес1 равен нулю, а при неявной загрузке принимает ненулевое значение. Следует отметить, что: □ Поток, вызвавший 0ИМа1п со значением 0П_РК0СЕ55_АТТАСН, не вызывает по- вторно 0ИМа1п со значением О1_1__ТНКЕАО_АТТАСН. □ Когда ВЕЕ загружается вызовом функции 1_оас11лЬгагу, существующие потоки не вызывают 0ИМа1п для вновь загруженной библиотеки. □ Функция 0ИМа1п не вызывается, если поток или процесс завершаются по при- чине вызова функции Тептп’па1:еТНгеас1 или ТептппабеРгосезз. Поскольку функция 0ИМа1п должна обрабатывать все возможные причины сво- его вызова, ее код обычно выглядит примерно так: В001_ АР1ЕМТВУ 011Ма1п( НАМО1Е ЬМосЫе, ОШЕО (Меазоп, ЬР70Ю 1рЕезеп/ес1) { змПсб (сМеазоп) { сазе 0ЕЕ_РЕ0СЕ88_АТТАСН: // Выполняем действия по инициализации (например, выделение памяти) // Если произошла ошибка, то возвращаем ЕАЕ8Е; гебыгп ТРЕЕ; сазе ОЕЕ_ТНРЕАО_АТТАСН: // Выполняем действия по инициализации, связанные с новым потоком Ьгеак; сазе ВЕЕ_ТНРЕАВ_ВЕТАСН:
// Освобождаем переменные, связанные с потоком Ьгеак; сазе 0ЕЕ_РВ0СЕ88_0ЕТАСН: // Выполняем все действия по деинициализации Ьгеак; } геЕигп ТЕМЕ: // этот код возврата игнорируется } Если ВЬЕ проектируется с учетом ее использования в многопоточном прило- жении, то ей может потребоваться так называемая локальная память потока (ТЕ8). Действия по инициализации и деинициализации такой памяти также осуществ- ляются в функции 0ИМа1п. Пример использования функции 0ИМа1п приведен далее в листинге 11.7. Локальная память потока (Т1_§) Мы уже говорили, что процесс, загрузивший ВЕЕ, получает собственную копию глобальных данных, используемых этой библиотекой. Это защищает процессы, использующие ВЕЕ, от взаимного влияния друг на друга. Но если ВЕЕ использу- ется несколькими потоками одного процесса, то глобальные переменные библио- теки разделяются всеми потоками. Это может создавать неприятные проблемы. Рассмотрим, например, многопоточное приложение М±С11еп± (листинг 11.6), ис- пользующее библиотеку динамической загрузки МуИЬ.сШ. Листинг 11.6. Проект М1С1|еп1 ////////////////////////////////////////////////////////////////////// // МЬСПепЬ.срр #1пс1ыс1е <\л/1 пс1о\л/8. И> #1пс1ыс1е "МуЫЬ.Ь" ВШВВ ШМАР1 ТЬгеас1ЕипсА(ЕР701В); ВШВВ ШИАР1 ТИгеабРипсВ(ЕРУОЮ); ЕРЕ8ВЕТ САЕЕВАСК МРгос(НИМО, ЫМТ, ПРАВАМ, ЕРАВАМ); //=============================== Ш И1МАР1 пМа1 п(Н1М8ТАИСЕ Ыпзбапсе, Н1М8ТАМ1СЕ ЬРгеуТпзЬапсе, ЕР8ТВ 1рСтс1Е1пе, Ш пСтс18Пом) { НАМОЕЕ ЬТЬгеас1А = СгеаЬеТПгеасКШЕЕ, 0, ТЬгеабЕипсА, О, О, МЬЕЕ); НАМОЕЕ ЬТЬгеабВ = СгеаЬеТПгеасКШЕЕ, 0, ТЬгеабЕипсВ, О, О, МЬЕЕ); МеззадеВохСШЕЕ, "Завершить основной поток?", "ШпМа1п", МВ_ОК); гебыгп 0; } //======================= ВШВВ ШИАР1 ТЬгеас1ЕипсА(ЕР701В 1ру) { РгШ( "Поток А: Привет!"); РгШ( "Поток А: Эксперимент удался!"); РгШ( "Поток А: Пока!"); геРигп 0;
} //============================== ОШКО ШИАР1 ТНгеас1РипсВ(1Р\/010 1ру) { РгШ( "Поток Б: Не11о!"); РгШ( "Поток Б: ТБе ехреп’тепР шз а зиссезз!"); Рг1пР("Поток Б: Ву!"); ге^игп 0; } ////////////////////////////////////////////////////////////////////// В этой программе основной поток (функция М1 пМатп) создает два дочерних по- тока с входными функциями ТВ геад Ей п сА и ТН геад Ей п с В. В каждом дочернем потоке имеются три вызова ВЬЬ-функции РппЕ Создайте проект типа \Л/1 пЗ2 АррПсаВоп с именем МЕСНепЕ и добавьте в его состав файлы МКИепЕсрр и МуИЬ.Н. Скопируйте в папку проекта библиотеку импорта МуИЬ.ИЬ. Добавьте в настройки компоновщика ссылку на библиотеку импорта МуПЬ.НЬ. После компиляции проекта скопируйте в папку БеЬид файл МуПЬ.сШ и запусти- те ЕХЕ-файл на выполнение. На экране появятся три диалоговых окна, сформи- рованных вызовами функции МеззадеВох: Заголовок окна Текст в окне 1 МуЫЬ: РппЦ вызов 1 Поток А: Привет! 2 МуЫЬ: РппЦ вызов 2 Поток Б: Не11о! 3 \Л/1пМа1п Завершить основной поток? Однако, скорее всего, вы увидите только третье окно, поскольку первые два окна будут лежать под ним. Так уж устроена функция МеззадеВох — она размещает диалоговое окно всегда в центре экрана. Но, переместив с помощью мыши эти окна в разные позиции, вы получите возможность увидеть одновременно все три окна. Обратите внимание на заголовок окна, сформированного функцией Рпп1: из по- тока Б. В заголовке указано, что это уже второй вызов данной функции, хотя мы точно знаем, что для потока Б он является первым. Если мы продолжим работать с программой, щелкая на кнопке ОК в появляющихся диалоговых окнах, то уви- дим, что нумерация вызовов функции Рпп1: является сквозной (общей для всех потоков). Причина очевидна — эта нумерация формируется при помощи глобаль- ной переменной соип!: в файле МуИЬ.срр. А теперь представьте, что мы создаем серверное приложение, которое обраба- тывает каждый клиентский запрос, создавая отдельный поток, и в этом потоке вызывается ВЬЬ-функция, выполняющая некоторую работу. Но кроме основной работы она подсчитывает количество обращений клиента, чтобы потом выставить банковский счет за обслуживание. Понятно, что глобальная переменная соип!: в ис- ходном коде вьь плохо справится с такой задачей. Возможны и другие ситуации, когда некоторый объект должен быть глобаль- ным для отдельного потока, но при этом не разделяться другими потоками про- цесса. Чтобы обеспечить такую возможность, в АУш32 АР1 был создан механизм ло- кальной памяти потока (Ткгеай Ьоса1 31:ога$е — ТЬ8). Существуют две разновид- ности такой памяти: а) динамическая ТТЗ, б) статическая ТТЗ.
Динамическая Т1_5 Организация и функционирование динамической ТЬ8 поясняются на рис 11.2. Когда стартует любой поток, операционная система выделяет ему в памяти так называемый массив ТЬ5-слотов. Элементы этого массива представляют собой че- тырехбайтные ячейки, которые и называют слотами. Процесс Рис. 11.2. Организация динамической Т1_8 Стартовавший поток не имеет доступа к массиву ТЬЗ-слотов, пока не получит от системы индекс первого свободного слота. Дело в том, что индексация всех сло- тов для всех потоков одного процесса является общей. Поэтому, если стартовав- шие ранее потоки уже зарезервировали для себя слоты, например, с индексами О, 1, 2, то новый поток имеет возможность «приватизировать» слот с индексом 3. Для получения свободного слота вызывается функция Т1$АИос: дс1мТ181пс1ех = ЛзАПосО; Функция ТЬАПос резервирует первый свободный слот и возвращает его ин- декс, который обычно сохраняется в глобальной переменной §с!^ТЫпс1ех. В слу- чае неудачи (нет свободных слотов) функция возвращает значение -1. Индекс дд\л/Т151пс1ех может использоваться в дальнейшем всеми потоками про- цесса для доступа к зарезервированному слоту. Обратите внимание на важный момент: информация, содержащаяся в «одноименных» слотах разных потоков, является уникальной для каждого потока.
Количество ячеек п в массиве ТЬ8-слотов определяется константой Т1_$_М1- М1М11М_А\/А11_АВ1_Е. Для АУхпйоауз 95/ЫТ эта константа равна 64, но, начиная с АУш- с1о\\ 5 2000, она уже имеет значение 1088. Заметим, что функция Т1$А11ос в своей реализации сканирует глобальную бито- вую карту, содержащую п разрядов. Каждый разряд в этой карте находится в со- стоянии/гее или т_изе, указывая, свободен или занят соответствующий ТЬЗ-слот. Если найден бит со значением/гее, функция Т1$АИос изменяет его значение на гп_изе и возвращает найденный индекс. Перед возвратом управления функция проходит по всем потокам и обнуляет ТЕ8-слоты с выделенным индексом. Данная операция облегчает потокам корректную инициализацию своих слотов. Непосредственного доступа к слоту у потока нет. Чтобы записать значение в слот, поток вызывает функцию Т1з5е1:\/а1ие, передавая ей значение индекса дд\л/Т1з1пс1ех: Т1з8еШ1ие(дс1мТ181пс1ех, р61оЬ0а^а); Здесь р61оЬ0а1а — адрес в памяти, по которому размещены данные, глобальные в контексте потока, но недоступные для других потоков. Как правило, такие дан- ные содержат более чем одну переменную. В принципе, можно было бы для каж- дой переменной резервировать отдельный ТЬ8-индекс. Но, с учетом имеющихся ограничений на количество слотов, выделенных процессу, это было бы крайне не- рационально. Поэтому обычно работа с локальной памятью потока организуется следующим образом: □ Все необходимые переменные собираются в общую структуру, например: 61оЬОа^а { Ш х; Ш у; Ш соипР; // . . . сИаг ЬиГГег[256]; }; Затем объявляется указатель на эту структуру: 61оЬ0аРа* р61оЬОаРа; □ Поток выделяет память в куче (Ьеар) для сохранения структуры типа 61оЬ0а1а. Для этого вызывается одна из функций резервирования памяти, например, 1_оса1АПос: р61оЬ0аРа = (61оЬ0аРа*)1оса1А11ос(ЬРЖ 81хеоГ(61оЬ0аРа)); □ Полученный адрес р61оЬ0а1а сохраняется в соответствующем ТЕ8-слоте при помощи функции Т1з5е1:\/а1ие: Т1 зЗеЦУа! ие(дсШ81пс1ех, р61 оЬОаРа); □ Когда той или иной функции требуется доступ к данным в ТЕ8, вызывается функция Т1$6е1:\/а1ие: 61оЬ0аРа* р61оЬ0аРа = (61оЬ0аРа*)Т18беШ1ие(дмТ181пс1ех); Полученный указатель р61оЬ0а1а позволяет использовать любое поле струк- туры 61оЬ0а1а как для чтения, так и для записи. □ Перед завершением работы приложения использующийся слот должен быть освобожден. Для этой операции вызывается функция ТЕЕгее, принимающая
в качестве параметра индекс дс1\л/Т151пс1ех. Результатом выполнения ПзЕгее яв- ляется установка соответствующего разряда битовой карты в состояние /гее. ТЬ8 может применяться в любом многопоточном приложении. Особенно акту- ально использование механизма локальной памяти потока при разработке библио- тек динамической компоновки. Ведь ВЬЕ могут загружаться любыми приложени- ями, в том числе и многопоточными. Покажем использование описанной технологии на примере разработки библио- теки МуМИлЬ, которая имеет такую же функциональность, как и МуИЬ, но адапти- рована для работы с многопоточными приложениями. Напомним, что библиотека МуИЬ предоставляет своим клиентам единственную функцию \/о1с1 Рг1 ггЬ( 1РСТ8ТК ^ехВ; которая выводит на экран окно сообщений с текстом ±ех± и заголовком вида: Му1_1Ь: РгШ: вызов И где М — целое число, обозначающее номер вызова функции РппЕ Мы уже выяснили, что для того, чтобы нумерация вызовов функции Рпп1: отно- силась бы именно к тому потоку, из которого она вызывается, необходимо помес- тить глобальную переменную соип! в локальную память потока. Другим недостатком интерфейса библиотеки МуИЬ является отсутствие воз- можности управлять позицией вывода окна сообщений на экране. Функция МеззадеВох, использованная в реализации функции Рип!, всегда выводит это окно в центре экрана. А это неудобно, когда несколько потоков одновременно вызыва- ют РппЕ диалоговые окна «складываются» в стопку — каждое следующее поверх предыдущего. Очевидный путь решения этой проблемы — отказаться от использования фун- кции МеззадеВох и реализовать окно сообщений «врукопашную», при помощи мо- дального диалогового окна. Однако такой путь порождает новые проблемы. Необ- ходимо где-то сохранять: □ строку ЕехЕ, передаваемую через параметр функции Рип!, чтобы потом исполь- зовать эту строку в диалоговой процедуре; □ строку Ше с заголовком диалогового окна, также используемую в диалоговой процедуре; □ значения координат х, у, определяющих позицию вывода диалогового окна. И опять единственно возможным корректным решением является хранение всех этих данных в локальной памяти потока. Все эти соображения учтены в проекте МуМИлЬ (листинг 11.7). Кроме функции Рип! библиотека МуМИлЬ содержит: а) функцию ТпйМуМИлЬ, позволяющую настроить позицию вывода окна сообщений; б) функцию Рпп1> Вох01дРгос, исполняющую роль диалоговой процедуры; в) функцию точки входа 0ИМа1п, в которую помещены действия по инициализации/деинициализации ло- кальной памяти потока. Для того чтобы функционирование библиотеки МуМИлЬ стало более наглядным, в ее коде сделаны вставки функции ТРАСЕ, обеспечивающей вывод отладочной ин- формации в окно Ои1ри1. Эта функция определена в файле К\Л/пс1 Ех.срр (см. листинг 8.4 в главе 8).
Листинг 11.7. Проект МуМРЫЬ ////////////////////////////////////////////////////////////////////// // МуМРЕЮ #1пс1ис1е <Л пс1ом8. И> #с!еТ1 пе ЕХРОРТ ехРегп "С" с1ес"1 зрес(сП 1ехрогР) ЕХРОРТ уо1с1 Рг1пР(ЕРСТ8ТР РехР); ЕХРОРТ уо1с1 1п1 РМуМРЕ1 Ь( 1 пр х, 1 пр у); ////////////////////////////////////////////////////////////////////// // МуМРШэ.срр #1пс1ыс1е <зРс11 о. Н> #1пс1ыс1е "МуМРШэ.Р" #1пс1ыс1е " К1д/пс1Ех. ГГ #1пс1ыс1е "гезоигсе.И" 8РгисР 61оЬОаРа { 1 пР х; 1Пр у; 1пР соипР; сИаг Р1Р1е[100]; сИаг ЬиРРег[256]; }; Н1М8ТА1Х1СЕ И1П8Р011; ОШРО дс1\л/Т181 пс1ех; ВООЕ САЕЕВАСК Рг1пРВох01дРгос(НШ0 ПО1д, О1Р1Т иМзд, ИРАРАМ мРагат, ЕРАВАМ 1Рагат); //================================= ЕХРОРТ уо1с1 Рг1пР(ЕРСТ8ТР РехР) { // Отладочный вывод ОШРО РПгеасПО = беРСигтепРТПгеасПсК); ТРАСЕ("Рг1пР: вызов из потока параметр РехР = '%8'\п", РПгеасПО, РехР); // Получаем адрес памяти, связанный с индексом дсЕ/Т181пОех 61оЬ0аРа* р61оЬ0аРа = (61оЬ0аРа*)Т18беР\/а1ие(дс1мТ181пс1ех); р61оЬОаРа->соипР++; 8рг1пРР(р61оЬ0аРа->Р1Р1е, "МуМРШэ: РгппР: вызов М', р61оЬ0аРа->соипР); 8Ргпсру(р61оЬ0аРа->ЬиРРег, РехР, 99); 01а1одВох(П1п8Р011, МАКЕ1МТРЕ800РСЕ(Ю0_РР1МТ_В0Х), МОЕЕ, Рг1пРВох01дРгос); } //======================= ЕХРОРТ уоЮ 1ппРМуМРЕ1Ь( 1 пР х, 1пР у) { // Получаем адрес памяти, связанный с индексом дс1мТ181пОех 61оЬ0аРа* р61оЬ0аРа = (61оЬ0аРа*)Т18беР\/а1ие(дс1мТ181пс1ех); р61оЬОаРа->х = х; р61оЬОаРа->у = у; р61оЬОаРа->соипР = 0; } //========================== ВООЕ САЕЕВАСК Рг 1 пРВохО!дРгос(НШО П01д, ОПХ1Т иМзд, ИРАРАМ мРагат, ЕРАВАМ 1Рагат) { РЕСТ г; продолжение
Листинг 11.7. {продолжение} 61оЬ0аТа* р61оЬ0аТа = (01оЬ0аТа*)Т18беТ\/а1ие(дсЕ/Т181пс1ех); 8мПсИ (иМад) { сазе Ш_Ю1Т01АЕ06: ЗеШпс1омТехШ01д, р61оЬ0аТа->Ш1е); ЗеШпс1омТехТ(6еТ01д1Теш(001 д, 10С_Е0Ш), р61оЬ0аТа->ЬиТТег); 6еШпс1омРесШ01д, &г); МоуеЮ пс1о\л/(001 д, р61оЬ0аТа->х, р61 оЬОаТа->у, г.гпдОЛ - г. 1 еТТ, г.ЬоТТот - г Лор, ТРОЕ); геТигп ТЕСЕ; сазе Ш_С0ММАШ: з\л/1ТсО (ЬОШРОЫРагат)) { сазе ЮОК: ЕпсЮ1а!од(001д, 0): геТигп ТРОЕ; } Ьгеак: } геТигп ГАЕЗЕ; } //=============================== ВООЕ АР1ЕМТРУ 011Ма1п(НЮЗТАМСЕ ОппзТОЕЕ, 0ШР0 ТсЕ/Реазоп, ЬР70Ю 1 ру^еаегуеб) { 61оЬ0аТа* р61оЬОаТа; ОЮзТОИ = Н1П8Л0ЕЕ: 0ШР0 ТОгеасПО; 8\л/1Лс1п (ТсЕ/Реазоп) { сазе 0ЕЕ_РШСЕЗЗ_АТТАСН: // Отладочный вывод ТРАСЕ("011Ма1п: подключение ОЕЕ, НппеЛОЕЕ = Шп", МпеЛОЕЕ); // Размещаем ТЬЗ-индекс 1Т ((дсЕ/Т1 зТпбех = Т1зА11осО) == ОхЕЕЕЕЕЕЕЕ) геТигп ЕАЕЗЕ; ТРАСЕ("011Ма1п: получен индекс дс1мТ181пбех = %с1 первого свободного слота\п", дс1\л/Т181 пбех); Ьгеак; сазе ОЕЕ—ТНРЕАО—АТТАСН: // появился новый поток // Отладочный вывод ТИгеасИО = ОеТСиггепТЮгеасПсК); ТРАСЕ("011Ма1п: создание процессом нового потока, ЫпгеасИО = Шп", ЫпгеасИО); 1Т (Т18беТ\/а1ие(дсЕ/Т181пс1ех) == МОЕЮ // если память в ТЕЗ еще не выделена { // Выделяем память в куче р61оЬ0аТа = (61оЬ0аТа*)Ьоса1А11ос(ЬРТР, 81хеоТ(61оЬ0аТа));
// Связываем ТЬС-индекс потока с адресом памяти р61оЬ0аба Т1з5еЦ\/а1 ые(дс1\л/Т181пс1ех, р61 оЬОаба); // Отладочный вывод ТЕАСЕ( "011 Мапп: выделена память в куче с адресом Шп", р61оЬОаба); ТЕАСЕ("011 Мапп: адрес И записан в слот с индексом Шп", р61оЬОаба, дс1\л/Т181пс1ех); } Ьгеак; сазе ОЕЕ_ТНОЕАО_ОЕТАСН: // Отладочный вывод ЫпгеасИО = беЬСыггепШгеасПсК); ТЕАСЕ("011 Мап’п: завершение потока, ЫпгеасИО = Шп", ЫпгеасИО); // Освобождаем размещенную память для этого потока р61оЬ0аба = (61оЬ0аЬа*)Т1з6еШ1ие(д(М181пс1ех); 1Г (р61оЬ0аба != МОЕЕ) Ьоса1Егее((НЕОСАЕ)р61оЬОаба); Ьгеак; сазе 0ЕЕ-РК0СЕ35-0ЕТАСН: // Отладочный вывод ТРАСЕ("011 Мап’п: отключение ОЕЕ\п"); // Освобождаем ТЬ8-1пс1ех. Т1зЕгее(дсЕ/Т1з1пс1ех); Ьгеак; } гебыгп ТРОЕ; } ////////////////////////////////////////////////////////////////////// Заметим, что при вызове функции 1_оса1А11ос, выделяющей блок памяти в куче, ее первый параметр имеет значение 1_РТК. Константа 1_РТК — это флаг, включающий в себя комбинацию двух других флагов: 1_МЕМ_Е1ХЕО и 1_МЕМ_2ЕКО1М1Т. Значение 1_МЕМ_Е1ХЕО побуждает функцию выделить фиксированный (неперемещаемый) блок памяти в куче и вернуть указатель на начало этого блока. Значение 1_МЕМ_2ЕКО1М1Т обеспечивает инициализацию содержимого выделенного блока па- мяти нулями. Создайте проект типа \Лйп32 Оупа1тпс-1лпк ИЬтату с именем МуМШЬ и добавьте в его состав файлы МуМШЬ.Ь, МуМШЬ.срр, К\Л/пс1Ех.Н и КУУпдЕх.срр. С помощью редактора ресурсов добавьте в проект ресурс диалогового окна. В свойствах диалогового окна установите Ю равным Ю0_РК1МТ_В0Х и снимите фла- жок 5у$1:ет тепи. Уменьшите высоту окна примерно на 1/3. Удалите кнопку Сапсе1, а кнопку ОК разместите по центру у нижней границы окна. В центре окна поместите элемент управления Есй! Ьох, установив для него сле- дующие свойства: выравнивание текста — по центру, флажок Вогдег — снят, флаж- ки И и 11:111 пе, Аи1оН$сго11, Кеад оп1у — установлены. После компиляции проекта в папке ОеЬид должны появиться файлы МуМШЬ.дП и МуМШЬ.НЬ. Теперь подготовим клиентское приложение МШ$СИеп1: (листинг 11.8) для ис- пытания новой библиотеки.
Листинг 11.8. Проект МГПзСНеп!: ////////////////////////////////////////////////////////////////////// // МВПзС! 1епб.срр #1пс1ис1е <\л/1 пс1о\л/8. Н> #1пс1ис1е "МуМШЬ.Б" ОШЕО ШИАР1 ТНгеабРипсА( 1Р\/010); 0ШЕ0 ШИАР1 ТИгеабРипсВ( 1Р\/010); 1РЕ51ЛТ САЕЕВАСК МРгос(ШМО, 1ЛМТ, ПРАВАМ, ЕРАВАМ); //================================— Ш И1МАР1 Н1 пМа1 п(Н1М8ТАМСЕ Шзбапсе, Н1М5ТАМСЕ БРгеу1пзбапсе, ЕРЗТР 1рСтсИ1пе, Ш пСтбЗИоУл/) { НАИОЕЕ ИТИгеас1А = СгеабеТБгеасКМОЕЕ, 0, ТБгеасЕипсА, О, О, М1Л_1_); НАИОЕЕ Ш1геас1В = СгеабеТБгеасООЕЕ, 0, ТОгеасЕипсВ, О, О, 1Ш); МеззадеВохШОЕЕ, "Завершить основной поток?", "ШпМа1п", МВ_ОК); гебыгп 0; } //========================== ОИОВО ШИАР1 ТИгеас1ЕипсА(ЕР701В 1ру) { 1пПМуМШЬ(400, 350); РгШС'Поток А: Привет!"); РгШ( "Поток А: Эксперимент удался!"); РгШ( "Поток А: Пока!"); гебыгп 0; } //========================== ВИОВВ ШИАР1 ТИгеабЕипсВ(ЕР701В 1ру) { 1пПМуМШЬ(660, 350); РгШ( "Поток Б: Не11о!"); РгШ( "Поток Б: ТБе ехрегПтепб маз а зиссезз!"); РгШ( "Поток Б: Ву!"); гебигп 0; } ////////////////////////////////////////////////////////////////////// Создайте проект типа \Л/1пЗ 2 Арр Исай о п с именем М±Т15С11еп± и добавьте в его со- став файлы МШзСНегтЬ.срр и МуМШЬ.Ь. Скопируйте в папку проекта библиотеку импорта МуМШЬ.НЬ. Добавьте в настройки компоновщика ссылку на библиотеку импорта МуМШЬ.НЬ. После компиляции проекта скопируйте в папку ОеЬид файл МуМШЬ.сШ и запустите программу на выполнение в отладочном режиме (Р5). На экране должны появиться три окна, как показано на рис. 11.3. Поочередно щелкая на кнопке ОК двух верхних окон, вы можете убедиться, что нумерация вызовов функции Рпп! осуществляется для каждого потока от- дельно. А теперь посмотрите на отладочный вывод в окне 0 и!р и!. С некоторыми сокра- щениями он должен выглядеть примерно так: Еоабеб зутЬо!з Тог ' Б:\Ргод1д/1 пАр1 \МВП зС11 епШеЬидХМуМШЬ. сП 1' В11Ма1п: подключение ВЕЕ, МпзШЕ = 10000000 011Ма1п: получен индекс дс1мТ1 зIпс!ех = 3 первого свободного слота 011Ма1п: создание процессом нового потока, ббгеасПО = 8Е0 011Ма1п: выделена память в куче с адресом 143778 011Ма1п: адрес 143778 записан в слот с индексом 3 РгпгтЕ: вызов из потока 8Е0, параметр РехР = 'Поток А: Привет!'
011Ма1п: создание процессом нового потока, ТОгеасПО = АЕС 011Ма1п: выделена память в куче с адресом 143900 011Ма1п: адрес 143900 записан в слот с индексом 3 Рг1п1с: вызов из потока АЕС, параметр ^ех^ = 'Поток Б: Не11 о!' Рг1п1с: вызов из потока 8Е0, параметр ^ех^ = 'Поток А: Эксперимент удался!' Рг1п1с: вызов из потока АЕС, параметр ^ех^ = 'Поток Б: ТИе ехрептегтЕ шз а зыссезз!' Рг1п1с: вызов из потока 8Е0, параметр РехР = 'Поток А: Пока!' Рг1п1с: вызов из потока АЕС, параметр РехР = 'Поток Б: Ву!' 011Ма1п: завершение потока, ^ПгеасИБ = 8Е0 011Ма1п: завершение потока, ^ПгеасИБ = АЕС 011Ма1п: отключение ОЕЕ Рис. 11.3. Результат первого вызова функции Рпп1 из потоков А и Б Статическая Т1_5 Использование этого вида локальной памяти потока на порядок проще. Достаточ- но при объявлении переменной добавить модификатор_____дес1зрес(1:Нгеад), например: __с1ес! зрес(Ыягеаб) Ш соигтЕ = 0; чтобы каждый поток работал с собственной копией этой переменной. Модификатор______дес1зрес(1:Нгеад) поддерживается компилятором УНпа! С++. Он сообщает компилятору, что соответствующую переменную следует поместить в специальный раздел исполняемого файла, имеющий имя .1:1$. Функционирование статической ТЕ8 происходит в тесном взаимодействии с операционной системой. Загружая приложение в память, система отыскивает в исполняемом файле раздел Л1$ и выделяет блок памяти для хранения всех стати- ческих ТЕ8-переменных. При этом учитываются переменные, объявленные как в самом ЕХЕ-файле, так и во всех ВЕЕ с неявной компоновкой. Каждая ссылка на ТЕ8-переменную переадресуется к участку, расположенному в выделенном блоке памяти. Поэтому компилятору приходится генерировать дополнительный код для ссылок на статические ТЕ8-переменные, что увеличивает размер приложения и за- медляет скорость его работы. Но главным недостатком статической ТЕ8 является то, что если вы объявите переменные с модификатором______с1ес1$рес(1:Нгеас1) в коде ВЕЕ, то эту библиотеку нельзя будет загружать на этапе выполнения приложения при помощи функции 1_оас1 ИЬгагу. Если же вы попытаетесь это сделать, то получите ошибку «Ассезз ую1аН- оп». Поэтому при разработке ВЕЕ следует использовать только динамическую ТЕ8.
Специальные приложения В этой главе предлагаются примеры решений для двух практических областей. Сна- чала излагаются основы техники анимации в АУтскпуз-программах. Затем рассмат- ривается рисование в реальном времени в приложениях, имитирующих бортовую аппаратуру. Анимация Создание анимационных приложений — это весьма обширная тема, и ей посвящено несколько статей в В связи с ограничениями на объем данной книги мы рассмотрим только самую простую технику анимации, суть которой заключается в чередовании нескольких шагов. Нужно нарисовать объект, через некоторое время стереть его и изменить позицию объекта. Промежуток времени, в течение которого объект остается в текущей позиции, обычно задается таймером. Приложение со стандартным таймером В качестве примера рассмотрим программу «Прыгающий мячик», в которой имити- руется полет мяча в прямоугольном боксе от одной стенки к другой. Ударяясь о стенку бокса, мяч отскакивает под соответствующим углом и продолжает движе- ние в новом направлении. Роль «бокса» в этой программе исполняет клиентская область окна приложения. Чтобы сделать пример более интересным, дополним его двумя требованиями: а) мяч должен вращаться во время движения; б) фон, на кото- ром происходит полет мяча, должен изображать некоторую текстуру. Первая попытка анимации содержится в листинге 12.1. В этой программе де- монстрируется применение следующих средств АУш32 АР1: □ Использование узорной кисти (Сгеа^еРаНегпВгизН) для фона окна (5е1С1а$$1_опд) и для операции стирания (НИКес!) предыдущего изображения мяча. □ Контекст устройства в памяти1 (Сгеа^еСотраВЫеОС) для размещения в нем ВВВ- растра с изображением мяча (5е1ес1ОЬ]’ес1:) и последующего его вывода в контекст дисплея (ВИВИ). 1 Или совместимый контекст устройства.
□ Использование региона отсечения (Сгеа^еЕШрН’сВдп, 5е1ес1СИрКдп) для выделе- ния в прямоугольном растре области с изображением мяча, которая затем копи- руется при помощи функции ВИВИ. □ Мировые преобразования (5е1\Л/ог1с1ТгапзГопп) для перемещения и вращения изоб- ражения мяча. □ Функции 5ауеОС и Ве$1:огеОС, применяемые для сохранения и восстановления текущего состояния контекста устройства. Листинг 12.1. Проект ВоипсеВаН1 ////////////////////////////////////////////////////////////////////// // ВоипсеВа! 1 .срр #1пс1ис1е <\л/1 псЫз. Н> #1пс1ис1е <збсНо.Н> #1пс1ис1е <табН.Н> #1пс1ис1е "К1л/пс1.ГГ #1пс1ис1е "гезоигсе.Н" #с1еТ1пе Р1 3.14159265 В^ЕЗиВТ САЕЕВАСК 1л1пс1Ргос(НШ0, В1МТ, МРАРАМ, ВРАЕАМ); 7016 ОгамВа! 1 (ШИВ, НОС, НВ1ТМАР, В1ТМАР, ЕЕОАТ, ЕЕОАТ, 1гВ); //============================================ 1пР ШИАР1 Ш пМа 1 п (НIМЗТА1Х1СЕ Н1пзТапсе, Н1М8ТАМСЕ НРгеуТпзТапсе, ВРЗТВ 1рСтс1В1пе, 1пТ пСтс18Ном) { М86 1рМзд; КВ/пс! та1п1л!п6("Воипсе ЬаП", Н1пзТапсе, пСтЮНом, 1л1пс1Ргос); \л/М 1 е (СеТМеззаде(&1 рМзд, МОВВ, 0, 0)) { ТгапзШеМеззадеШрМзд); 01 зраЮНМеззадеШ рМзд); } геТигп (1рМзд.мРагат); } /======================= ВВЕЗОВТ САЕЕВАСК Шс1Ргос(НШ0 Шпс1, 01ИТ иМзд, АРАРАМ мРагат, ВРАРАМ 1 Рагат) { З'Ва'Ыс РЮАТ х = 0, у = 0; // текущая позиция мяча зТаТ1 с РЮАТ 0Х, ОХ; // приращения координат для сдвига на новую позицию зТаТ1с 1пТ а1рИа = 0; // угол, определяющий вращение мяча зТаТ1с НОС НОС; // контекст дисплея зТаТ1с НВРОЗН НВкВгизН; // узорная кисть НВ1ТМАР НВтрВкдг; // растр для узорной кисти зТаТ1с НВ1ТМАР НВтрВаП; // растр с изображением мяча зТаТ1с В1ТМАР Ьт; // параметры растра РЕСТ гесб; // прямоугольник клиентской области РЕСТ гВаП; // прямоугольник, ограничивающий изображение мяча з\л/1 ТсН (иМзд) { сазе 1л1М_СКЕАТЕ: НОС = 6еТ0С(Н1л1пс1); продолжение & 1 Не забудьте добавить к проекту файлы КМпсВН и КУУпсВсрр, текст которых приведен в листинге 1.2.
Листинг 12.1. {продолжение} СМС11епЬРесЬ(Шпс1, &гесЬ); с1Х = гесЬ.г1 дЬЬ / 100.; НУ = гесЬ.ЬоНот / 50.; // Создать таймер (0.1 с) 8еЬТ1тег(Шпс1, 1, 100, ШШ; ИВтрВкдг = Ьоас1ВПтар( (Н1М8ТАМСЕ)6е^1пбом1опд(Шпб, Си/1__НIМ8ТА1Х1СЕ), МАКЕ I ИТЕЕ30В ЕС Е(IВВ_ЗТ0ИЕ)); НВкВгизЬ = СгеаЬеРаиегпВгизИСИВтрВкдг); 8еЮаз8Еопд(Шпс1, ЗСЕ_НВВВАСКЗВ0иМВ, (ЕОМО)НВкВгизН); ИВтрВаП = Ьоас1ВПтар( (Н1М8ТАМСЕ)6е^1пбомЕопд(Шпб, О1л/Е_НIИЗТАМСЕ), МАКЕI НТВ ЕЗ 0 0 ВС Е(IВ В_ВАЕ Е)); Ве-СОВдесЕ(ИВтрВаО 1, зыеоПЬт), (ЕРЗТВ)&Ьт); ЗеВОгарМС8Мос1е(ВВС, 6М_АВ7АМСЕВ); Ьгеак; сазе 1л/М_Т1МЕВ: 6еЬС11егОесЬ(Шпс1, йгесЬ); // Стираем прежнюю картинку мяча ЗеВВесВ(&гВа"11, (1пЬ)х, (Ш)у, (Ш)х + Ьт.ЬтШсЕН, (Ш)у + Ьт.ЬтНе1дИЬ); Е11 "IВесВ(ВВС, &гВа! 1, ИВкВгызИ); // Новая позиция мяча х += с1Х; У += НУ; а!рЬа += 10; 15 (а!рЬа > 360) а!рЬа = 0; // Если мяч достиг края окна, направление его движения изменяется 15(х + Ьт.ЬтШс15Ь > гесЬ.п’дЬЬ || х < 0) С1Х = -с1Х; 15(у + Ьт.ЬтНе1дЬ5 > гесЬ.ЬоЬЬот || у < 0) (IV = -НУ; ВгамВа! 1 (Шпс1, НОС, ЬВтрВаП, Ьт, х, у, а!рЬа); Ьгеак; сазе 1л1М_ВЕ8ТР0У: К111Т1тег(Шпс1, 1); Ре1 еазеВС(Шпс1, ВВС); РозЬОыПМеззадеСО); Ьгеак; с1е5аи15: геЬигп Ве5ШпсЫРгос(Шпс1, иМзд, мРагат, 1Рагат); } геЬигп 0; } //========================^ УОПС1 ВгамВа! 1 (НШВ Ыпс1, НОС Ыс, НВ1ТМАР ЬВтр, В1ТМАР Ьт, ЕЕОАТ х, ЕЕОАТ у, Ш а!рЬа) { ХЕОВМ хСогт;
НО Юдп; // Подготовка к выводу мяча НОС НВаПМетОС = СгеаЬеСотраЬ1Ь1еОС(Ыс); 8е1есШу)есШВа11МетОС, ЬВтр); // Создаем регион отсечения НКдп = СгеаЬеЕ111рЫсВдп(х, у, х + Ьт.Ьт1л/1 с11оГ|, у + Ьт.ЬтНе1д1тЕ); 8е1есЮ1рВдп(Ыс, НВдп); // Мировые преобразования для перемещения и вращения мяча хГогт.еМП = (ЕЬОАТ) соз(а1рНа * 2 * Р1 / 360); //вращение хГогт.еМ12 = (ЕЮАТ) з1п(а1рНа * 2 * Р1 / 360); //вращение хГогт.еМ21 = (ГЕОАТ) -з1п(а1рНа * 2 * Р1 / 360); //вращение хТогт.еМ22 = (ГЕОАТ) соз(а1рНа * 2 * Р1 / 360); //вращение хТогт.еОх = х + Ьт.ЬтШс1Ю / 2.; //смещение по оси х хТогт.еОу = у + Ьт.ЬтНе1дЮ / 2.; //смещение по оси у // Вывод мяча ЗауеОС(Нбс); ВООЕ геб = 8еТ1л1ог1бТгап8Тогт(Нбс, &хСогт); В1ЬВ1Ь(Ис1с, -Ьт.ЬтШс1Ш2, -Ьт.ЬтНе1дЮ/2, Ьт.ЬтШООН, Ьт.ЬтНе1дЮ, НВаНМетОС, 0, 0, ЗРССОРУ); ВеЮогеОС(Ьс1с, -1); 8е1 есЮ11 рРдп(Обе, МОЕЮ; Ое1еТеОЬ0есШРдп); 0е1еТе0С(ЬВа11МетОС); } ////////////////////////////////////////////////////////////////////// Обратите внимание на вызов функции 5еТСгар1п'с5Мос1е для переключения кон- текста устройства в графический режим (6М_А0\/АМСЕ0). Этот режим нужен для ис- пользования мировой системы координат и мировых преобразований. В теле функ- ции Ога\л/Ва11 мировые преобразования реализуются вызовом функции 5е1\Л/ог1д- ТгапзГогт, а изменяющиеся значения полей структуры хГогт обеспечивают эффект пе- ремещения и вращения мяча. Эти преобразования должны использоваться только при выводе изображения мяча. Поэтому перед вызовом 5е1\Л/от1с1ТгапзГопп мы запомина- ем текущее состояние контекста устройства с помощью 5ауеОС, а затем восстанавлива- ем его вызовом ВезТюге ОС. Основные события разворачиваются в блоке обработки со- общения ММ_Т1М ЕК. Приведенный код вместе с комментариями, на наш взгляд, не требует дополнительных пояснений. Чтобы скомпилировать и запустить эту программу на вашем компьютере, необ- ходимо добавить к проекту файл ресурсов, содержащих изображения рисунка фона и мяча. Сначала подготовьте и разместите в папке проекта ВМР-файлы с этими изображениями. Для рисунка фона мы воспользовались файлом 51опе.Ьтр, кото- рый можно скопировать из пакета МхсгозоЙ ОШсе1. Второй файл, ВаН.Ьтр, неслож- но подготовить в любом графическом редакторе, например в М8 Рахпб, сохранив его в 256-цветном формате2. Процедура добавления к проекту растровых ресурсов в режиме «Импорт»3 описана в главе 5. Определяя эти ресурсы, свяжите файл 1 Файл зХопе.Ьшр обычно находится в папке 8Еу1ез, путь к которой может выглядеть как С:\Рго§гат Р11е8\М1сго8оЙ Ойгсе\Ойтсе 10\Вйтар8\8Еу1е8. 2 Вы можете также взять готовый файл ВаП.Ьтр в составе проекта ВоппсеВаП, если скопируете фай- лы к данной книге, доступные на сайте издательства луту.рйег.сот. 3 Когда БХВ-растр подготовлен с помощью внешнего графического редактора
51опе.Ьтр с идентификатором ЮВ_5ТОМЕ, а файл ВаН.Ьтр — с идентификатором ЮВ_ВАИ. В результате в составе вашего проекта должны появиться файлы ВоипсеВаП.гс и гезоигсе.Н. Когда вы успешно решите все эти проблемы и откомпилируете проект, в окне программы ВоипсеВаИ появится летающий мячик, как показано на рис. 12.1. Рис. 12.1. Прыгающий мяч в приложении ВоипсеВаП Все работает замечательно, за исключением одной детали. Дело в том, что вра- щающийся мячик отвратительно мерцает. Причиной этого является быстрое пос- ледовательное выполнение двух операций с контекстом дисплея: стирание пре- жнего изображения мяча (НИВес!) и вывод нового изображения (В1ЧВ1±). Вы можете убедиться в этом, закомментировав вызов функции НИКес!. После этого изображение перестанет мерцать, но вместо летающего мячика получится что-то вроде червя, прогрызающего тоннель в камне. Для решения обнаруженной нами проблемы обычно используется прием, из- вестный как двойная буферизация. Двойная буферизация Неприятное мерцание изображения в анимационном приложении можно устра- нить, если сформировать очередную фазу картинки в виртуальном контексте ус- тройства. Для этого используется контекст в памяти, совместимый с контекстом дисплея. В нашем случае очередная фаза содержит две операции: а) стереть пред- шествующее изображение мяча; б) нарисовать новое изображение мяча. После этого содержимое совместимого контекста копируется в контекст дисплея. Продемонстрируем эту технику в листинге 12.2, содержащем модификацию предыдущей программы.
Листинг 12.2. Проект ВоипсеВа11Ас1у ////////////////////////////////////////////////////////////////////// // ВоыпсеВа!1АсВ.срр #1пс1ис1е <\л/1 пс1о\л/8. Ь> #1пс1ис1е #1пс1ис1е <таЮ.Ь> #1пс1ис1е "К1л/пс1.ГГ #1пс1ис1е "гезоигсе.Ь" #беТбпе Р1 3.14159265 ВкЕЗиВТ САЕЕВАСК 1л1пбРгос(НШ0, В1МТ, МРАРАМ, ВРАКАМ); 7016 ОгамВа! 1 (НШО, НОС, НОС, НВ1ТМАР, В1ТМАР, ЕЬОАТ, ЕЬОАТ, 1пТ); //========================^ 1пТ ШИАР1 ШпМа1п(НIИЗТАМСЕ ЫпзТапсе, Н1М5ТАМСЕ ЬРгеуТпзТапсе, ЕРЗТЕ ТрСтбЫпе, 1пТ пСтсЕЮЮ { М86 1рМзд; КВ/пс! та1п1л!п6("Воипсе ЬаП", МпзТапсе, пСтсЕЮм, ЫгбРгос); \л/Гп 1 е (6еТМеззаде(&1рМзд, ЖЕ, 0, 0)) { ТгапзШеМеззадеШрМзд); 01 зраТсИМеззаде(&1рМзд); } геТигп (1рМзд.мРагат); } //========================^ ЕРЕЗОЕТ САЕЕВАСК ЫпбРгос(НШО Шпс1, 01ИТ иМзд, ЫРАРАМ мРагат, ЕРАРАМ 1Рагат) { 8ТаТ1с РЮАТ х = 0, у = 0; // текущая позиция мяча зТаТ1с РЮАТ ОХ, 07: // приращения координат для сдвига на новую позицию зТаТ1с 1пТ а1рЬа = 0; // угол, определяющий вращение мяча зТаТ1с НОС НОС; // контекст дисплея зТаТ1с НВРОЗН НВкВгизН; // узорная кисть НВ1ТМАР НВгпрВкдг; // растр для узорной кисти зТаТ1с НВ1ТМАР НВтрВаП; // растр с изображением мяча зТаТ1с В1ТМАР Ьт; // параметры растра РЕСТ гесТ; // прямоугольник клиентской области РЕСТ гВа!1; // прямоугольник, ограничивающий изображение мяча зТаТ1с НОС НМетОсЕгате; // совместимый контекст (контекст в памяти) зТаТ1с НВ1ТМАР НВтрЕгате; // растр для совместимого контекста зТаТ1с 1 пТ соипТ = 0; з\л/1 ТсЕ1 (иМзд) { сазе Ш_СРЕАТЕ: ЬОС = беТОС(Шпб); беТС11епТРесТ(Шпс1, йгесТ); 6Х = гесТ.г1дЮ / 100.; 6Х = гесТ.ЬоТТот / 50.; // Создать таймер (0.1 сек) . продолжение
Листинг 11.2 (продолжение) 8еШтег(Шпс1, 1, 100, ШШ; НВтрВкдг = Еоас1ВПтар( (Н1М8ТАМСЕ)СеЫл11пЬомЕопд(ЬШЬ, СИ1__Н1 М8ТА1Х1СЕ), МАКЕ I ИТВЕ8ОЬВСЕ(IВВ_8Т0ИЕ)); НВкВгизЬ = СгеаЬеРаЬЬегпВгизЬШВтрВкдг); 8еЬС1 аззЬопдСШпс!, ССЕ_НВВВАСКСВОЬМО, (Е0М8)НВкВгиаЮ ; НВтрВаП = Еоас1ВПтар( (Н1М8ТАМСЕ)СеЫл11пЬомЕопд(ЬШЬ, 8ИЕ_Н1 И8ТАМСЕ), МАКЕ IИТЕЕ80В ЕС Е(10В_ВАЕ Е)); СеЬОЬцесШВтрВа! 1, зыеоПЬт), (ЕР8ТЕ)&Ьт); НМетОсЕгате = СгеаЬеСотраЫЫеОС(ЬОС); НВтрЕгате = СгеаЬеСотраЫЫеВ1Ьтар(Ь0С, гесЬ.г1дИЬ, гесЬ.ЬоЬЬот); 8е1есЬОЬ0есЬ(ЬМетОсЕгате, НВтрЕгате); 8еЬСгарЬ1сзМос1е(ЬМет0сЕгате, СМ_АО\/АМСЕО); Ьгеак; сазе Ш_812Е: СеЬС! 1епЬВесЬ(Шпс1, &гесЬ); НВтрЕгате = СгеаЬеСотраЫЫеВ1Ьтар(Ь0С, гесЬ.п’дИЬ, гесЬ.ЬоЬЬот); Ое1еЬеОЬ0есЬ(8е1есЬОЬ0есЬ(ЬМетОсЕгате, НВтрЕгате)); // Копирование фона в НМетОсЕгате ВПВ1Ь(НМетОсЕгате, 0, 0, гесЬ.г1дНЬ, гесЬ.ЬоЬЬот, КОС, О, О, 8ВСС0РУ); Ьгеак; сазе Ш_Т1МЕВ: СеЬС! 1епЬВесЬ(Шпс1, &гесЬ); ОсоипЬ) { // Копирование фона в НМетОсЕгате ВПВ1Ь(НМетОсЕгате, 0, 0, гесЬ.г1дИЬ, гесЬ.ЬоЬЬот, КОС, О, О, 8ВСС0РУ); соипЬ++; } // Стираем прежнюю картинку мяча 8еЬВесЬ(&гВа11, х, у, х + Ьт.ЬтШс1Ыз, у + Ьт.ЬтНе1дИЬ); Е111 ВесШМетОсЕгате, &гВа! 1, НВкВгизЬ); // Новая позиция мяча х += ИХ; У += НУ; а!рЬа += 10; (а!рЬа > 360) а!рЬа = 0; // Если мяч достиг края окна, направление его движения изменяется 1Т(х + Ьт.ЬтШс1ЬЬ > гесЬ.т’дИЬ || х < 0) ИХ = -ИХ; 1Т(у + Ьт.ЬтНе1дИЬ > гесЬ.ЬоЬЬот || у < 0) НУ = -НУ; ОгамВа! 1 (Шпс1, НОС, НМетОсЕгате, НВтрВаП, Ьт, (1пЬ)х, (1пЬ)у, а1рЬа); Ьгеак; сазе 1л1М_0Е8ТВ0У: К11 П1тег(Н1л1пс1, 1); Ве1 еазеОС(Шпс1, НОС);
0е1еЬе0С(ЬМет0сЕгате); РозЩтМеззадеСО); Ьгеак; с1еГаи!Ь: геЬигп ОеГШ МсмРгос(ММ, иМзд, мРагат, 1 Рагат); } геЬигп 0; } //========================================== уоМ ОгамВа! 1 (Н1л1М0 ММ, НОС Мс, НОС ЬМетЕгатеОС, НВ1ТМАР ЬВтр, В1ТМАР Ьт, ЕЕОАТ х, ЕЕОАТ у, 1пТ а!рба) { ХГОГкМ хТогт; НРСМ Мдп; // Подготовка к выводу мяча НОС НМетОсВаП = СгеаТеСотраЫЫеОС(Мс); 8е1есЬ0ЬдесШМет0сВа11, ЬВтр); // Создаем регион отсечения Мдп = СгеаТеЕ! 11рЫсВдп(х, у, х + Ьт.ЬтШс1ТЬ, у + Ьт.ЬтНе1дМ); 8е1есЬС11рРдп(НМетГгатеОС, Мдп); // Мировые преобразования для перемещения и вращения мяча хГогт.еМП = (ГЬОАТ) соз(а1рМ * 2 * Р1 / 360); //вращение хТогт.еМ12 = (ГЬОАТ) 81п(а1рМ * 2 * Р1 / 360); //вращение хТогт.еМ21 = (ГЬОАТ) -81п(а1рМ * 2 * Р1 / 360); //вращение хТогт.еМ22 = (ГЬОАТ) соз(а1рЬа * 2 * Р1 / 360); //вращение хТогт.еОх = х + Ьт.ЬтШс1ТЬ / 2; //смещение по оси х хТогт.еОу = у + Ьт.ЬтНе1дМ / 2; //смещение по оси у // Вывод мяча в контекст ЬМетЕгатеОС 8ауеОС(ЬМетЕгатеОС); ВООЬ геТ = 8еТ1л1ог1ЬТгап8Тогт(ЬМетЕгатеОС, &хТогт); В1ТВ1Т(ЬМетЕгатеОС, -Ьт.ЬтШсЫп/2, -Ьт.ЬтНе1дМ/2, Ьт.Ьт1л11 с1Ып, Ьт.ЬтНе1дМ, ЬМетОсВаП, 0, 0, ЗГССОРУ); РезбогеОСТМетГгатеОС, -1); // Копирование изображения из ЬМетЕгатеОС в Обе ВЕСТ гесТ; ОеТС! 1егОесТ(ЫМ, &гесТ); В1ТВ1Т(Мс, 0, 0, гесТ.пдМ, гесТ.ЬоТТот, ЬМетЕгатеОС, 0, 0, ЗГССОРУ); ЗеПесТС!1рВдп(ЬМетЕгатеОС, ИНЕЕ); 0е1еТеОЬдесТ(Мдп); 0е1еТеОС(ЬМетОсВа!1); } ////////////////////////////////////////////////////////////////////// Двойная буферизация в программе реализована на основе контекста в памяти НМетОсЕгате, совместимого с контекстом дисплея. Виртуальный контекст создает- ся вызовом функции Сгеа^еСотраВ’ЫеОС, после чего в него выбирается при помощи функции 5е1ес±ОЬ]ес± растр Н ВтрЕгате, также совместимый с контекстом дисплея и имеющий размеры клиентской области окна приложения. Инициализация контекста НМетОсЕгате для копирования в него изображения фона осуществляется в блоке обработки сообщения ММ_Т1МЕВ. Чтобы инициализа- ция была однократной, мы используем счетчик со и Ей и вызываем функцию ВДВ 11 только при нулевом значении счетчика. Также нужно позаботиться о новой
инициализации контекста НМетОсЕгате в случае изменения размеров окна. Для этого добавлен код обработки сообщения ММ_$12Е. Также обратите внимание на то, что стирание прежней картинки мяча в вирту- альном контексте происходит в блоке обработки сообщения ММ_Т1МЕК. После вы- числения новых координат мяча вызывается функция 0 га м В а II, и ей передается вир- туальный контекст в качестве параметра НМетЕгатеОС. В теле функции Ога\л/Ва11 завершается формирование очередной фазы картинки, когда вызывается функ- ция В1ТВИ для вывода изображения мяча в виртуальный контекст. Только после этого вся картинка копируется при помощи второго вызова ВИВИ: из НМетЕгатеОС в контекст дисплея. Рекомендуем вам откомпилировать этот проект и убедиться в эффективности метода двойной буферизации. Рисование в реальном времени Разработка авиационной бортовой радиотехнической аппаратуры содержит этап тестирования и взаимной стыковки отдельных подсистем. При этом часто вместо отсутствующей подсистемы применяются программные имитаторы. К приложени- ям, имитирующим реальную аппаратуру, предъявляются довольно жесткие требо- вания. Например, программный имитатор приемника радиолокационной инфор- мации, используемый вместо реального индикатора, должен успевать выводить на экран радиолокационную картинку в том темпе, в котором ее выдает передатчик локатора. Рассмотрим проблемы, возникающие при создании таких программ, на при- мере разработки имитатора приемника информации от метеорадиолокатора. Требования к приемнику информации от метеорадиолокатора Передатчик метеорадиолокатора формирует и выдает по цифровому каналу свя- зи информацию от метеорадиолокатора в соответствии со стандартом «АКШС сйагас^епзНс 708А»1. Этот стандарт в числе прочих требований определяет также параметры протокола передачи: □ Информация от метеорадиолокатора передается пакетами размером 1600 бит. Каждый пакет относится к одному лучу сканирования. □ В состав пакета входит: служебная информация; информация об угле сканирования — 12 бит; цветовая информация для 512 точек дальности локационного луча — 1536 бит (по три бита на каждую точку). □ Период выдачи пакетов составляет от 5,00 до 7,82 мс. □ Максимальная угловая скорость вращения антенны радиолокатора — 90 °/с . 1 АКШС — аббревиатура фирмы «АегопаиОса! Кас1ю, ГЫС.» (ША).
Приемник информации от метеорадиолокатора должен получать указанные па- кеты из цифрового канала связи, распаковывать полученную информацию и отображать радиолокационную картинку на своем индикаторе. Следует, конечно, более подробно рассмотреть взаимосвязь некоторых пара- метров протокола передачи. Код угла сканирования $сапАпд1е представлен целым 12-разрядным двоичным числом. Значение $сапАпд1е может рассматриваться и как беззнаковое число, нахо- дящееся в диапазоне от 0 до 4095 в десятичном исчислении, и как знаковое число в диапазоне от -2048 до +2047. В любом случае получается 4096 значений, которые должны покрывать максимальный сектор обзора радиолокатора, равный 360°. От- сюда вес сйдйМет’дЫ: одной единицы кода $сапАпд1е вычисляется как 360 / 4096 = 0,087890625°. Если РАСК_РЕК100 — период выдачи пакетов в миллисекундах, то максимально возможная скорость сканирования (при условии передачи каждого пакета) огра- ничена следующей величиной: зреесМШ Раск Реп’ об = с11 д1 +1л/е1 дН+ / (РАСК_РЕРЮО / 1000.) Подставив указанное выше значение для ейдй\Л/е1 дЫ: и значение 5 для РАСК_РЕКЮО, получим значение $реес1\Л/й:НРаскРепос! = 17,578°/с. Но ведь протоколом определена максимальная скорость сканирования 90°/с! Чтобы успевать передавать информацию при такой скорости сканирования, передатчик не отправляет в канал связи каждый очередной пакет, а пропускает несколько пакетов. Введем понятие коэффициента просеивания, который опреде- ляется следующим выражением: 81еуеСоеГГ = се11(змеерЗреесЮие / зреесМШРаскРенос!): В этом выражении $теер5реесЮие — требуемая скорость сканирования. Например, если 8\л/еер5реесЮие = 90, то для РАСК_РЕК100 = 5 получаем значение коэффициента просеивания $1’еуеСое1Т = 6. Это значит, что передатчик должен от- правлять в канал связи каждый шестой пакет. Приемник должен знать скорость сканирования 8\л/еер5реесЮие и период выдачи пакетов. Тогда он может вычислить коэффициент $1’еуеСое1Т, который используется на приемной стороне уже как коэффициент размножения луча развертки. Только в этом случае будет восстановлена исходная радиолокационная картинка (конечно, с теми потерями, которые вызваны пропуском $1’еуеСое1Т-1 лучей). Разработка модели программного имитатора Настоящий программный имитатор приемника информации от метеорадиолока- тора является довольно сложным приложением. К тому же для его функциониро- вания требуется соответствующее аппаратное обеспечение. Поскольку сейчас нас интересуют только вопросы реализации быстрого рисования, в этой главе рас- сматривается построение упрощенной модели программного имитатора. В упрощенной модели имитатора приемника информации от метеорадиоло- катора мы абстрагируемся от таких проблем, как прием пакетов из цифрового канала связи и их распаковка для извлечения необходимых параметров. Модель имитатора будет воспроизводить условную радиолокационную картинку в секторе от -60 до +60°, состоящую из четырех «колец» разного цвета.
Период передачи пакетов РАСК_РЕК1ОО будет 5 мс. Меню приложения должно позволять выбрать требуемую скорость сканирования из множества значений { 15, 30, 60, 90 } градусов в секунду. В строке состояния приложения должны отображаться следующие значения: а) скорость сканирования; б) коэффициент размножения лучей; в) расчетное время сканирования всего сектора (в прямом и обратном направлениях); г) фактичес- кое время сканирования всего сектора. Первая версия модели программного имитатора Первая версия будет называться АппсКесе1’уегВас1. Смысл этого названия станет яс- ным на этапе тестирования программы. Создайте новый проект с именем АппсВесе1’уегВас1. Скопируйте из папки проекта ТооГПр (см. листинг 8.4) в папку проекта АппсВесе1’уегВас1 файлы Кдопс1Ех.Н и Кдопс1Ех.срр. Скопируйте из папки проекта КПтег (см. листинг 10.1) файл КПтег.Н. Добавьте ско- пированные файлы в состав проекта. Также добавьте к настройкам проекта на вклад- ке Ипк имена библиотек сотс1132.ИЬ и мптт.ИЬ. Добавьте к приложению ресурс меню с идентификатором ЮК_МЕМ1П. Главное меню должно содержать пункты с атрибутами, указанными в табл. 12.1. Таблица 12.1. Пункты главного меню Имя пункта Тип пункта Идентификатор Скорость сканирования Подменю Старт Команда Стоп Команда ЮМ_5ТАИТ ЮМ_5ТОР Подменю Скорость сканирования должно содержать пункты с атрибутами, при- веденными в табл. 12.2. Таблица 12.2. Пункты подменю «Скорость сканирования» Имя пункта Тип пункта Идентификатор 15 °/с Команда ЮМ_5РЕЕО_15 30 °/с Команда ЮМ_5РЕЕО_30 60 °/с Команда ЮМ_5РЕЕО_60 90 °/с Команда ЮМ_5РЕЕО_90 Добавьте в состав проекта файл АппсВесе^егВасГсрр с текстом, приведенным в листинге 12.3. Листинг 12.3. Проект АппсКесе1УегВас1 ////////////////////////////////////////////////////////////////////// // Аг1псВесе1уегВас1.срр #1пс1ис1е <\л/1 пс1ом8. И> #1пс1ис1е <соттсРг1.И> #1пс1ис1е <Мт8у8Рет.И> #1пс1ис1е <зРс11 о. И> #1пс1ис1е <таРИ.И> #1пс1ис1е "гезоигсе.И" #1пс1ис1е "К1л1пс1Ех.И"
#1пс1и6е "КТ1тег.1э" #с1еТ1 пе Ю_8ТАТи8ВАк 201 #с1еТ1 пе РАСК_РЕВЮВ 5 //в миллисекундах #6еС1пе ЕЕЕТ_МАВС1М -683 // апд1е = -60°/с #6еС1пе В1СНТ_МАВС1М 683 // апд1е = +60°/с #6еС1пе Р1 3.141592653589793 НОС д_НОС; ШИВ ЫпбЗбаШВап; боиЫе змеерЗреебОие; // требуемая скорость развертки (градусов в секунду) боиЫе зреесОл/1 61эРаскРег1об; // скорость развертки для периода РАСК_РЕВЮО 1пб з1еуеСоеГГ; // коэффициент просеивания/размножения лучей развертки боиЫе 8меерТ1те0ие; // расчетное время просмотра сектора (с) боиЫе 8меерТ1теЕасб; // фактическое время просмотра сектора (с) 1п6 8сапАпд1е; // код угла сканирования в принятом пакете боиЫе 61 д1 6Ие1 дНб; // вес единицы кода зсапАпд1е СОЕОВВЕГ се!1[512]; // массив цветов точек дальности 1пб ХО, 70; // начало системы координат для развертки боиЫе х, у; // текущие кординаты вывода точки КТ1тег 61тег; СуребеС епит { ЕОВТН, ВАСК } 01 В; 010 с11гес61оп = ЕОВТН; // направление сканирования 7016 ОрбабеЗбабизВаЫ); УО16 САЕЕВАСК Т1 тегЕыпс(01ИТ, ЮТ, ОИОВО, ОИОВО, ОИОВО); уо16 СебРаскебО; // Получение пакета из канала связи 7016 6е6Мех6Апд1е(); // Вычисление следующего значения кода угла 7016 8ебСо1огВеатЕогШ); // Имитация формирования массива се11 // для прямого сканирования 7016 8ебСо1огВеатВаск(); // Имитация формирования массива се11 // для обратного сканирования 7016 ОгамВеатО; // Вывод луча на экран ЕКЕ80ЕТ САЕЕВАСК ИпбРгос(НШО, ЮТ, НРАВАМ, ЕРАВАМ); //========================= 1пб 1ЮАР1 Ш пМа 1 п (НIМ8ТАМ1СЕ Ыпзбапсе, Н1М8ТАМ1СЕ КРгтМпзбапсе, ЕР8ТВ 1рСтбЕ1пе, 1п6 пСтбЗЮ) { М86 тзд; КИпбЕх та1пНп6("Аг1псВесе17егВаб", Юзбапсе, пСтбЗНом, ИпбРгос, МАКЕ IК1ТВЕ80ЫВСЕ ( 10В_МЕШ1), 100, 100, 900, 600); \л/Н11е (ОебМеззадеС&тзд, МЫЕЕ, 0, 0)) { ТгапзШеМеззадеС&тзд); В18ра6с1эМе88аде(&т8д); } гебигп тзд.мРагат; } продолжение &
Листинг 12.3 {продолжение} //==================================================================== 1КЕ51ЛТ САЕЕВАСК ИРлос (НШО ШлсЕ 1ЛМТ иМзд, ПРАВАМ мРапат, ЕРАВАМ 1Рапат) { 8'Ьа'Ыс НМЕМЫ ИМепи; // дескриптор главного меню ВЕСТ песТ, пс8В; 1 лТ а1л/1 сПоНз Е4]; зТаЫ с ММЕЕ5ЫЕТ ттТлтел; змПсИ (иМзд) { сазе Ш_СВЕАТЕ: ИМели = СеТМели(Шлс1); Зе-ЕС 1 аззЬолд (ИШЬ, ЗСЕ_НВЕВАСКЗЕОСИ В, (ЕОМОСпеаТеЗо! 1 бВгизВ (ИЗВ (100, 100, 100))); змеерЗреесЮие = 90; СИескМелиВасМо1Тет(СеТ8иЬМели(ИМели, 0), IВМ_ЗРЕЕВ_15, 1ВМ-8РЕЕВ-90, 1ВМ_ЗРЕЕВ_90, МЕ_ВУСОММАМВ); // Создание строки состояния ИмлЬЗТаТизВал = СпеаТеЗТаТизШ пс1ом(1л18_СН1 ЕВ | 1л/3_\/131ВЕЕ, ПШсЕ 1В 8ТАТВ8ВАВ); аШб-ЕИз [0] = 205; аШб-ЕИз [1] = 355; аШб-Ебз [2] = 610; аШб-Ебз [3] = -1; Зепс1Ме88аде(НшсЕТаТизВаг, ЗВ_ЗЕТРАВТЗ, 4, (ЕРАВАМ)аШ с1ТНз); Зепс1Ме88аде(НшсЕТаТизВаг, ЗВ_31МРЕЕ, ЕАЕЗЕ, 0); сМд1 Е1л1е1 дИТ = 360.0 / 4096; зреес1ШТНРаскРеп1об = сМд1Т1л1е1дИТ / (РАСК_РЕВЮВ / 1000.); з1еуеСоеТТ = се11(змеерЗреесЮие / зреес1ШТНРаскРео1ос1); ВрсШеЗЕаЕизВагЧ); СеТС! 1епТВесТ(НШсЕ &оесТ); СеШ лбом Вес! (НмпсЕТаТизВал, &лс8В); Х0 = лесТ.л1дПТ / 2; Х0 = лесТ.ЬоТТот - (лсЗВ.ЬоТТот - лсЗВ.Тор); // Контекст устройства для рисования развертки д_ИВС = СеТВС(ИШЬ); 8еТСо1ооВеатЕолТН(); Ьгеак; сазе Ш_812Е: Зелс1Ме88аде (ИмлЬЗТаТизВал, Ш_812Е, мРалат, 1Рагат); Ьгеак; сазе Ш_С0ММАМВ: зтТсИ (ЕОШВВЫРагат)) { сазе 1ВМ-ЗРЕЕВ-15: змеерЗрееЬВие = 15; СИескМелиВасМо1Тет(СеТ8иЬМепи(ИМели, 0), IВМ_ЗРЕЕВ_15, 1ВМ-ЗРЕЕВ-90, ЕОШВВЫРагат), МЕ_ВХСОММАМВ); Ьгеак;
сазе ЮМ-5РЕЕ0-30: змеерЗреесЮые = 30; СЬескМепиВасМо1Гет(6еГ8иЬМепи(ЬМепи, 0), ЮМ_8РЕЕ0_15, ЮМ-5РЕЕ0-90, ЬОШВВЫРагат), МЕ_ВУСОММАМВ): Ьгеак; сазе ЮМ-5РЕЕ0-60: змеерЗреесЮые = 60; СЬескМепиВасМо1Гет(6еГ8иЬМепи(ЬМепи, 0), ЮМ_8РЕЕ0_15, ЮМ-5РЕЕ0-90, ЬОШВВЫРагат), МЕ_ВУСОММАМВ): Ьгеак; сазе 1ВМ-5РЕЕВ-90: змеерЗреесЮие = 90; СЬескМепиВасМо1Гет(6еГ8иЬМепи(ЬМепи, 0), IВМ_5РЕЕВ_15, 1ВМ-8РЕЕВ-90, ЬОШВВ(мРагат), МЕ_ВУСОММАМВ): Ьгеак; сазе ЮМ_8ТАкТ: Ытег.ЗЬагЬО; тгтГПтег = Ыте8еЬЕуепЬ(РАСК_РЕР10В, 0, ИтегЕипс, ге1 пЬегргеЬ_сазЬ<В1лЮРВ>(Шпс1), Т1МЕ_РЕВЮВ1С); 1Г (!ттТ1тег) МеззадеВох(Шпс1, "Еггог оГ ЫтеЗеЬЕуеШ ", МЬЕЕ, МВ-ОК I МВ-1С0М8Т0Р); Ьгеак; сазе ЮМ_8Т0Р: ЫтеК111 ЕуепГ(ттТ1тег); Ьгеак; } з1еуеСоеГГ = се11 (з\л/еер8реесЮие / зреес1ШЬЬРаскРег1ос1); Ьрс1аЬе8ЬаЬизВаг(); Ьгеак; сазе Ш_ВЕ8ТВ0У: Ве1 еазеВС(Шпс1, д_РОС); РозШПГМеззаде(0); Ьгеак; с1еГаи!Ь: геЬигп ВеГШ пЬомРгос(Шпс1, иМзд, мРагат, 1 Рагат); } геЬигп 0; } //=======================^^ уо1с1 Ьрс1аЬе8ЬаЬизВаг() { сбаг ЬехШОО]; зрг1 пШЬехЬ, "Скорость сканирования: ГОГ °/с", змеерЗрееЬВие); 8епс1Меззаде(Ьшс18ГаГизВаг, 8В_8ЕТТЕХТ, 0, (ЬРАВАМ)ГехГ); зрг1 пШГехГ, "Коэфф, размнож. лучей: П", з1еуеСоеГГ); 8епс1Меззаде(Ьшс18ГаГизВаг, 8В_8ЕТТЕХТ, 1, (ЬРАВАМ)ГехГ): змеерИтеВые = 2. *( В16НТ_МАВ61И - ЬЕЕТ_МАВ61 И) * (РАСК-РЕВЮВ/1000.) / з1еуеСоеП: зрг1 пШГехГ, "Расчетное время просмотра сектора: Г2Г с", змеерИтеВые), продолжение
Листинг 12.3 {продолжение} 8еп6Ме88аде(Ып68ТаТи8Ваг, 8В_8ЕТТЕХТ, 2, (ВРАКАМ)ТехТ); зрг1 пШТехТ, "Фактическое время просмотра сектора: Г2Т с", 8\л/еерТ1теЕасТ): 8епс1Ме88аде(ЫпбЗТаТизВаг, 8В_8ЕТТЕХТ, 3, (ВРАКАМ)ТехТ): } //========================^ уо16 САЕЕВАСК ТптегЕипс(Е11 ИТ мПтегЮ, 1ЛМТ тзд, ВШКВ бмОзег, ВШКВ (М, ВШКВ сШ { // Дескриптор окна-владельца ШИВ ЫпсЮмпег = ге1пТегргеТ_са8Т<НШВ>(6м08ег): // (в модели имитатора не используется, но может понадобиться // в реальном имитаторе для отправки сообщений окну-владельцу) ОеТРаскеТО: // получение пакета из канала связи ВгамВеатО: // вывод луча из полученного пакета // Размножение лучей Тог (1 пФ 1 = 1; 1 < з1еуеСоеТТ: ++1) { 0еТМехТАпд1е(): ВгамВеапХ); } } //========================^ уо16 ОеТРаскеТО { 0еТМехТАпд1е(): } //=======================^^ уо16 0еТМехТАпд1е() { сВаг ТехЩОО]: 1Т (61гесТ1оп == ЕОКТН) 1Т (++зсапАпд1е >= К10НТ_МАК01Ю { 61гесТ1оп = ВАСК; 8еТСо1огВеатВаск(): 8\л/еерТ1теЕасТ = Т1тег.беТТ1те() / 1000: Т1тег.8ТагТ(): зрг1пТТ(ТехТ, "Фактическое время просмотра сектора: Г2Т с", 8\л/еерТ1теЕасТ): 8еп6Ме88аде(1п\л/п68ТаТи8Ваг, 8В_8ЕТТЕХТ, 3, (ЕРАКАМ)РехР): } 1С (61 гесЫоп == ВАСК) 1С (--8сапАпд1е <= ЕЕЕТ_МАКВ1И) { 61гесФ1оп = ЕОКТН: 8еТСо1огВеатЕогШ): } } //=======================^^ уо16 8еТСо1огВеатЕогШ) { Тог (Ш 1 = 0: 1 < 512: ++1) {
1Г (1 < 128) е!зе 1Г (1 < 256) е!зе 1Г (1 < 384) е!зе се11[1] = К8В(255, 0, 0); се11[1] = 0(0, 255, 0); се11[1] = 0(255, 255, 0); се11[1] = 0(0, 0, 255); //========================== уо1с1 8еГСо1огВеатВаск() { Гог (1пГ 1 =0; 1 < 512; ++1) { 1Г (1 < 128) се!1 [1] = 0(0, 0, 255); е!зе 1Г (1 < 256) се!1 [1] = 0(255, 255, 0); е!зе 1Г (1 < 384) се!1 [1] = 0(0, 255, 0); е!зе се11[1] = 0(255, О, 0); //========================================== уо1с1 ВгамВеатО { х = ХО; У = ХО; с1оиЫе апдОе = 8сапАпд1е * с11 д1 Г1л1е1 дНГ; с1оиЫе С = соз(Р1 * (90 - апд!е) / 180); с1оиЫе 8 = з1п(Р1 * (90 - апд!е) / 180); Гог (1пГ к = 0; к < 512; ++к) { х += С; у -= 8; 8еГР1хе1 (д_ГЮС, (1гД)х, (1пГ)у, се!1[к]); } } ////////////////////////////////////////////////////////////////////// Следует обратить внимание на некоторые детали реализации модели. Имитатор приемника информации от метеорадиолокатора должен опрашивать цифровой канал связи с периодом РАСК_РЕК100, равным 5 мс. Эта подзадача реа- лизована с помощью мультимедийного таймера, который, как было показано в главе 10, можно успешно использовать в миллисекундном диапазоне. Таймер запускается по команде меню Старт и может быть остановлен в любой момент ко- мандой Стоп. После запуска таймера операционная система вызывает каждые 5 мс функ- цию обратного вызова П’гпегЕипс. Эта функция и выполняет основную работу ими- татора. Через третий параметр функция П’тегЕипс получает дескриптор главного окна приложения. Хотя в данном случае этот дескриптор в теле функции П’тегЕипс не используется, в реальном имитаторе он может пригодиться для отправки сооб- щений главному окну. Алгоритм функции П’тегЕипс не требует особых пояснений благодаря проду- манной функциональной декомпозиции и выразительным именам вызываемых функций. А вот функция СеГРаскеГ нуждается в таких пояснениях. Если бы предметом нашего рассмотрения был реальный имитатор приемника информации от метеора- диолокатора, то «реальная» функция СеГРаскеГ должна была бы получать очередной пакет из канала связи и обеспечивать его распаковку. В результате такой распаков- ки эта функция извлекала бы значение кода угла сканирования и формировала бы массив се II. Но так как рассматриваемая программа является всего лишь моделью
реального имитатора, то «модельная» реализация функции Се1Раске! просто вызыва- ет функцию ех1Апд1е. Функция ех1Ап д 1е обеспечивает формирование следующего значения кода угла сканирования. При каждом вызове этой функции знак приращения кода угла $сапАпд1е зависит от направления сканирования. При движении по часовой стрелке (ЕОКТН), код угла увеличивается на единицу, при движении против часовой стрелки (ВАСК) — уменьшается на единицу. На границах сектора сканирования переменная сйгесНоп изменяет свое значение. Помимо этого вызывается функция 8е1:Со1огВеатЕог1Н или 5е1Со1огВеатВаск, чтобы заполнить массив сеИ цветовыми кодами для точек дальности локационного луча. В результате при движении впе- ред рисуются кольца красного, зеленого, желтого и синего цветов (перечисление идет от центра к периферии). При движении назад рисуются кольца синего, жел- того, зеленого и красного цветов. В реальном имитаторе вызовы функций 5е1Со1огВеат ЕогИэ и 5е1Со1огВеат Васк от- сутствуют, поскольку массив сеII формируется функцией 6е1Раске1. В теле функции 6е!М ех!Апд 1е также осуществляется хронометраж фактического времени просмотра сектора в обоих направлениях. Измерение отрезка времени между предыдущим и текущим прохождениями правой границы сектора осуществ- ляется с помощью объекта Втег класса КПтег, который рассматривался в главе 10. Измеренный интервал выводится в четвертое поле строки состояния приложения. Рисование луча развертки реализовано в теле функции ОгамВеат. Точки дально- сти локационного луча из массива се11 выводятся на экран с помощью функции 5е1Р1’хе1. Для рисования используется контекст устройства с глобальным дескрип- тором д_НОС. Приложение получает этот контекст один раз при своем старте в блоке обработки сообщения ММ_СВЕАТЕ и освобождает его при завершении рабо- ты в блоке обработки сообщения ММ_ОЕ$ТВОУ. После компиляции проекта перейдем к его тестированию. На этапе тестирования ресурсоемких приложений очень важно наблюдать за степенью загрузки центрального процессора (ЦП). Операционная система содер- жит удобный инструмент — системное приложение Диспетчер задач \Л/1’пс1о\л/5 (Тазк Мападег), позволяющее вести такое наблюдение. Для вызова диспетчера задач используйте комбинацию клавиш С1г1+5Н1Т1:+ Езс. В строке состояния окна диспетчера задач отображается текущая загрузка цент- рального процессора в процентах. Это суммарная загрузка ЦП от всех запущенных процессов. Если переключиться на вкладку Процессы, то можно наблюдать за про- центом загрузки ЦП от каждого отдельного процесса. Теперь запустите приложение АппсВесекегВасГ По умолчанию в приложении установлена скорость сканирования 90 °/с. Выполните команду меню Старт. Ими- татор начнет воспроизводить «радиолокационную картинку» примерно так, как показано на рис. 12.2. Здесь приводятся результаты тестирования программы на компьютере с про- цессором 1пСе1 Сектой СРП 2,0 ГГц и операционной системой МхсгозоЙ АУтскпуз 2000. Диспетчер задач через несколько секунд после начала рисования показывает 100 % загрузки центрального процессора! При этом программа реагирует на коман- ды пользователя с задержкой в несколько десятков секунд. Точно так же парализо- ваны и другие работающие на компьютере приложения, включая и диспетчер задач.
Рис. 12.2. Окно приложения АппскесеЬ/егВас! Очевидно, что для скорости сканирования 90 °/с наш имитатор не справляется с рисованием в реальном времени. Об этом же свидетельствует и превышение фак- тического времени просмотра сектора (2,52 с) над расчетным временем просмотра сектора (2,28 с). При тестировании программы со скоростью сканирования 60 °/с загрузка ЦП падает до 60 %, и приложением уже можно пользоваться. Но не будем забывать, что в рассматриваемой модели имитатора многие подзадачи не решаются, а в настоящем имитаторе нагрузка на процессор возрастет. В чем же причина низких эксплуатационных качеств, которые продемонстри- ровала первая версия нашей программы? — В использовании функции 5е1Р1’хеI! В главе 2 мы уже говорили, что реализация функции 5е1Р1’хеI такова, что процесс вывода пиксела занимает более 1000 тактов работы процессора. Поэтому для быст- рого рисования нужны другие технологии. Одним из возможных решений данной проблемы является применение Н1В- секций для рисования. Использование этой технологии мы покажем во второй версии программы. Вторая версия модели программного имитатора Основы применения НТВ-секций были изложены в главе 3, но без демонстрации на программных примерах. В данном разделе этот пробел будет восполнен. Напомним, что НТВ-секцией называется НТВ-растр, который обеспечивает непосредственные чтение и запись как со стороны приложения, так и со стороны СНТ. Массив пикселов НТВ-секции хранится обычно в виртуальной памяти прило- жения.
Для создания НТВ-секции используется функция Сгеа1е01 В5есБоп, например: НВ1ТМАР ИВтр = СгеаРеВ1В8есР1оп(Г1с1с, &Ьт1, В1В_О_С0ЮВ8, &рВ1Рз, ШЕЕ, 0); Функции передается адрес информационного блока Ьгт, содержащего характе- ристики создаваемого растра. После выполнения функции в аргумент рВйз записы- вается указатель на массив пикселов НТВ-секции. Быстрое рисование на поверхно- сти НТВ-секции основано на прямом доступе к пикселам в массиве рВйз. Чтобы вычислить адрес для доступа к байтам, представляющим пиксел с координатами (х, г/), нужно знать количество байт, занимаемых каждой строкой изображения. Эта величина вычисляется с помощью следующего выражения: ЬуРеРегЕ1пе = ((\л/1сИзН * Ьт1.Ьт1Неас1ег,ЫВ1РСоипР + 31) / 32) * 4; Ниже будет рассмотрен класс К01Ь5ес1:1ОП, в котором инкапсулированы опера- ции рисования на НТВ-секции. Определение и использование класса КНТЬЗесИоп демонстрируется во второй версии программного имитатора. Создайте новый проект с именем АппсВесеНег. Скопируйте из папки проекта АппсВесеНегВас! (см. листинг 12.3) в папку проекта АппсВесеНег файлы с расшире- ниями .Н, .срр и .гс, скорректировав их имена заменой подстроки АппсВесеНегВас! на АппсВесеНег. Добавьте скопированные файлы в состав проекта. Добавьте к на- стройкам проекта на вкладке Ипк имена библиотек сотс±132.11Ь и мптт.ИЬ. Добавьте к проекту файлы КНТЬЗесИоп. Н и КОп’ЬЗесИоп.срр с текстом, содержа- щимся в листинге 12.4. Отредактируйте файл АппсВесеНег.срр, приведя его текст в соответствие с листингом 12.4. Листинг 12.4. Проект АппсКесев/ег ////////////////////////////////////////////////////////////////////// // КВ1Ь8есР1оп.И #1пс1ис1е <\л/1 пс1ом8. Е1> с1а88 К01Ь8есХ1оп { риЫ 1с: КВ1Ь8есР1оп() : рВ1Рз(0) {} В001 СгеаХе(НОС Нс1с, 1пР _\л/1 сИзН, 1пР Д1е1дИР); 1п11пе уо1с1 8еХР1 хе! (1 пР х, 1 пР у, СОЕОВВЕЕ со!ог) { 1пР 1пс1 = 3 * х; ((ВУТЕ*)рВ1Р8)[у*ЬуРеРег1_1пе + 1 псЩ = 0еРВ\/а1 ие(со!ог); ((ВУТЕ*)рВ1Р8)[у*ЬуРеРегЕ1пе + 1пс1 + 1] = 6еР6\/а1 ие(со!ог); ((ВУТЕ*)рВ1Р8)[у*ЬуРеРегЕ1пе + 1пс1 + 2] = 0еРВ\/а1 ие(со!ог); } 1п11пе уо1с1 Вга\л/(НВС Нс1с) { 8РгеРсЕ101В1 Рз (Нс1с, 0, 0, мШИ, Ие1дИР, 0, 0, мШИ, Ие1дИР, рВ1Рз, &Ьт1, ВIВ_В8В_С0Е0ВЗ, ЗВССОРУ); } рг1уаРе: НВ1ТМАР ИВтр: В1ТМАР1ИЕО Ьт1; 1 пР тс1РЕ1; 1 пР НеадНР: 1 пР ЬуРеРегИпе; РУОЮ рВ1Рз: }:
////////////////////////////////////////////////////////////////////// // К01Ь8еШ оп. срр #1пс1ис1е "КОЮЗеШопТ" ВООЕ КОЮЗеШоп: :СгеаШН0С Нс1с, Ш _\л/1сИзГ|, Ш _Ие1 дОО) { \л/1 сИзГ| = _\л/1сИзГ|: Г|е1дГ|1з = _Ие1д1тЕ; Ьт1,0т1Неас1ег .Ы31хе = 81 геоТ( В1ТМАР1МЕОНЕАОЕЮ ; Ьт1 .Ьт1Неас1ег.Ь1ЫШИ = мсЖ Ьт1. Ьт1 Неас1ег. Ь1 Не1 д1и = Те1д6б; Ьт1 .Ьт1Неас1ег .ЫР1апез = 1; Ьт1 .Ьт1Неас1ег .ЫВПСоипР = 24; Ьт1 .Ьт1Неас1ег .ЫСотргеззтоп = В1_Р6В; ЬубеРегЕ1пе = ((\л/1сИа1п * Ьт1.Ьт1Неас1ег.ЫВПСоипР + 31) / 32) * 4; ОВтр = СгеабеО1ВЗеШоп(Нбс, &Ьт1, 01В_Р6В_С0Е0РЗ, &рВПз, МОЕЕ, 0); гебигп (НВтр != МОЕЕ); } ////////////////////////////////////////////////////////////////////// // Аг1псРесе1уег.срр #1пс1ис1е <\л/1 псЫз. Н> #1пс1ис1е <соттсбг1,6> #1пс1ис1е <Мт8у8бет.6> #1пс1ис1е <8бс11оТ> #1пс1ис1е <таЖ6> #1пс1ис1е "гезоигсе.И" #1пс1ис1е "КОпс1Ех.И" #1пс1ис1е "КТ1тег.И" #1пс1ис1е "КОЮЗеШопТ" /* Определения констант и переменных - такие же, как в листинге 12.3 */ КОЮЗеШоп сНЬЗесб; /* Объявления прототипов функций - такие же, как в листинге 12.3 */ /======================= Ш ШИАР1 ШпМапп(НIМЗТАМСЕ Шнапсе, Н1М5ТАМСЕ КРгеуШОапсе, ЕРЗТР 1рСтс1Е1пе, Ш пСтсЗИом) { М86 тзд; К1л/пс1Ех та1п^пс1("Аг1г1сРесе1уег", бТпзбапсе, пСтсОом, ОпбРгос, МАКЕ1МТЕЕ800РСЕ(10Р_МЕМ01), 100, 100, 900, 600); \л/И11е (ОебМеззадеС&тзд, МОЕЕ, 0, 0)) { ТгапзШеМеззадеС&тзд); ОтзрабсММеззадеС&тзд); } гебигп тзд.мРагат; } //===========================================— ЕРЕЗОЕТ САЕЕВАСК ОпбРгос (НШО Шпс1, О1МТ иМзд, ОРАРАМ мРагат, ЕРАРАМ 1 Рагат) { зТаТпс НМЕМО КМепи; // дескриптор главного меню РЕСТ гесб, гс8В; продолжение
Листинг 12.4 {продолжение} 1пТ а1л/1 сПзНз Е4]; зТаЫ с ММВЕ80ЕТ тпТИтег; зЮсЬ (иМзд) { сазе Ш_СВЕАТЕ: /* Здесь такой же текст, как в листинге 12.3 */ // Создание 01В-секции сНЬЗесТ.СгеаТе(д_ЬОС, гесТ.н’дИТ, УО); Ьгеак; сазе Ш_812Е: 8епс1Меззаде (Ыпс18ТаТизВаг, Ш_812Е, мРагат, 1Рагат); Ьгеак; сазе Ш_С0ММАМ0: /* Здесь такой же текст, как в листинге 12.3 */ Ьгеак; сазе Ш-ВЕ8ТР0У: Ве1 еазеОС(Шпс1, д_НОС); РозЬОьп ЬМеззаде(0); Ьгеак; беТаиИ: геТигп ОеТШпсЫРгос(Шпс1, иМзд, мРагат, 1Рагат); } геТигп 0; } //============================= уо1с1 0рс1аТе8ТаТизВаг() { /* Здесь такой же текст, как в листинге 12.3 */ } //========================================== уо1с1 САЕЕВАСК Т1 гпегЕипс (011\1Т ШтегЮ, ЮТ тзд, ОШВО скОзег, ОШВО с1\л/1, ОШВО сШ { // Дескриптор окна-владельца НШО ЫпсЮмпег = ге1пТегргеТ_сазТ<НШ0>(с1м0зег); // (в модели имитатора не используется, но может понадобиться // в реальном имитаторе для отправки сообщений окну-владельцу) РО1ИТ ррТ[4]; // массив точек для создания региона отсечения // Определяем точки ррТ[0] и ррТ[3] ррТЕОЪу = ррТЕЗЪу = УО; 1Т (сИгесТЮп == ЕОВТН) { ррТ[0].х = ХО - 1; ррТ[3].х = ХО + 1; } 1Т (сНгесЫоп == ВАСК) { ррТ[0].х = ХО + 1; ррТ[3].х = ХО - 1; } ОеТРаскеТО; // получение пакета из канала связи ОгамВеатО; // вывод луча из полученного пакета
// Определяем точку ррШ] ррШЪу = у; 1Г (сИгесЫоп == ГОКТН) ррб[1].х = х - 1; 1Г (сИгесЫоп == ВАСК) ррб[1].х = х + 1; // Размножение лучей Гог (1пР 1 = 1; 1 < зтеуеСоеГГ; ++1) { 0еЖхРАпд1е(); ОгамВеатО; } // Определяем точку ррб[2] ррР[2].у = у; 1Г (с11гесР1оп == РОРТН) ррб[2].х = х + 1;; 1Г (сПгесШп == ВАСК) ррб[2].х = х - 1; // Создание региона отсечения НВОИ ОРдп = СгеабеРо1удопРдп(ррб, 4, ШИОЖ); 8е1есОС11рРдп(д_НОС, ОРдп); // Копирование изображения на экран (с учетом региона отсечения) сПЬЗесР. Огам(д_КОС); ОеШеОЬдесШРдп); } //==================================================================== уо1с1 ОебРаскебО { 6ебМехбАпд1е(); } //==================================================================== уо1с1 0еЖхРАпд1е() { /* Здесь такой же текст, как в листинге 12.3 */ } //==================================================================== уо1с1 8еРСо1огВеатРогШ) { /* Здесь такой же текст, как в листинге 12.3 */ } //==================================================================== уо1с1 8ебСо1огВеатВаск() { /* Здесь такой же текст, как в листинге 12.3 */ } //==================================================================== уо1с1 ОгамВеатО { х = ХО; У = ХО; боыЫе апд!е, С, 8; апдОе = 8сапАпд1е * сНдШетдИР; С = соз(Р1 * (90 - апдОе) / 180); 8 = 81п(Р1 * (90 - апдОе) / 180); Рог (Ш к = 0; к < 512; ++к) { х += С; У -= 8; // Вывод точки на поверхность 01В-секции с11Ь8есб.8ебР1хе1 ((1пШ, (Ш)у, се!1[к]); } } //////////////////////////////////////////////////////////////////////
Интерфейс класса К01’Ь5ес1п’оп содержится в файле КОтЬЗесЫоп.Н, а реализация — в файле КСНЬЗесИоп.срр. Метод 5е1:Р1хеI предназначен для вывода пиксела с цветом со1ог в точку (х, у) на поверхность В1В-секции. Рисование с помощью 5е1:Р1хеI создает изображение, раз- мещенное в памяти в массиве рВйз. Чтобы изображение появилось на экране дисп- лея, нужно вызвать метод Ога\л/, копирующий изображение из В1В-секции в кон- текст устройства Нс1с. Объект ейЬ5ес1 объявлен в глобальной области видимости: КОйэЗесШп сНЬЗесД; В1В-секция создается в блоке обработки сообщения ММ_СВЕАТЕ: сНЬЗесД .СгеаШд_ИОС, гесД.ндЖ УО); с размерами, соответствующими видимой части клиентской области главного окна. В теле функции ОгамВеагп вместо СВ1-функции 5е1Р1’хе1 вызывается одноимен- ной метод объекта сйЬ5ес1:: сНЬЗесД ,8еШхе1 ((1гД)х, (1ггЬ)у, се!1[к]); После того как нарисованы основной луч и размножаемые лучи (в теле функции ИгпегЕипс), должен быть вызван метод Огам для переноса изображения на экран. Хотя метод 5е1:Р1хе1 работает очень быстро, общее время рисования включает также и время выполнения метода Огам, которое зависит от площади копируемой поверхности, то есть от параметров растра мтсЙН и НетдЫ. Измерение времени выполнения метода Огам в нашей программе (пока не были приняты специальные меры) показало результат около 8 мс. Очевидно, что такое время нас категоричес- ки не устраивает, поскольку период поступления пакетов равен 5 мс. Фактически, при такой реализации программа работала бы еще хуже, чем АппсВесеВегВас!. К счастью, есть способ ускорить рисование при помощи регионов отсечения. Действительно, нет никакой необходимости каждые 5 мс копировать на экран весь растр размером хллсИН х Неп’дЫ:. Достаточно скопировать только ту часть растра, в которой нарисованы последние $1’еуеСое1Т лучей. Для этого создается многоугольный регион: НО НРдп = Сгеа^еРо1удопРдп(рр^, 4, ШИОЖ); по четырем точкам из массива рр1. Правила определения граничных точек региона отсечения поясняются на рис. 12.3. Рис. 12.3. Определение граничных точек региона отсечения: а) сканирование вперед; 6) сканирование назад
Определение точек массива рр1 и создание региона отсечения реализованы в теле функции ПтегЕипс. После выбора региона отсечения в контекст устройства д_НОС вызывается метод Огам: сИ Ь8есЦ . Огам(д_ИОС); Профилирование этой инструкции в отладочной версии программы (с помо- щью таймера — объекта класса КПтег) показало, что она выполняется примерно за 0,2 мс. Тестирование приложения АппсКесе^ег, выполняемого со скоростью сканиро- вания 90°/с, показало, что загрузка центрального процессора составляет не более 5 %! Таким образом, для рисования в реальном времени использование НТВ-секций в сочетании с регионом отсечения может быть неплохим решением. Напомним, что термин «реальное время» в применении к АУшбом^-приложени- ям является весьма условным, поскольку АУшбом^ не является операционной сис- темой реального времени. Об этом мы уже говорили в главе 10. Поэтому возможны отдельные сбои в функционировании таких программ, как АлпсКесеНег, если \У1пс1о\\ъ вдруг решит, что у него есть дела поважнее. Но так как имитатор Ал псКесеНег не управляет никакими стратегическими объектами, то подобные сбои вполне допустимы.
Приложение 1 Интегрированная среда У15иа1 С++ 6.0 1п1е§га1ей Еесе1ортеп1; ЕпъггоптепЬ (интегрированная среда разработки), или со- кращенно ЮЕ — это программный продукт, объединяющий текстовый редактор, компилятор, отладчик и справочную систему. Мы предполагаем, что пакет МхсгозоЙ Ухзпа! 81псйо 6.0, в состав которого вхо- дит ЮЕ МхсгозоЙ Ухзпа! С++ 6.0, уже установлен на вашем компьютере. Любая программа, создаваемая в среде Ухзпа! С++, даже такая простая как «Не11о, АУогЮ!», всегда оформляется как отдельный проект (рпуесР). Проект — это набор взаимосвязанных исходных файлов (с расширением .срр), заголовочных файлов (с расширением .Н), файла ресурсов (с расширением .гс) и некоторых других файлов, компиляция и компоновка которых позволяют соз- дать исполняемую программу (файл с расширением .ехе). Разработчики Ухзпа! ЗЕпсйо предусмотрели сервис для коллективной разра- ботки программных продуктов. Он реализован в виде «рабочей области». Рабо- чая область (рго]ес{ ^огкзрасе') может содержать любое количество различных проектов, сгруппированных вместе для согласованной разработки. В данном приложении приводятся минимально необходимые сведения для на- чала работы с интегрированной средой и для построения проекта типа \Л/1п32 АррИсаНоп — етрЕу рго]'ес1:. Все примеры программ в книге реализуются в проек- тах этого типа. Рабочая область проекта у нас всегда будет содержать только один проект. Запуск ЮЕ. Типы приложений Запуск Ухзпа! С++ осуществляется при помощи команды меню Пуск ► Программы ► М1 егозой У1’зиа1 ЗЕисйо 6.0 ► М1’сго8ой У1’зиа1 С++ 6.01. Впрочем, можно просто щелк- нуть мышью на соответствующей пиктограмме, если вы позаботились о ее разме- щении на рабочем столе компьютера. После запуска Ухзпа! С++ появляется главное окно программы, показанное на рис. П1.1. В зависимости от настроек для вашего рабочего стола Ухзпа! С++ его вид может несколько отличаться от показанного на рисунке. 1 На вашем компьютере путь к исполняемой команде меню может быть другим.
На самом деле главное окно УНпа! С++ принадлежит студии разработчика М1СГО8ОЙ Веуе1орег 81пс1ю, которая является интегрированной средой, поддер- живающей Ухзпа! С++, Ухзпа! ф М8 ЕогЕгап Ро\\ег 8Шюп и некоторые другие продукты. Окно Рго]ес1 \Л/огк8расе Окно Ес1Иог Окно ОифсЛ Рис. П1.1. Главное окно (рабочий стол) \/15иа1 С++ Рабочий стол Ухзпа! С++ обычно содержит три окна. Окно Рго]ес1: Могкзрасе (окно рабочей области) позволяет эффективно управ- лять проектом при написании и сопровождении больших многофайловых про- грамм. Пока что (см. рис. П1.1) оно закрыто, но после создания нового проекта или загрузки сохраненного ранее проекта одна из вкладок этого окна будет содер- жать список файлов, входящих в проект. Окно ЕдИог (окноредактора) используется для ввода и проверки исходного кода. Окно ОиЕриЕ (окно вывода) служит для вывода сообщений о ходе компиляции, сборки и выполнения программы. В частности, сообщения о возникающих ошиб- ках будут появляться именно в этом окне. Под заголовком главного окна, как и во всех АУшболуз-приложениях, распола- гается строка меню. Назначение команд меню и кнопок панелей инструментов мы будем рассматривать по мере необходимости, разбирая основные приемы ра- боты в ГОЕ. Здесь же только заметим, что для кнопок панелей инструментов пре- дусмотрена удобная подсказка. Если пользователь наведет курсор мыши на кноп-
ку и задержит его там на пару секунд, то всплывет окно подсказки, в котором бу- дет описано предназначение кнопки. Веуе1орег 81пс1ю позволяет создавать проекты разных типов, ориентирован- ных на различные сферы применения. В этой книге все программные примеры должны строиться в виде проекта типа \Л/1 пЗ2 АррПсаВ’оп — етр1:у рго]ес1:. Создание нового проекта Для создания нового проекта типа \Л/1’п32 АррПсаВоп нужно выполнить простую последовательность действий: □ Выполните команду меню Ейе ► Мем... □ В открывшемся диалоговом окне Мем выберите вкладку Ргсдес18. На этой вклад- ке надо выбрать тип \Л/ш32 АррПсаВоп, после чего указать имя проекта в тексто- вом поле Рго]ес1: Мате, например, Не11оЕготМ$дВох. Также потребуется ввести имя каталога размещения файлов проекта в текстовом поле ЬосаВоп1. Если ука- занный каталог отсутствует, то он будет создан автоматически. После этого остается только нажать кнопку ОК. □ После этого будет запущен так называемый мастер приложений АррПсаВоп М’гагф который открывает диалоговое окно \ЛйпЗ 2 АррПсаВ’оп —5Еер1 оГ ^пред- ложением определиться, какой подтип приложения вы хотите создать. В этом окне надо выбрать опцию Ап етр1:у ргозесЕ и нажать кнопку Нш’зН. □ После щелчка будет отображено окно М ем Рго]еС1 ТпГогтаВоп с параметрами про- екта и информацией о каталоге, в котором будет размещен создаваемый про- ект. В этом окне нужно нажать кнопку ОК. После выполненных шагов на экране будет отображено окно, внешний вид которого показан на рис. П1.2. Рис. П1.2. Рабочий стол \/1зиа1 С++ после создания проекта НеПоРготМздВох 1 Рекомендуется для размещения проектов выделить специальную папку, например 0:\МуРгсдесТз.
Прежде чем продолжать работу, свернем временно главное окно Ухзпа! С++ и заглянем в папку Не11оЕготМ$дВох, созданную мастером приложений для нашего проекта, а точнее, для нашей рабочей области. Там будут расположены файлы Не11оЕготМ$дВох.с1$до, НеПоЕготМздВох.дзр, НеПоЕготМздВох.ор!:, Не11оЕготМ$дВох.псЬ. Помимо этого там же будет расположена папка ОеЬид или Ве1еа$е, что зависит от конфигурации проекта. Поясним назначение перечисленных файлов: □ НеПоЕготМ$дВох.с1$№ — файл рабочей области проекта, используемый внутри интегрированной среды разработки. Он содержит всю информацию о проек- тах, входящих в данную рабочую область. □ Е1 е11оЕготМ$дВох.дзр — проектный файл, используемый для построения отдель- ного проекта. В ранних версиях Ухзпа! С++ этот файл имел расширение .так. □ Не11оЕготМ$дВох.ор1: — файл, в котором хранятся опции рабочей области про- екта. Благодаря этому файлу при каждом открытии рабочей области проекта все параметры Веуе1орег 81исйо, выбранные во время последнего сеанса рабо- ты с данной рабочей областью, будут восстановлены. □ Не11оЕготМ$дВох.псЬ — этот служебный файл создается компилятором и содер- жит информацию, которая используется в инструменте интегрированной сре- ды под названием СПаззУпехл/. Вкладка С1а88Х/1’е\л/ находится в окне Рго] ес1 Могкзрасе и показывает все классы С++, определения которых входят в состав проекта, а также все члены этих классов. □ ОеЬид — это папка, в которую будут помещаться файлы, формируемые компи- лятором и сборщиком. Из них нас будет интересовать только исполняемый файл, имеющий расширение .ехе. Развернем обратно главное окно Ухзпа! С++ с открытой рабочей областью (см. рис. П1.2), чтобы продолжить работу с первой программой. Следует отме- тить, что в окне Рго]ес1 Могкзрасе появились вкладки С1а$$ VIехл/ и Ейе VIехл/. Перейдите на вкладку Е11е VI ехл/. Эта вкладка предназначена для просмотра спис- ка файлов проекта. Откроем список Не11оЕготМ$дВох ГИе$, щелкнув мышью на знач- ке с изображением плюса. В результате будет отображено дерево списка файлов, содержащее пиктограммы папок 5оигсе Ейез, Неадег Ейез, Везоигсе Ейез. Все папки пусты, так как проект был создан с опцией Ап етр1:у рго]ес1:. Добавление к проекту файлов с исходным кодом В состав проекта можно добавлять как новые файлы, так и уже существующие. Обе эти ситуации надо рассматривать отдельно. Добавление существующего файла В этом случае файл с исходным кодом (пусть это будет файл Не11оЕготМ$дВох.срр) вы уже подготовили ранее. Теперь, чтобы добавить его в состав проекта, выпол- ните простую последовательность действий: □ Скопируйте исходный файл Не11оЕготМ$дВох.срр в папку рабочей области про- екта. В данном случае это будет папка Не11оЕготМ$дВох. □ Вернитесь к списку Не11оЕготМ$дВох Гйе$ в окне Рго]ес! Могкзрасе вашего проек- та и щелкните на папке 5оигсе Ейез. □ В появившемся контекстном меню выберите команду Ас1с1 ЕПез ±о Ео1с1ег...
□ В открывшемся диалоговом окне IпзеЕ1Те$ 1 п1:о Рго]ес1 выберите нужный файл (Не11оЕготМ$дВох.срр) и нажмите кнопку ОК. Добавление нового файла Добавление в состав проекта нового файла производится несколько иначе: □ Выполните команду меню ЕПе ► Мем... В результате этого будет открыто диа- логовое окно Мем. □ На вкладке Ейез выберите тип файла (в данном случае это будет С++ Боигсе Ейе). □ В текстовом поле Ейе Мате нужно задать имя файла (в данном случае Не11оЕгот МздВох.срр). При этом флажок Адд 1:о ргсд’ес!: должен быть установлен. □ Нажмите кнопку ОК. После этого в окне Ебйог появится окно текстового редактора с заголовком Не11оЕготМ$дВох.срр. Одновременно в окне Рго]ес1 Могкзрасе слева от папки 5оигсе Е1’1е$ появится знак плюса. Если щелкнуть по нему мышью, то папка 5оигсе Ейез раскроется и будет отображен список файлов. В настоящий момент в нем будет только один файл Не11оЕготМ$дВох.срр. ПРИМЕЧАНИЕ------------------------------------------------------------------ Имеется альтернативный способ добавления к проекту нового файла. Необходимо щелкнуть правой кнопкой мыши на пиктограмме папки Зоигсе Я1ез в окне Рго]ес1 Могкзрасе, а затем во всплывшем контекстном меню выбрать команду Абб Я1ез 1о Ро1бег. Появится диалоговое окно 1пзег1: Я 1ез 1п1о Рго]ес1 с текстовым полем «Имя файла». Необходимо ввести требуемое имя (на- пример, НеНоРготМздВох.срр) и нажать кнопку ОК. Рис. П1.3. Вид главного окна после ввода исходного текста в файл НеПоРготМздВох.срр Теперь займемся исходным текстом программы. Введите в окне текстового редактора, например, такой код:
#1пс1ис1е <м1 пс1о\л/8. И> Ж 1лПМАР1 Ш пМапп (НIМ5ТАК1СЕ Шнапсе, Н1М5ТАМСЕ ИРгеуТпзШсе, ЕРЗТР 1рСтс1Е1пе, Ш пСтОом) { МеззадеВохСШЕЕ, "Не11о, 1л11п32 шг!с1!", "Не11о Тгот Меззаде Вох", МВ_ОК); ге^ыгп 0; } На рис. Ш.З показан вид главного окна Ухзпа! С++ после проделанной работы. Многофайловые проекты Никаких особых усилий при создании многофайловых проектов вам прилагать не придется. Разработчики Неуе1орег 81псйо уже позаботились об удобстве вашей работы. Просто надо будет многократно повторить процедуру создания и добав- ления исходных файлов, описанную выше. В многофайловых проектах обычно присутствуют и заголовочные файлы. Они создаются/добавляются после щелчка правой кнопкой мыши на пиктограмме папки Неадет ЕНез в окне Рто] ес! Могкзрасе. Затем во всплывшем контекстном меню необходимо выбрать команду Ас1с1 ЕНез 1:о Ео1с1ег. В результате будет отображено диалоговое окно 1п$еП: ЕПез 1п±о Рго]ес± с текстовым полем Имя файла. После ввода требуемого имени, например, АпуМате.Н, нужно нажать кнопку ОК. ПРИМЕЧАНИЕ---------------------------------------------------------------- Папки Зоигсе Н1ез и Неабег Н1е5, пиктограммы которых можно увидеть в окне Рго]ес1 Могкзрасе, на самом деле физически не существуют. То есть все файлы помещаются в основную папку рабочей области проекта. Но, согласитесь, такое упорядочение дерева списка файлов в окне Рго]ес1 Могкзрасе очень удобно. Компиляция, компоновка и выполнение проекта Эти операции могут быть выполнены или при помощи меню Вш’1с1 главного окна, или с помощью кнопок панели инструментов. Следует кратко описать основные команды меню Вш 1д: □ Согпрйе — компиляция выбранного файла. Результаты компиляции отобража- ются в окне Оьйри!:. □ Вш Ш — компоновка проекта. Компилируются все файлы, в которых произош- ли изменения с момента последней компоновки. После компиляции происхо- дит сборка всех объектных модулей, включая библиотечные, в результирую- щий исполняемый файл. Сообщения об ошибках компоновки выводятся в окно Ои1:ри1:. Если обе фазы компоновки завершились без ошибок, то созданный ис- полняемый файл с расширением .ехе может быть запущен. □ ВеЬиШ АН — то же, что и ВиНд, но при выполнении этой команды компилиру- ются все файлы проекта независимо от того, были ли в них изменения. □ Ехесьйе — выполнение исполняемого файла, созданного в результате компо- новки проекта. Операции Сотрйе, ВшШ и Ехесьйе удобнее выполнять при помощи соответству- ющих кнопок панели инструментов Вийс1 М1п1Ваг или через «горячие» клавиши. Панель инструментов В ийс! Мтш’Ваг в увеличенном виде показана на рис. П1.4.
СОВЕТ-------------------------------------------------------------------------- Иногда при компоновке многофайлового проекта с командой ВиПс! будут отображаться сообщения об ошибках компиляции или сборки, которые вы не можете объяснить. Рекомендуем вам в этом случае обязательно попробовать выполнить команду кебыс! АН. Во многих случаях такой прием помогает «починить» ваш проект. СотрПе (С1И+Е7) Ехеси1е ргодгатт (С1Н+Е5) (Эо (Е5) ВиНс! (Е7) 1пзей/Ретоуе ВгеакРот! (Е9) Рис. П1.4. Панель инструментов ВиНб М1п|Ваг Откомпилируйте проект, щелкнув на кнопке ВиПд (Е7). Диагностические со- общения компилятора и сборщика будут отображаться в окне вывода 0и1:риЕ Мы надеемся, что у вас все будет в порядке и последняя строка в окне вывода будет выглядеть следующим образом: Не11оЕготМздВох.ехе - 0 егтог(з), 0 шгт’пд(з). Теперь вы можете запустить приложение на выполнение, нажав кнопку Ехесьйе Ргодгат (С1г1+Е5). Кнопка 6о (Е5) используется для запуска программы в отладочном режиме. Приложение может выполняться под отладчиком, только если оно построено в отладочной конфигурации проекта. Конфигурация проекта Ухзпа! С++ позволяет строить проект либо в отладочной конфигурации (АУЙ132 ВеЬп§), либо в выпускной конфигурации (АУш32 Ке1еазе). Пока программа не отла- жена, лучше работать с проектами в отладочной конфигурации. Обычно она ис- пользуется по умолчанию. Вы можете проверить, с какой конфигурацией на самом деле идет работа. Для этого надо выполнить команду меню Рго]’ес1: ► БеЕЬ'пдз.... В результате будет открыто диалоговое окно Рго]’ес1: ЗеЙпдз. Проверьте, какое зна- чение установлено в окне комбинированного списка 5ей1пд$ Еог:. Если это не \Л/1 пЗ 2 ОеЬид, то нужно выполнить команду меню Вш’1с1 ► 8е1 АсВ’уе СопВдигаВоп... и выбрать нужное значение. Как закончить работу над проектом Когда работа будет завершена, следует выполнить команду меню ЕПе ► С1о$е Могк-$расе. А можно просто закрыть приложение Ухзпа! С++. Как открыть проект, над которым вы ранее работали Есть три способа открыть проект: 1. Запустите среду Ухзпа! С++. Выберите в меню ЕПе пункт Ореп Могкзрасе... В открывшемся диалоговом окне надо отыскать вашу папку с проектом, а в ней —
файл Рго]ес1:Мате.сйм. Теперь можно открыть этот файл, щелкнув по нему мышью 2. Запустите среду Ухзпа! С++. Выберите меню ЕПе и наведите курсор мыши на пункт Кесеп!: Могкзрасез. Если во всплывшем меню со списком последних фай- лов, с которыми шла работа, вы найдете интересующий вас файл Рго]есЕМ ате.сЕм, то щелкните на нем. 3. Найдите вашу папку с проектом, а в ней отыщите файл Рго]ес±Мате.сЕм, после чего просто щелкните на нем мышью. Встроенная справочная система М1сгозо/1; Бесе1орег ХеШюгк — это набор онлайновых и оффлайновых служб, предназначенных для оказания помощи разработчику в написании прило- жений с использованием продуктов и технологий фирмы МтсгозоЙ. Справочная система М5БМ ЫЬгагу) обычно устанавливается на ком- пьютере вместе со средой МхсгозоЙ Ухзпа! 81псйо 6.0. Она содержит обширную информацию по программированию, в том числе и описание структур данных и функций \У1п32 АР1. Далее для краткости вместо термина «справочная система М8ВЖ будем употреблять короткий термин «М8ВЖ. Если М8^^ установлена, то в ЮЕ Ухзпа! С++ она доступна через меню Неф главного окна. Кроме того, очень удобно пользоваться интерактивной справкой. Если вы находитесь в окне ЕсНЕог, то достаточно навести текстовый курсор на интересующий вас оператор или библиотечную функцию С++ и нажать клавишу Е1. При этом будет вызвана М8^^ с отображением нужной вам информации. Если запрошенный термин встречается в разных разделах М8ВН то сначала появится диалоговое окно Найденные разделы. В списке разделов надо выбрать тот, в котором упоминается «Ухзпа! С++» или «\У1пс1о\\ъ 8ВК». Работа с отладчиком Полное описание возможностей встроенного отладчика Ухзпа! С++ и приемов работы с ним может потребовать отдельной книги. Поэтому мы рассмотрим толь- ко начальные сведения о работе с отладчиком Ухзпа! С++. Проще всего это сде- лать, написав программу, заведомо содержащую несколько ошибок, а затем по- смотреть, как с помощью отладчика можно найти и исправить эти ошибки. Также мы научимся устанавливать в программе точки прерывания и выпол- нять программу до заданной точки. Когда во время выполнения встречается точ- ка прерывания, программа останавливается, а на экране появляется отлаживае- мый код. Это дает возможность детально выяснить, что именно происходит в программе в текущий момент. Кроме того, программу можно выполнять последовательно, строку за строкой. Такой процесс называется пошаговым выполнением. Этот режим позволяет сле- дить за тем, как изменяются значения различных переменных. Иногда он помога- ет понять, в чем заключается проблема. Если обнаруживается, что переменная принимает неожиданное значение, то это может послужить отправной точкой для выявления ошибки. После обнаружения ошибки ее можно исправить и заново выполнить програм- му в отладочном режиме.
Чтобы не отвлекаться на детали реализации АУшболуз-приложений, построим нашу демонстрационную программу как консольное приложение. Консольные приложения выглядят подобно программам, написанным в среде М8-ВО8. Для создания подобного проекта нужно сначала выполнить команду меню Ейе ► Мем... После этого нужно перейти на вкладку РгсдесЕз диалогового окна Мем. На этой вкладке надо выбрать тип \Л/1’п32 Соп$о1е АррПсаВоп, ввести имя проекта в текстовом поле Рго]ес1 Мате, указать имя каталога для размещения файлов в поле ЬосаВоп и нажать кнопку ОК. Назовем нашу программу именем Видду. Программа должна вычислять сред- нее арифметическое первых пяти натуральных чисел. Нетрудно догадаться, что в итоге должно получаться число 3, однако из-за специально сделанных ошибок программа сначала будет выдавать неправильный ответ1. Для построения программы создайте проект типа «консольное приложение» с именем Видду и добавьте к проекту файл Ьидду.срр со следующим текстом: #1пс1ис1е <1о81геат.И> 1п1 гтшпО { сопбС. И = 5; Ш а[М] = {1, 2, 3, 4, 5}; оаС. Зит, ауегаде; 1п! 1 ; Тог (1 =1; 1 < М; 1++) зит += а[1]; ауегаде = зит / И; сои! « "ауегаде = " « ауегаде « епс11; геТигп 0; } Откомпилируйте проект и запустите его. В консольном окне приложения бу- дет выведено нечто вроде следующего результата: ауегаде = -2.14748е+007 Программа вычислила, что среднее арифметическое первых пяти целых чисел равно -21 474 800 (на вашем компьютере может быть и другое число), а это мало похоже на число 3.0. Теперь можно начать отладку нашей злополучной программы. Установка точки прерывания Точка прерывания позволяет остановить выполнение программы перед любой вы- полняемой инструкцией, чтобы продолжать выполнение программы либо в поша- говом режиме, либо в непрерывном режиме до следующей точки прерывания. Чтобы задать точку прерывания перед некоторым оператором, необходимо ус- тановить перед ним текстовый курсор и нажать клавишу Е9 или щелкнуть мышью на кнопке 1п8еП:/Вето7е В геакРош!: на панели инструментов Вийс1 М1п1 Ваг. Точка пре- рывания обозначается в виде коричневого кружка на левом поле окна редактирова- ния. Повторный щелчок на указанной кнопке снимает точку прерывания. В про- грамме может быть несколько точек прерывания. Выполнение программы до точки прерывания Программа запускается в отладочном режиме с помощью команды меню В шТд ► 51а И: ОеЬид ► 6о. В результате код программы выполняется до той строки, на которой 1 Пожалуйста, закройте глаза на ошибки, если вы сразу увидите их в тексте программы. Приводи- мый пример несложен и предназначен только для изучения возможностей отладчика.
установлена точка прерывания. Затем программа останавливается и отображает в окне ЕдИог ту часть кода, где находится точка прерывания. Причем желтая стрелка на левом поле указывает на строку, которая будет выполняться на следующем шаге отладки. Продолжим демонстрацию описываемых средств на примере программы Видду. Установите точку прерывания перед оператором Гог и запустите программу в от- ладочном режиме, нажав клавишу Е5. Обратите внимание на то, что меню Вш’1с1 заменилось на меню ОеЬид. Вид глав- ного окна Ухзпа! 81пс1ю в этот момент показан на рис. П1.5. Стрелка на левом поле окна редактора указывает на инструкцию Гог, и отладчик остановился перед ее выполнением. Среди различных команд меню ОеЬид особый интерес представляют коман- ды 5Гер 1пГо (Е11), 5Гер Оуег (ЕЮ), 5Гер ОиГ (5ЫГ1:+Е11), Кип То Сигзог (СГг1+Е10) и 5Гор ОеЬиддшд (5ЫГ1:+ Е5). Выполнение последней команды вызывает заверше- ние работы с отладчиком. О дополнительных окнах отладчика, расположенных в нижней части главного окна Ухзпа! С++, мы поговорим попозже. Рис. П1.5. Меню ОеЬид и дополнительные окна отладчика Пошаговое выполнение программы Нажимая клавишу ЕЮ, можно выполнять один оператор программы за другим. Предположим, что при пошаговом выполнении программы вы дошли до строки, в которой вызывается некоторая функция Гипс1(). Если вы хотите посмотреть, как работает код вызываемой функции, то надо нажать клавишу ЕН. Если же внут-
ренняя работа функции вас не интересует, а нужен только результат ее выполне- ния, то надо нажать клавишу ЕЮ. Допустим, что вы вошли в тело функции Еипс1(), нажав клавишу Е11, но через некоторое время, пройдя несколько строк кода, решили выйти из функции. В этом случае надо нажать клавиши ЗНЙТ+ЕИ. Существует и другая возможность пропустить пошаговое выполнение неко- торого фрагмента программы. Надо установить текстовый курсор в нужное место программы и нажать клавиши СЮ+ЕЮ. Продолжим отладку программы Видду. Нажмите клавишу ЕЮ. Указатель сле- дующей выполняемой команды переместится на следующий оператор: зит += ; Проверка значений переменных во время выполнения программы Чтобы узнать значение переменной зит, в которой будет накапливаться сумма элементов массива а, задержите над ней указатель мыши. Рядом с именем пере- менной на экране появляется подсказка со значением этой переменной: зит = -1.07374е+008 Ага!!! Ведь еще не было никакого суммирования, и, следовательно, перемен- ная зит, по идее, должна быть равна нулю. Вот в чем дело! Мы забыли обнулить переменную зит до входа в цикл. Нажмите комбинацию клавиш 5Н1Л+Е5, чтобы выйти из отладчика и испра- вить найденную ошибку. Найдите строку с о следующим объявлением: оаС. зит, ауегаде; Добавьте в нее инициализацию переменной зит: оаС. зит = 0, ауегаде; Откомпилируйте заново проект при помощи кнопки ВиНд (Е7) и запустите его кнопкой ЕхесиТе Ргодгат (С1Г1+Е5). В результате будет отображен новый результат: ауегаде =2.8 Уже теплее, но все равно еще неправильно. Нажмите любую клавишу для за- вершения работы приложения. Необходимо продолжить отладку. Установите точку прерывания перед оператором Еог и запустите программу в отладочном режиме, нажав клавишу Е5. Теперь нажмите клавишу ЕЮ. Указатель следующей выполняемой команды переместится на следующий оператор: зит += а[1]; Нажмите клавишу ЕЮ, и указатель переместится на следующий оператор: Тог (1 = 1; 1 < М; 1++) Задержите теперь над переменной зит указатель мыши. Рядом с именем пере- менной появится подсказка со значением этой переменной: зит = 2. Стоп!!! Позади первая итерация цикла, и в переменной зит должно находить- ся значение первого элемента массива а, то есть число 1. А мы получили число 2, то есть значение второго элемента массива а. Пришло время вспомнить, что в языке С++ нумерация элементов массива начинается с нуля. Поэтому ошибка находит- ся в заголовке цикла: Тог (1 =1; 1 < М; 1++)
Эту ошибку нужно немедленно исправить. Повторите действия для исправле- ния ошибки. Надо выйти из отладчика и исправить текст программы в операторе Еог: Гог (1 =0; 1 < М; 1++) Теперь проект надо заново откомпилировать и запустить. Если в процессе ис- правления вы не внесли новых ошибок, то должен получиться результат ауегаде =3.0. Итак, программа работает правильно. В заключение отметим, что отладчик предоставляет и другие возможности для наблюдения за значениями переменных во время выполнения программы. Окна УапаЫез и УУа1сН Когда программа запускается в отладочном режиме, отладчик выводит два до- полнительных окна для наблюдения за значениями переменных (см. рис. П1.5). Эти два окна размещаются ниже окна ЕсИЕог, но выше окна Оьйри!:. Окно Уал’аЫез располагается слева, а окно УУаЕсН находится справа. Они могут быть спрятаны или вызваны на экран при помощи команды меню \йе\л/ ► ОеЬид \Л/1 пдохл/з. На вкладке АиЕо окна Уал’аЫез отображаются значения последних переменных, с которыми работал УНпа! С++. В верхней части окна Уал’аЫез находится окно раскрывающегося списка Согйех!:, об использовании которого будет сказано ниже. В окне УУаЕсН на любой из вкладок можно задать имя переменной, за текущими значениями которой вы хотите понаблюдать. Более подробную информацию о работе с этими окнами вы можете найти в справочной системе через меню Неф главного окна Ухзпа! С++. Некоторые полезные инструменты ЮЕ МхсгозоЙ Ухзпа! С++ 6.0 содержит много удобных средств, облегчающих раз- работку программ. К ним относятся, в частности, редакторы ресурсов, работа с которыми рассматривается в главах 5 и 7. Из других инструментов большую пользу могут принести команда Е1пд 1’п ЕНез и окно Соп±ех± со списком нескольких последних вызовов функций. Команда «Р!пс1 !п Р!1е$» Доступ к команде Е1пд 1’п ЕНез обеспечивает кнопка на панели инструментов, пока- занная на рис. П1.6. Рис. П1.6. Кнопка Нпс! 1п РПез Допустим, вы хотите найти все вхождения в файлы вашего проекта какого- либо имени. Наведите текстовый курсор на интересующее вас имя и нажмите кнопку Е1’пс1 1’п ЕНез. Появится диалоговое окно Е1’пс1 1’п ЕНез, в котором текстовое поле уже будет содержать интересующее вас имя. Вы можете дополнительно взве- сти флажки МаЕсЬ \л/Но1е \л/огс1 оп1у (искать только отдельные слова) и/или МаЕсЬ сазе (искать с учетом регистра букв). После этого остается нажать кнопку Е1пф и вы сразу получите в окне Оьйри!: полный список вхождений заданного имени по всем файлам проекта.
Попробовав один раз, вы уже не сможете отказаться от регулярного обще- ния с этим инструментом, ведь он так облегчает повседневную жизнь програм- миста! Окно Соп1ех1 Наиболее неприятной для программиста является ситуация с неожиданным кра- хом программы, при котором система выводит диалоговое окно с текстом «Ассезз \/1о1айоп». Это значит, что в результате какой-то ошибки ваша программа попыта- лась обратиться к недоступному для нее адресу оперативной памяти. Окно Согйех!:, находящееся в верхней части окна УапаЫез (см. рис. П1.5) и пред- ставляющее собой раскрывающийся список, помогает программисту в таких си- туациях быстро локализовать то место в программе, которое явилось причиной возникновения нештатной ситуации. В этом окне содержится список нескольких последних вызовов функций перед фатальным концом работы. В этом окне надо найти самый последний вызов, который принадлежит ваше- му исходному коду. Если щелкнуть мышью по этому элементу списка, то стрелка на левом поле окна редактора укажет на строку программы, содержащую «крими- нальный» вызов. Далее — уже дело техники (или аналитической работы ваших серых клеточек), чтобы сообразить, что же у вас «не так, как надо...». Рассмотрим простой пример. Довольно частой ошибкой является использова- ние указателя, для которого не выделена требуемая память, чтобы хранить значе- ние соответствующего типа. Предположим, что в групповом проекте использует- ся некий класс А, разрабатываемый двумя программистами1. Допустим, что в классе А имеется член со следующим определением: КЕСТ* рКесТ; Предположим далее, что первый программист, кодирующий конструктор клас- са А, забыл выделить память для указателя рКесЕ А в это время второй програм- мист, разрабатывающий метод 5Ьо\л/, будучи уверенным, что память уже выделена, напишет следующий фрагмент: уо1с1 А: :ЗНо\л/() { 1пТ хО = рКесТ->1еТТ; Ш уО = рКесТ->Тор; } Все! «Аннушка уже пролила масло...»® Любая программа, использующая объект класса А, при вызове метода 5Ьо\л/ за- вершится необработанным исключением (ипкапбес! ехсерНоп), о чем сообщает окно примерно такого вида (рис. П1.7). Рис. П1.7. Печальная весть о кончине программы 1 Мы здесь абстрагируемся от рекомендаций по надлежащей организации групповой разработки про- екта и от рекомендаций по стилю кодирования. Нарушение этих рекомендаций как раз и является питательной средой для появления ошибок того типа, который мы рассматриваем.
Однако если вы запустили приложение с такой ошибкой в отладочном режи- ме (Е5), то после щелчка на кнопке ОК отладчик показывает картинку, приведен- ную на рис. П1.8. Рис. П1.8. Информация в окне Соп1ех1, помогающая локализовать место краха программы Последним вызовом функций в списке, который показывает окно Соп1:ех1:, яв- ляется вызов метода А::5Ьо\л/(). Цветная стрелка на левом поле окна редактора по- казывает инструкцию, при выполнении которой и случилось наше несчастье. Так как в этой инструкции используется указатель рКес1, вполне обоснованным будет ваше желание проверить, а все ли с этим указателем в порядке? Счастливого плавания!
Приложение 2 Интегрированная среда У15иа1 51исНо.МЕТ Мы предполагаем, что пакет МхсгозоЙ Ухзпа! 81исНо.МЕТ уже установлен на ва- шем компьютере. Работа в ГОЕ Ухзпа! 81исНо.МЕТ имеет много общего с работой в ГОЕ Ухзпа! 81псйо 6.0, описанной в приложении 1. Но в то же время имеются отличия как в интерфейсе пакета, так и в используемой терминологии. В этом приложении описываются шаги по созданию нового проекта типа \Л/1п32 АррПсаНоп - етр1:у рго]ес1:, компиляции, сборке и выполнению приложе- ния. После запуска Ухзпа! 81псйо^ЕТ главное окно программы имеет вид, показан- ный на рис. П2.1. В зависимости от настроек для вашего пакета УНпа! 81исНо.МЕТ вид главного окна может несколько отличаться от того, который показан на ри- сунке. Рис. П2.1. Главное окно (рабочий стол) \/15иа1 5Н1с1ю.ЫЕТ
Конечно, уже сейчас можно найти первые отличия от Ухзпа! 81пс11о 6.0. Окно Рго]’ес1 АУогкзрасе переместилось от левого края к правому краю главно- го окна и изменило свое название. Теперь оно называется ЗоШйоп Ехр1огег. На месте окна ЕсйТог появилось окно с вкладками, которое сначала содержит только одну страницу 5±аг±Раде. По мере построения проекта в этом окне могут появляться и другие страницы. Таким образом, термином «Решение» теперь называется то, что раньше назы- валось «Рабочей областью». «Решение» также может содержать несколько про- ектов, но все наши программные примеры содержат ровно один проект. Окно 0 1йри1: осталось на том же месте и по-прежнему служит для вывода сооб- щений о ходе компиляции, сборки и выполнения программы. Создание нового проекта Для создания нового проекта типа \Л/1 п32 АррИсаВ’оп — етр1:у ргсдес! нужно выпол- нить следующую последовательность действий. Сначала надо выполнить коман- ду меню ЕПе ► Мем ► Ргсд’ес!:. Это приведет к отображению окна Мем Рго]ес±, пока- занного на рис. П2.2. Рис. П2.2. Окно Ыем Рго]ес± В списке Рго]ес1 Турез следует открыть папку У15иаIС++ Рго]ес1. Окно Мем Рго]ес1: примет вид, показанный на рис. П2.3. В списке подтипов для \й$иа1 С++ Рго]ес1 выберите пиктограмму папки \Л/1’п32. Окно Мем Рго]ес1: примет вид, показанный на рис. П2.4. В окне Тетр1а1:е$ надо выделить шаблон \Л/1п32 Рго]’есЕ Затем надо указать имя проекта в текстовом поле Мате, например, Не11оЕготМ$дВох. Также введите имя папки для размещения проекта в поле ЕосаВоп, например, Е:\МуРго]’ес1:. После на- жатия кнопки ОК будет отображено окно \Л/1 пЗ2 АррИсаВоп \Л/12агс1 - НеИоЕготМздВох (рис. П2.5).
Рис. П2.3. Окно Ые\л/ Рго]ес± после раскрытия папки \/15иа1 С++ Рго]ес1 Рис. П2.4. Окно Ые\л/ Рго]ес1 после выбора папки \Л/1п32 Рис. П2.5. Окно \Л/1п32 АррНсайоп \Л/1гагс1 - НеПоРготМздВох Щелкните мышью на пункте АррПсаВ’оп ЗеШпдз. Окно мастера примет вид, по- казанный на рис. П2.6. В группе флажков АдсН1:1опа1 орВопз установите флажок Етр!у рго]ес1: и нажми- те кнопку Нт$Н. Окно Ухзпа! 5гис11о.НЕТ примет вид, показанный на рис. П2.7.
Рис. П2.6. Окно после выбора пункта АррНсайоп 8ей1пдз Реаду Рис. П2.7. Окно \/15иа1 5Н1с1ю.ЫЕТ после создания нового проекта Создание нового проекта завершено. Откроем папку проекта Не11оЕготМ$дВох, чтобы посмотреть на служебные файлы, созданные мастером. Обратите внимание на два файла. Файл Не11оЕготМ$дВох.$1п — это файл «решения» (рабочей области), а Е1е11оЕготМ$дВох.7срго]’ — это файл проекта. Таким образом, расширение .$1п здесь используется вместо расширения. дз\л/ для Ухзпа! С++ 6.0 и, соответственно, расширение .усрго]’ — вместо расширения .с1$р. Добавление к проекту нового файла Чтобы добавить к проекту новый файл, нужно выполнить простую последова- тельность действий. Сначала щелкните правой кнопкой мыши на пиктограмме
папки 5оигсе Е1’1е$ в окне 5о1ийоп Ехр1огег. В появившемся контекстном меню надо выбрать команду Ас1с1 ► Ас1с1 Мем Кет. В результате будет отображено окно Ас1с1 Мем Кет (рис. П2.8). Рис. П2.8. Окно Ас1с1 Ыем Кет В окне Тетр1а1:е$ нужно выделить шаблон С++ Ейе или Н еадег ЕНе в зависимости от того, какого типа файл нужно присоединить к проекту. Введите имя файла в текстовое поле Мате, например, Не11оЕготМ$дВох, и нажмите кнопку Ореп. После этого в списке файлов 5оигсе Ейез окна 5о1иВоп Ехр1огег появится обозначение но- вого файла, например, Е1е11оЕготМ$дВох.срр, а также откроется окно текстового ре- дактора. Рис. П2.9. Окно У15иа1 81ис1ю.ЫЕТ после ввода текста в файл НеПоРготМздВох.срр
Введите текст в окне текстового редактора, например: #1пс1ис1е <м1 пс1о\л/8. И> Ш 1л/1МАР1 ШпМа1п(Н1М8ТАМСЕ Шнапсе, Н1М5ТАМСЕ ИРгеу1п^апсе, ЕРЗТР 1рСтс1Е1пе, 1 ггЬ пСгткОом) { МеззадеВох(ШЕЕ, "Не11о, Ш п32 шг!с1!", "Не11о Егот Меззаде Вох", МВ_ОК); гебыгп 0; } После ввода текста основное окно Ухзпа! 51ис11о.НЕТ примет вид, показанный на рис. П2.9. Сохраните набранный текст с помощью команды меню ЕПе ► 5ауе. Компиляция, сборка и выполнение Выберите в меню главного окна команду Вш’1с1 ► ВшШ 5о1иВоп (№1+5 К 1’11+В). Сообщения о ходе компиляции и сборки будут поступать в окно Ои1ри1. У нас ошибок нет, поэтому окно выглядит, как показано на рис. П2.10. Рис. П2.10. Окно \/1зиа1 8Щс1ю.ЫЕТ после компиляции и сборки проекта НеПоРготМздВох Откомпилированное приложение можно запустить на выполнение двумя спо- собами. Выполнение в обычном режиме производится с помощью команды меню ОеЬид ► 51аг1: ШЬои1: ОеЬиддшд (№1+ Е5). Запуск в отладочном режиме производится с помощью команды меню ОеЬид ► 51:аг1: (Е5).
Работа с редакторами ресурсов Для добавления ресурсов в приложение вместо команды меню 1пзег1: ► Кезоигсе, которая применялась в Ухзпа! 81пс1ю 6.0, здесь используется команда Рго]’ес1: ► АсМ Кезоигсе. После этой команды появляется диалоговое окно АсМ Кезоигсе. Выбор типа добавляемого ресурса делается так же, как в Ухзпа! 81исйо 6.0. В Ухзпа! 81ис11о.НЕТ вместо панели инструментов СогйгоШ используется окно ТооШох, вызываемое с помощью команды меню У1е\л/ ► ТооШох. Это окно располо- жено в левой части главного окна 81ис11о.НЕТ. Необходимый элемент управления в окне ТооШох выбирается щелчком мыши. Затем для размещения элемента управления на форме диалога делается повтор- ный щелчок мыши в том месте, где следует расположить элемент. Окно свойств для элементов управления здесь вызывать не нужно, так как свой- ства выбранного элемента управления отображаются в окне РгорегВез, которое находится либо ниже окна ЗоШйоп ЕхрШгег, либо рядом с ним. Другие действия с проектами осуществляются примерно так же, как и при ра- боте в среде Ухзпа! 81исйо 6.0.
Приложение 3 Работа с утилитой 5ру++ В МхсгозоЙ Ухзпа! 81пс1ю 6.0 (а также в Ухзпа! 81ис11о.НЕТ) есть инструменталь- ное средство под названием 5ру++. Эта программа «шпионит» за окном другого приложения, чтобы разработчик мог иметь представление о том, какие сообще- ния проходят через это окно. В этом приложении описывается работа с утилитой 5ру++ в составе Ухзпа! 81псйо 6.0. Запустить утилиту можно при помощи команды меню Ухзпа! 81псйо Тоо1$ ► 5ру++. Окно утилиты 5ру++ имеет вид, показанный на рис. П3.1. Рис. П3.1. Окно утилиты 8ру++ Технология шпионского наблюдения за интересующим вас приложением XXX довольно проста. Сначала запустите на выполнение приложение XXX. Затем раз- местите на экране окно утилиты Бру и окно приложения XXX так, чтобы они были видны одновременно и не перекрывали друг друга. Выполните команду меню Бру ► Ме$$аде$. В результате будет отображено диалоговое окно Ме$$аде ОрВопз (рис. П3.2). На вкладке \Л/1пс1оу\/5 схватите левой кнопкой мыши пиктограмму Н п дет Тоо1 и, удерживая кнопку, перетащите ее в то окно приложения XXX, за которым вы хоти- те наблюдать. После этого отпустите левую кнопку мыши.
Рис. П3.2. Окно Меззаде Орйопз В группе 5е1ес1:ес1 0Ь]'ес1: будет отображена следующая информация: □ дескриптор окна (\Л/1пс1о\л/); □ заголовок окна (Тех!); □ имя класса; □ стиль; □ координаты окна; □ дескриптор потока (ТНгеад Ю); □ дескриптор процесса (Ргосезз Ю). Теперь надо нажать кнопку ОК, и будет отображено окно Меззадез (\Л/1пс1о\л/...) (рис. ПЗ.З). Рис. ПЗ.З. Окно Меззадез (\Л/1пс1о\л/...) После этого утилита 5ру++ готова фиксировать все сообщения, поступающие в «помеченное» окно. Сообщения фиксируются в окне Меззадез (\Л/1пс1о\л/... ) в сле- дующем формате: Текущий номер сообщения Дескриптор окна Код сообщения Идентификатор сообщения Значения параметров
Код сообщения может иметь одно из значений, приведенных в табл. П3.1. Таблица П3.1. Возможные коды сообщений Код Описание Р Сообщение было отправлено в очередь сообщений с помощью функции Роз^Меззаде 8 Сообщение было отправлено в очередь сообщений с помощью функции ВепбМеззаде з Сообщение было отправлено с помощью ВепбМеззаде, но служба безопасности (зесип1у) не позволяет получить возвращаемое значение к Каждой строке с кодом 8 должна соответствовать строка с кодом к (геЩгп), которая содержит значение ответного сообщения. Иногда вызовы сообщений вложены друг в друга, когда обработчик одного сообщения посылает другое сообщение При первом сеансе использования 5ру++ вас, по-видимому, поразит количество сообщений, протекающих через окно. Например, при нажатии кнопки закрытия окна в основном окне наблюдается более сорока сообщений. Можно, однако, фильтровать поток сообщений, фиксируемых в окне Меззадез (\ЛЛпс1о\л/ ... ), чтобы упростить анализ работы приложения. Для фильтрации сооб- щений вернитесь в диалоговое окно Меззаде ОрВопз и откройте вкладку Меззадез (рис. П3.4). Рис. П3.4. Вкладка Меззадез Здесь есть два механизма включения/исключения фиксируемых сообщений: 1. В группе Меззаде бгоирз можно включить или исключить группу сообщений, относящихся либо к некоторому типу элемента управления, либо к некото- рому внешнему источнику сообщений. Например, сообщения, поступающие от мыши. 2. В списке Меззадез 1:о \йем содержится полный перечень сообщений, и каждое из них можно включить или исключить. По умолчанию все сообщения вклю- чены. Использование утилиты 5ру++ помогает иногда понять, почему ваше прило- жение работает не так, как вы ожидали.
Список литературы 1. Петпзолд Ч. Программирование для \У1пс1о\\-з 95. Т. 1. — СПб: ВНУ — Санкт- Петербург, 1997. — 739 с. 2. Мюррей У., Паппас К. Создание переносимых приложений для АУтскпуз. — СПб: ВНУ — Санкт-Петербург, 1997. — 816 с. 3. Саймон Р. МхсгозоЙ АУтскпуз 2000 АР1. Энциклопедия программиста. — Киев: ДиаСофт, 2001. — 1008 с. 4. Вильямс М. Программирование в АУшбом^з 2000. Энциклопедия пользователя. — Киев: ДиаСофт, 2000. — 640 с. 5. Рихтер Дж. АУтбом^з для профессионалов: создание эффективных АУт32- приложений с учетом специфики 64-разрядной версии АУтбом^з. — СПб: Питер, 2001. - 752 с. 6. Юань Фень. Программирование графики для \Упк1о\\-з. — СПб: Питер, 2002. — 1072 с. 7. Румянцев П. В. Азбука программирования в \Уш32 АР1. — М.: Горячая линия - Телеком, 2001.-312 с. 8. Касперски К. Техника оптимизации программ. Эффективное использование памяти. — СПб: ВНУ — Санкт-Петербург, 2003. — 464 с. 9. Павловская Т., Щупак Ю. С++. Объектно-ориентированное программирование: Практикум. — СПб: Питер, 2004. — 265 с.
Алфавитный указатель А Р а!ош1с ассезз 459 ра^е Ше 471 АУай-функции 462 В ра^е зрасе 67 ра1е!1е 59 хутбоху ргосебиге 42 хуог16 зрасе 67 ЬИтар 59 ЬИтар 81;ге1;сЫп§ шобе 182 ЬгпзЬ 59 ВиИоп 323 0 реп 59 рЬузхса! беухсе зрасе 67 рптагу Бггеаб 443 Рго^гезз Ьаг 423 рго|’ес1 хуогкзрасе 563 А ВВВ 175 Я активное окно 18 ВВК 19 гефоп 59 Анонимный канал 470 беуюе зрасе 67 гШса1 зесНоп 460 атомарный доступ 459 сИа1о§Ьох 304 сИа1о§ ргосебиге 304 сИа1о§ 1ешр1а1е ипйз 308 5 зсап собе 214 Б библиотека динамической В1В 175 8ВК 19 компоновки 19 В1В-секция 201 зесигйу безспр1ог 442 Библиотеки динамической Е зетаркоге 466 зког1си1 тепи 293 компоновки (ВББ) 20 бинарные растровые еуеп! 464 8Нбег 428 операции 79 Р 1оп1 59 зоске!з 470 8рт 435 з1а1из Ьаг 413 В векторная графика 174 1оп1 шаррт§ 128 зхуар Ше 471 венгерская нотация 27 6 СВ1 19, 58 Т 1Ьгеаб 443 Видимый регион 60 Виртуальный код клавиши 216 С1Л 16 1Ьгеаб соп!ех! 443 виртуальный код клавиши Н ЬапсПе 20 ТооШр 391 ТгиеТуре 119 Т8С 489 214 Время АУтбохуз 486 выключка 134 Ьо1кеу 273 и Г М ИТС-время 487 главное меню 272 ша11з1о18 470 таррт§ тобез 71 тоизе сигзог 226 М8М2 470 V У1г1иа1 Метогу Мапа^ег 471 горячая клавиша 273 д двойная буферизация 543 ти!ех 467 УММ 471 дескриптор 20
дескриптор защиты 442 дескрипторы экземпляра приложения 55 диалоговая процедура 304 Диалоговое окно 304 динамическое связывание 20 Диспетчер виртуальной памяти 471 дочернее окно 18 И Именованный канал 470 Индикатор процесса 423 информационный контекст 66 К каскадное меню 274 квант 444 Кисть 35 кисть 59 класс окна 23 «классы КИШ» 184 КАУнс1Ех» 408 классы КИйэЗесИон 557 КИосншен! 146 КТппег 492 КАУнс! 46 (УГппег 502 Клиент 473 Кнопка 323 Комбинированный список 356 Контекст потока 443 Контекст устройства 43, 59 Контекст устройства в памяти 65 Контекстное меню 293 контекстное меню 272 координаты АУтйохуз клиентские 28 оконные 28 экранные 28 Кривая Безье 86 Критическая секция 460 курсор мыши 226 Л Латентность 491 Логический шрифт 124 М макросы Се1ВУа1не 77 СеЮУаЬе 77 СеШУЛне 77 НПУОШЭ 55 БОАУОКИ 55 МАКЕ1МТКЕ8ОИКСЕ 253 РАБЕТТЕШИЕХ 77 РАБЕТТЕКСВ 77 КОВ 77 Метафайл 206 Мировая система координат 67 модальные диалоговые окна 304 мультимедийная библиоте- ка млшшп.НЬ 266 мьютекс 467 Н немодальные диалоговые окна 304 немые клавиши 218 О Обновляемый регион 60 окно АУтйохуз клиентская область 29 окно верхнего уровня 18 Окно подсказки 391 Окно редактирования 341 окно сообщений 26 оконная процедура 22, 42 оконный класс регистрация класса 32 стандартный (предопре- деленный) 23 стили 33 описатель 20 Отсечение 112 П «палитра» 159 палитра 59 ЕЕанель инструментов 385 первичный поток 443 перо 59, 87 ЕЕиксел 76 ЕЕиктограмма 243 пиктограмма 28 поразрядные флаги 33 ЕЕоток 443 поток 17 ЕЕримитив синхронизации 459 ЕЕропускная способность 491 ЕЕроцесс 443 процесс 17 Р Рабочая область 563 разрешение системного таймера 487 растеризация шрифта 120 растровая графика 174 Растровая кисть 99 растровая операция 79 растровое изображение 59 Регион 59, 109 Регион отсечения 60 Регулятор 428 редактор. См. См. АррВгохузег режим масштабирования растра 182 режим отображения 71 режим отображения ММ ТЕХТ 72 режим пользователя 441 режим рисования 80 режим смешивания фона ОРА2ИЕ 81 ТКАХ8РАКЕХТ 81 режим ядра 441 Ресурсы 242 родительское окно 18 С семафор 466 Сервер 473 Система координат устрой- ства 67 системная очередь сообще- ний 22 Системное время 487
Скан-код 214 скан-код 41 Событие 464 совместимый контекст 65 Сокеты 470 сообщение 21 «сообщения РВМ8ЕТРО8» 424 РВМ8ЕТКАХСЕ» 423 РВМ8ЕТ8ТЕР» 424 РВМ 8ТЕР1Т» 424 8ВСЕТКЕСТ» 414 8В8ЕТМ1ХНЕ1СНТ» 415 8В 8ЕТРАКТ8» 414 8В 8ЕТТЕХТ» 414 8В81МРБЕ» 414 ТВАВВ8ТК1ХС» 390 ТВ АПТО81ХЕ» 391 ТВМ СЕТРО8» 431 ТВМ8ЕТБ1ХЕ81ХЕ» 430 ТВМ8ЕТРАСЕ81ХЕ» 430 ТВМ8ЕТКАХСЕ» 430 ТВМ 8ЕТТ1СЕКЕ2» 430 ИВМСЕТРО8» 437 ИВМ8ЕТКАХСЕ» 437 АУМСНАК» 218 УУМСОММАХВ» 284 УУМСОХТЕХТМЕХП» 293 АУМСТБСОБОКЕВ1Т» 343 УУМСТБСОБОК8ТАТ1С» 321 АУМВКААУ1ТЕМ» 325 УУМН8СКОББ» 430, 437 УУМ1Х1ТВ1АБОС» 318 АУМ1Х1ТМЕХИ» 284 УУМКЕУВОУУХ» 216 АУМ КЕУИР» 216 УУМК1ББЕОСП8» 221 УУМБВПТТОХВВБСБК» 227 АУМБВИТТОХВОАУХ» 227 АУМ МЕХИСНАК» 284 АУММЕХП8ЕБЕСТ» 284, 416 АУМ МОП8ЕМОУЕ» 226 «сообщения (продолжение) АУММОП8ЕАУНЕЕБ» 227 УУМ ХОТ1ЕУ» 385 УУМ 8ЕТЕОСП8» 221 УУМ 8ЕТЕОХТ» 321 УУМ8У8СОММАХВ» 284 АУМУ8СКОББ» 430, 437 сообщения УУМСБО8Е 45, 51 УУМ СОММАХВ 51 УУМ СОРУВАТА 474 УУМСКЕАТЕ 51 УУМВЕ8ТКОУ 46, 51 УУМЕКА8ЕВКСХВ 61 УУМН8СКОББ 144 УУМ1Х1ТВ1АБОС 51 АУМ МОУЕ 51 УУМ ХСРА1ХТ 61 УУМ РА1ХТ 42, 51, 61 \УМ_(ХЛТ 24, 41 УУМ 81ХЕ 51 УУМТ1МЕК 51 АУМУ8СКОББ 144 очередь сообщений приложения 22 параметры сообщения 23 синхронные и асинхрон- ные 53 цикл обработки сообще- ний 24, 40 Список 344 сплайн Безье 86 Сплошная кисть 97 статическое связывание 19 Страничная система координат 67 страничный файл 471 Строка состояния 413 «структуры В1ТМАР» 193 В1ТМАРЕ1БЕНЕАВЕК» 176 В1ТМАР1ХЕО» 183 В1ТМАР1ХЕОНЕАВЕК» 176 СНОО8ЕСОБОК» 377 СНОО8ЕЕОХТ» 377 ВКААУ1ТЕМ8ТКПСТ» 325 «структуры (продолжение) 1ХПСОММОХСОХ1КО1БЕХ» 381 ЕОСРАЕЕТТЕ» 171 ХМНВК» 392 ОРЕХЕ1БЕХАМЕ» 377 РАБЕТТЕЕХТКУ» 161 КСВ2ПАВ» 180 ТВВИТТОХ» 389 ТООБ1ХЕО» 407 ТООБТ1РТЕХТ» 392 структуры СОРУВАТА8ТКИСТ 474 БОСВКП8Н 100 БОСЕОХТ 125 М8С 40 РА1ХТ8ТКИСТ 61 РО1ХТ 40 РКОСЕ881ХЕОКМАТЮХ 448 КЕСТ 44 8СКОББ1ХЕО 143 8ТАКТИР1ХЕО 447 8У8ТЕМТ1МЕ 487 ТЕХТМЕТК1С 121 УУХВСБА88ЕХ 32 ХЕОКМ 72 счетчик 435 счетчик меток реального времени 489 типы данных СОБОККЕЕ 77 НВКП8Н 97 НЕОХТ 124 НСВЮВ} 87 Н1СОХ 253 НРЕХ 87 типы данных УУтйохуз 20 Ф файл подкачки 471 Физическая система координат 67 Фокус ввода 214 фокус ввода 18 «функции АрренйМени» 281 ВЙВ11» 197
«функции (продолжение) СкескВ1§Ви11он» 332 СкескКаскоВиИон» 333 Сгеа1еВ11шар» 193 Сгеа1еВ11тар1псНгес1» 193 Сгеа1еСаге1» 219 Сгеа1еСотра11ЫеВ11тар» 194 Сгеа1еСотраНЫеВС» 197 Сгеа1еВ1а1о§» 366 Сгеа1еВ1ВИтар» 194 Сгеа1еВ1В8есНон» 201 Сгеа1еЕнкМе1аЕПе» 207 Сгеа1еНа111опеРа1е11е» 168 Сгеа1е81а1н8АУтс1о\у» 413 Сгеа1еТоо1ВагЕх» 390 Ве1е1еМепн» 281 Г)е81гоуСаге1» 220 В1а1о§Вох» 319 ВгахуМепиВаг» 283 Еп(Ш1а1о§» 319 Се1В1а1о§Ва8еПш18» 308 Се1В1§11ет» 306 Се1В1§11ет1п1» 344 Се1В1§11етТех1» 343 Се1ЕпкМе1аЕИеНеас1ег» 211 Се1Мепи» 284 СеЮф’ес!» 196 Се18иЬМепи» 284 ЕПбеСаге!» 220 1ш1СоттопСоп1го18Ех» 380 1п8ег1Мепи» 281 18В1а1о§Ме88а§е» 367 Еоас1Ассе1ега1ог8» 301 ЕоабВИтар» 195 ЕоасИта^е» 195 ЕоасШЬгагу» 382 ЕоабМепи» 281 Ме88а§еВох» 372 Ои1ри1ВеЬи§81гт§» 413 Р1ауЕнкМе1аЕПе» 210 КеакхеРакИе» 168 КетоуеМени» 281 8е1ес1Ра1е11е» 168 8епс1В1§11етМе88а§е» 342 «функции (продолжение) 8е1В1В118ТоВеу1се» 183 8е1В1§11етТех1» 344 8е1Мени» 281 8е1МепиВе1аи1111ет» 276 8е1Мепи11ет1н1о» 281 8е181ге1скВ11Мос1е» 182 8е1АУтс1о\уТех1» 321 8ко\уСаге1» 220 8ко\уАУтс1о\у» 312 81ге1скВ11» 198 81ге1скВ1В118» 181 ТгаскРорирМепиЕх» 294 Тгап81а1еАссе1ега1ог» 301 Тгап81а1еМе88а§е» 218 функции _Ьефп111геас1ех 456 Ап§1еАгс 85 Агс 84 АгсТо 85 ВефнРат! 43, 61 СкескМениИет 282 СкескМениКаскоИет 283 Скогб 106 С1о8еНансИе 442 СотЬтеК^н 111 Сгеа1еВги8к1пскгес1 100 Сгеа1еСотраНЫеВС 65 Сгеа1еЕШр11сК§н 109 Сгеа1еЕШр11сК§н1пскгес1 109 Сгеа1еЕуеп1 464 Сгеа1еЕПеМаррт§ 472 Сгеа1еЕоп1 125 Сгеа1еЕоп11пскгес1 128 Сгеа1еНа1скВги8к 98 Сгеа1е1С 66 Сгеа1еМи1ех 467 Сгеа1еРа11егпВги8к 99 Сгеа1еРеп 90 Сгеа1еРеп1пскгес1 91 Сгеа1еРо1у§опК§п 110 Сгеа1еРо1уРо1у§опК§п 110 Сгеа1еРгосе88 4 47 Сгеа1еКес1К§п 109 Сгеа1еКес1К§п1пскгес1 109 Сгеа1еКоипс1Кес1К§п 110 Сгеа1е8етаркоге 466 функции (продолжение) Сгеа1е$о11(1Вги8к 97 Сгеа1еТкгеас1 451 Сгеа1еАУтс1о\у 35 Сгеа1еАУтс1о\уЕх 38 Ве1\Утс1о\уРгос 42 Ве1е1еСг111са18ес11оп 461 Ве1е1еОЬ]ес1 82 Ве81гоу\Утс1о\у 46 В18ра1скМе88а§е 41 Вга\уЕоси8Кес1 105 БгахуТех! 44, 136 ВгахуТех1Ех 136, 139 ЕШрзе 105 ЕнаЫеМепиНеш 283 ЕнбРат! 62 Еп1егСг111са18ес11оп 460 Ех1Сгеа1еРеп 93 Ех1Тех1Ои1 140 ЕШКес! 104 ЕтбКезоигсе 264 ЕтсАУтбоху 474 ЕгатеКес! 104 Се1Еоса1Т1те 487 Се1АгсВ1гес1юн 85 Се1ВкМо6е 81 Се1С1а88Еон§ 56 Се1Скен1Кес1 44 СеЮС 62 Се1Веу1сеСар8 74 Се1В1§С1г1ГО 306 Се1Ме88а§е 41 Се1Мос1и1еНапс11е 56, 255 Се1Р1хе1 78 Се181оскОф’ес1 82, 88, 124 Се18у81етТ1те 487 Се18у 81етТ 1те Аф и81тен1 487 Се1Тех1Скагас1егЕх1га 133 Се1Тех1Ех1ен1Рот132 134 Се1Тех1Еасе 129 Се1Тех1Ме1пс8 121 СеЕПскСоин! 487 СеШхаЗохуВС 62 Се1АУ1нс1о\уКес1 68 1нДа1еКес1 102 1ш11а112еСг111са18ес1юп 460 1п1ег1оске(Шесгешеп1 459
функции (продолжение) ТШегкк'кесПпсгешеШ 459 ТнуаНбакеКеск 64, 260 ТнуаНбакеК^н 64 1пуег1;Кес1; 105 КШТппег 504 ЕеауеСпНса18есНоп 461 ЫнеТо 83 ЬоасИсон 253 ЬоасИша^е 253 ЬоабКезоигсе 264 ЬоабЗШн^ 270 ЬоскКезоигсе 264 МарУ1е\уО1РИе 472 Мезза^еВох 26 МосШуМепи 283 МосШуАУогНТгапзкогт 74 МоуеТоЕх 83 ОИзеШес! 102 ОИзеШ^н 111 ОрепЕхеШ 465 ОренЗешаркоге 466 РеекМезза^е 57 Р1е 106 Р1ау8онпс1 265 Ро1уВе21ег 87 Ро1уВе21егТо 87 Ро1у§оп 107 Ро1у1те 84 Ро1у1теТо 84 Ро1уРо1у§оп 108 Ро1уРо1у1те 84 РозкМезза^е 54 Роз1()ш1Ме88а§е 46 РНнКефон 110 ()иегуРег1огтапсеСоипкег 489 функции (продолжение) ()иегуРег1огтапсеЕгеци- епсу 488 Кес1ап§1е 103 КефзкегСИзз 35 КефзкегСИззЕх 32 Ке1еазеИС 62 Ке1еазе8етар1юге 466 КезкогеИС 73 КоипбКес! 106 ЗауеИС 73 8сго11АУтс1о\у 145 8е1ес1СНрК§п ИЗ 8е1ес1Оф’ес1 82 8еп(Ш1§ЕетМе88а§е 346 ЗепбМезза^е 54 ЗепФЫоН^уМезза^е 54 8е1АгсВ1гес1юп 85 8е1ВкСо1ог 45 8е1ВкМо6е 45, 81 ЗекВгизЕОг^Ех 98 ЗекСкззЬоп^ 51 8е1Сигзог 258 8еЮСРепСо1ог 89 ЗеЮгарЫсзМобе 73 ЗекМарМобе 71 ЗекР1хе1 78 ЗекР1хе1У 78 ЗекРо1уЕШМобе 108 ЗекРгюпгуСПазз 4 46 ЗекКеск 102 ЗекКОР2 80 ЗекТехкАН^п 130 8е1Т ехкСЕагаскег Ехкга 133 ЗекТехкСо1ог 45 ЗекТехУизкШсакюп 134 функции (продолжение) ЗекТЕгеабРгюпку 446 ЗекПшег 504 ЗекУ1е\урогкЕхкЕх 71 ЗекУ1е\урогкОг§Ех 70 ЗекАУтбохуЕхкЕх 70 ЗекАУтбо\уОг§Ех 70 8е1АУтс1о\уК§п 112 ЗекАУогМТгапзкогт 67, 73 ЗЕеПЕхесике 268 ЗЬохуАУтсклу 39 81еер 452, 498 ТаЬЬебТехкОик 131 ТехкОик 130 НшеВефпРепоб 509 НшеЕпбРепоб 509 НшеСеЮеуСарз 509 НшеКШЕуеН 511 111пе8е1Е\е1П 510 ТгапзЫеМезза^е 41 ТгуЕ111егСг111с<118ес11()11 461 ПпшарУ1е\уО1ЕПе 472 Пр6а1е\Утс1о\у 63 АУ аНЕог МиШр1еОЬ] ес!з 463 АУа11Еог8т§1еОЬ]ес1 462 обратного вызова (САЬЬВАСК) 22 поддержки окон 49 функция В18ра1с11Ме88а§е 24 функция АУтМат 25 Ш шаблонные единицы 308 Шрифт 59 Штриховая кисть 98