Перейти на главную   
  helloworld.ru - документация и книги по программированию  
helloworld.ru - документация и книги по программированию    
    главная     хостинг     создание сайтов    
Поиск по сайту:  
Смотрите также
Языки программирования
C#
MS Visual C++
Borland C++
C++ Builder
Visual Basic
Quick Basic
Turbo Pascal
Delphi
JavaScript
Java
PHP
Perl
Assembler
AutoLisp
Fortran
Python
1C

Интернет-технологии
HTML
VRML
HTTP
CGI
FTP
Proxy
DNS
протоколы TCP/IP
Apache

Web-дизайн
HTML
Дизайн
VRML
PhotoShop
Cookie
CGI
SSI
CSS
ASP
PHP
Perl

Программирование игр
DirectDraw
DirectSound
Direct3D
OpenGL
3D-графика
Графика под DOS

Алгоритмы
Численные методы
Обработка данных

Системное программирование
Драйверы

Базы данных
MySQL
SQL

Другое

Хостинг


Друзья
demaker.ru
Реклама

Лучший хостинг. Аренда серверов




helloworld.ru
         ОГЛАВЛЕНИЕ

ВЕДЕНИЕ
Комплект поставки ObjectWindows
  Что представляет собой руководства по ObjectWindows
Требования по программному и аппаратному обеспечению
Установка ObjectWindows
  Использование INSTALL
Файл README для ObjectWindows
Файлы помощи ObjectWindows
  Помощь в Windows
  Помощь в ДОС
Соглашения, используемые в данных книгах
  Соглашения по именам в ObjectWindows
Как связаться с фирмой Borland
  Возможности поставляемого пакета
  Возможности фирмы Borland

ЧАСТЬ I. ЗНАКОМСТВО С ObjectWindows

Глава 1. Наследовать окно
Что такое прикладная программа для Windows
  Достоинства Windows
  Требования
Объектно-ориентированное программирование в Windows
  Лучший интерфейс с Windows
     Формирование оконной информации
     Абстрагирование функций Windows
     Автоматический ответ на сообщения
Структура Windows программы
  Структура Windows
  Взаимодействие с Windows и с ДОС
"Hello, Windows" ("Здравствуй Windows")
     Характеристики запуска прикладной программы
     Характеристики главного окна
  Процесс создания прикладной программы

Глава 2. Шагаем по Windows
Создание прикладной программы ObjectWindows: предварительные рассуждения
  Библиотека контейнерных классов
  Каталоги
  Указание необходимой библиотеки
     Программы ObjectWindows, использующие DLL
     Создания файла ресурсов
Создание прикладной программы ObjectWindows: специфика
  Использование ИСР для создания приложения ObjectWindows
  Использование утилит командной строки для создания прикладных программ
  ObjectWindows
Шаг 1: Простая Windows программа
  Требования прикладной программы
     Определение класса прикладная программа
Шаг 2: Класс главного окна
  Что такое оконный объект ?
     Дескрипторы
     Родительские и дочерние окна
  Создание объекта главное окно.
  Ответы на сообщения
  Завершение работы прикладной программы

Глава 3. Вывод информации в окно
Что такое контекст вывода ?
Шаг 3 : Вывод в окно текстовой информации
  Структура сообщения
  Очистка экрана
Шаг 4 : Рисование линий в окне
  Модель перемещения
  Реакция на сообщения перемещения
Шаг 5 : Изменение толщины линий
  Выбор нового карандаша
     Замена карандаша
  Реализация диалога ввода
Шаг 6 : Вывод графической информации
  Модель рисования
  Хранение графики как объекта
  Перерисовка сохраненной графической информации

Глава 4. Дабавление меню
Ресурсы меню
Шаг 7 : Меню для главного окна
Перехват сообщений меню
Ответ на сообщение меню

Глава 5. Поддержка диалога
Шаг 8 : Добавление всплывающего окна
  Создание всплывающего окна
  Функция MakeWindow
Добавление блока диалога
  Добавление компоненты данных
  Инициализация блока диалога
Шаг 9 : Сохранение графической информации в файле
  Отслеживание статуса
  Открытие и сохранение файлов

Глава 6. Всплывающие окна
Шаг 10 : Вывод всплывающего окна помощи
Использование модулей с ObjectWindows
  Модификация главной программы
  Создание модуля
Добавление управляющих элементов к окну
  Что такое управляющие элементы
  Создание управляющих элементов окна
  Упраляющие объекты как компоненты данных
  Организация работы управляющих элементов
  Реакция на события управляющих элементов

ЧАСТЬ 2. ИСПОЛЬЗОВАНИЕ ObjectWindows

Глава 7. Обзор ObjectWindows
Соглашения ObjectWindows
Иерархия ObjectWindows
  Object
  TModule
  TApplication
  Интерфейсные объекты
     TWindowsObject
  Оконные объекты
     TWindow
     TEditWindow
     TFileWindow
  Диалоговые объекты
     TDialog
     TFileDialog
     TInputDialog
  Управляющие объекты
     TControl
     TButton
     TCheckBox
     TRadioButton
     TListBox
     TComboBox
     TGroupBox
     TStatic
     TEdit
     TScrollBar
  Объекты MDI
     TMDIFrame
     TMDIClient
  Прокручивающиеся объекты
Функции Windows API.
  ObjectWindows вызывает функции Windows
  Доступ к функциям Windows
  Комбинирование стилевых констант
  Типы функций Windows
     Интерфейсные функции управления окнами
     Функции интерфейса с графическими устройствами (GDI)
     Функции интерфейса с системными средствами
  Функции с обратным вызовом
     Функции подсчета
        Использование интеллектуальных обратных вызовов
Сообщения Windows
  Параметры сообщений Windows
  Типы сообщений Windows
     Сообщения управления окнами
     Инициализационные сообщения
     Сообщения ввода
     Системные сообщения
     Сообщения буфера информационного обмена (Clipboard)
     Сообщения о системной информации
     Сообщения манипулирования управляющими элементами
     Сообщения уведомления управляющих элементов
     Уведомляющие сообщения линеек прокрутки
     Сообщения не из области пользователя
     Сообщения мультидокументального интерфейса
  Обработка сообщений по умолчанию
  Посылка сообщений
  Диапазоны сообщений
  Сообщения, определяемые пользователем

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

Глава 9. Интерфейсные объекты
TWindowsObject
Почему интерфейсные объекты ?
Отношение родители-дети между окнами
  Списки дочерних окон
  Итерация по дочерним окнам
Обработка сообщений
  Ответы на сообщения
  Командные сообщения и сообщения дочерних окон
     Обработка командных сообщений
     Обработка сообщений дочерних окон
  Обработка сообщений по умолчанию

Глава 10. Оконные объекты
Класс ТWindows
Инициализация и создания оконных объектов
  Инициализация оконных объектов
  Создание оконных элементов
  Обобщение информации о создании и инициализации
Регистрация оконного класса
  Атрибуты регистрации
     Компонента стиля класса
     Компонента иконки
     Компонента курсора
     Компонента цвета фона
     Компонента меню по умолчанию
Прокручиваемые окна
  Атрибуты прокрутки
  Организации прокрутки в окне
  Пример с прокруткой
  Авто-прокрутка и отслеживание местоположения
  Модификация единиц прокрутки и диапазона
  Модификация положения прокрутки
  Установка размера страницы
  Оптимизация функций-компонент Paint при организации прокрутки
Окна редактирования и окна работы с файлами
  Окна редактирования
  Окна работы с файлами

Глава 11. Диалоговые объекты
Использование ресурсов диалога
Использование дочернего диалогового объекта
  Конструирование и инициализация дочерних диалоговых объектов
  Создание и выполнение диалогов
     Модальные и немодальные диалоги
  Закрытие дочернего диалога
  Использование диалога как главного окна
     Определение класса Windows для вашего немодального диалога
  Работа с управляющими элементами и обработка сообщений
     Работа с управляющими элементами диалога
     Ответы на сообщения уведомления управляющих элементов
     Пример связи диалог/управляющий элемент
  Усложненный пример использования диалогов
Диалоги ввода
Файловые диалоги

Глава 12 Объекты управления
Использование объектов управления
  Конструирование и создание объектов управления
  Уничтожение и удаление управляющих элементов
  Управляющие элементы и обработка сообщений
     Манипулирование управляющими элементами окон
     Реакция на предупреждающие сообщения управляющих элементов
Фокус управления и клавиатура
Управляющие элементы блоков списков
  Конструирование и создание блоков списков
  Модификация блоков списков
  Запросы к блокам списков
  Получение выборов из блока списка
Статичные управляющие элементы
  Конструирование статичных управляющих элементов
  Запросы к статичным управляющим элементам
  Модификация статичных управляющих элементов
  Пример: Приложение StatTest
Управляющие элементы кнопок нажатия
  Реакция на сообщения кнопок
Блоки проверки и селективные кнопки
  Конструирование блоков проверки и селективных кнопок
  Запрос состояния селективного блока
  Модификация состояния селективного блока
  Реакция на сообщения блоков проверки и селективных кнопок
Блоки группы
  Конструирование блока группы
  Реакция на сообщения блока группы
  Пример программы: BtnTest
Линейки прокрутки
  Конструирование объектов линеек прокрутки
  Запросы линейкам прокрутки
  Модификация линеек прокрутки
  Реакция на события линейки прокрутки
  Пример: SBarTest
Редактируемые управляющие элементы
  Конструирование редактируемых управляющих элементов
  Буфер информационного обмена и операции редактирования
  Запросы к редактируемым управляющим элементам
  Модификация редактируемых управляющих элементов
     Удаление текста
     Вставка текста
     Принудительный выбор и прокрутка текста
  Пример программы: EditTest
Комбинированные блоки
  Три разновидности комбинированных блоков
     Простые комбинированные блоки
     Выпадающие комбинированные блоки
     Выпадающие списковые комбинированные блоки
     Выбор типов комбинированных блоков
  Конструирование комбинированных блоков
  Модификация комбинированных блоков
  Пример приложения: CBoxTest
Передача данных управляющих элементов
  Определение буфера передачи
  Конструирование управляющих элементов и разрешение передачи
  Передача данных
  Модификация передачи для управляющих элементов
  Примеры передачи

Глава 13. Адаптированные объекты управления
Модификация предопределенных управляющих элементов
  Модификация стилей создания
     Как сделать предопределенный управляющий элемент рисуемым
  Модификация предопределенных реакций на сообщения
     Задание дополнительной обработки для предопределенного управляющего
     элемента
     Обход реакции предопределенного управляющего элемента
Использование адаптированного управляющего элемента
  Разработка адаптированного управляющего элемента
     Описание адаптированного управляющего элемента

Глава 14. Объекты MDI
Компоненты приложений MDI
  Каждое окно MDI является объектом
Конструирование окон MDI
  Конструирование окон рамки MDI
  Конструирование дочерних окон MDI
Обработка сообщений в приложениях MDI
Управление дочерними окнами MDI
  Активизация дочерних окон
  Меню дочерних окон
Пример приложения MDI

Глава 15. Объекты, которые возможно использовать в потоках
Библиотека iostream
Перегруженные операторы << и >>
Классы, имеющие возможность участвовать в потоках, и TStreamable
Менеджер потоков
Конструкторы классов, имеющих возможность участвовать в потоках
Имена классов, имеющих возможность участвовать в потоках
Использование менеджера потоков
  Установление связей в программе менеджера потоков
  Создание потокового объекта
  Использование потокового объекта
Совокупности в потоках
  Придание массивам возможности участвовать в потоках
  Построительная функция класса, имеющего возможность участвовать в
  потоках
  Функция-компонента streamableName
  Функция-читатель, имеющая возможность работы с потоками

ЧАСТЬ 3. ПРОГРАММИРОВАНИЕ ДЛЯ WINDOWS

Глава 16. Управление памятью
Использование менеджера памяти
Как Windows управляет памятью
  Дескрипторы и адреса
     Особенности реального режима
     Особенности защищенного режима
Локальная и глобальная память
Использование локальной области динамически распределяемой памяти
  Размещение и доступ к локальной памяти
  Освобождение и отбрасывание блоков локальной памяти
  Переразмещение и модификация блоков локальной памяти
  Запросы к блокам локальной памяти
  Замечания по программированию
Использование глобальной области динамически распределяемой памяти
  Размещение и доступ к глобальной памяти
  Другие способы блокировки блоков глобальной памяти
  Освобождение и отбрасывание блоков глобальной памяти
  Переразмещение и модификация блоков глобальной памяти
  Запросы к блокам глобальной памяти
  Изменение глобального отбрасывания
  Получение предупреждений о недостаточной памяти
  Замечания по программированию

Глава 17. Взаимодействие процессов
Буфер информационного обмена Windows
  Форматы Буфера информационного обмена
  Помещение данных в Буфер информационного обмена
  Получение данных из Буфера информационного обмена
  Задержка преобразования
Обмен сообщениями между процессами
Динамический обмен данными
  Термины
  Установка диалога
  Прекращение диалога
  Функции-компоненты обмена данными
  Запрос одного элемента данных
  Событийное преобразование данных
  Запрос к серверу на изменение значения данных
  Выполнение команд макросов в сервере
  Системная тема

Глава 18. Введение в GDI
Контекст представления
  Управление контекстом представления
  Что находится в контексте представления?
     Побитовая графика
     Цвет
     Режимы отображения
     Области усечения
     Средства рисования
Средства рисования
  Имеющиеся средства
  Логические средства
     Логические карандаши
     Логические кисти
     Логические шрифты
Представление графики в окнах
  Рисование в окнах
     Управление контекстом представления
     Вызов оконных графических функций
  Отрисовка окон
  Стратегии графики
  Использование средств рисования
Функции рисования GDI
  Рисование текста
  Рисование линий
     Move To и LineTo
     Polyline
     Arc
  Рисование форм
     Rectangle
     RoundRect
     Ellipse
     Pie и Chord
     Polygon
Использование палитр
  Установка палитры
  Рисование с помощью палитр
  Запросы к палитрам
  Модификация палитры
  Реакция на изменение палитры

Глава 19. Ресурсы в подробностях
Создание ресурсов
Добавление ресурсов к выполняемому файлу
Использование Компилятора ресурсов
  Файлы описания Компилятора ресурсов
  Советы по работе с Компилятором ресурсов
Загрузка ресурсов в приложении
  Загрузка меню
  Загрузка таблиц акселераторов
  Загрузка блоков диалога
  Загрузка курсоров и пиктограмм
  Загрузка ресурсов строк
  Загрузка побитовых отображений
  Использование побитового отображения для создания кисти
  Представление побитовых отображений в меню

Глава 20. Стандартные руководящие принципы разработки приложений
Принципы разработки
  Предоставление реакции, ожидаемой пользователем
  Возможность пользователя управлять ходом событий
Стандартные внешний вид и режим работы
  Общее представление
  Взаимодействие с помощью мыши и клавиатуры
  Меню
  Блоки диалога
  Другие соглашения разработки
Написание надежных программ
  Элементарные операции
     Пул безопасности
     Другие ошибки при создании окон
  Проверка потребителей памяти

         ТАБЛИЦЫ

Таблица 2.1 Каталоги INCLUDE по умолчанию
Таблица 2.2 Библиотеки для каждой модели памяти
Таблица 2.3 DLL и библиотеки импорта
Таблица 2.4 Классы ObjectWindows, которые требуют файлов ресурсов
Таблица 3.1 Наиболее часто получаемые сообщения "мыши".
Таблица 3.2 Сообщения, используемые в Шаге 4.
Таблица 7.1 Диапазоны сообщений Windows
Таблица 10.1 Компоненты данных TWindowAttr.
Таблица 10.2 Атрибуты регистрации окна
Таблица 10.3 Типичные установки компонент данных окна редактирования
Таблица 10.4 Функции-компоненты окна работы с файлами и идентификаторы
             меню
Таблица 12.1 Управляющие элементы Windows, поддерживаемые ObjectWindows
Таблица 12.2 Предупреждающие сообщения блоков списков
Таблица 12.3 Общие стили редактируемых управляющих элементов
Таблица 12.4 Идентификаторы меню и вызываемые ими функции-компоненты
Таблица 12.5 Функции-компоненты, определяемые TScrollBar
Таблица 12.6 Компоненты данных TListBoxData
Таблица 12.7 Компоненты данных TComboBoxData
Таблица 14.1 Стандартные действия, команды и функции-компоненты MDI
Таблица 17.1 Форматы буфера информационного обмена
Таблица 18.1 Имеющиеся средства рисования
Таблица 18.2 Простые RGB-значения цветов
Таблица 18.3 Константы шага и семейства шрифта
Таблица 19.1 Параметры командной строки Компилятора ресурсов
Таблица 20.1 Значения ошибок создания окон

         РИСУНКИ

Рисунок 1.1 Экранные компоненты приложения Windows
Рисунок 1.2 Как приложение Windows взаимодействует с Windows и ДОС
Рисунок 2.1 Полное приложение ObjectWindows.
Рисунок 2.2 Реализация первого Шага вашего приложения ObjectWindows
Рисунок 2.3 Наша прикладная программа отвечает на событие
Рисунок 2.4 Шаг приложения с переопределенной реакцией на завершение
            работы
Рисунок 3.1 Вывод координат точки нахождения указателя "мыши" в момент
            нажатия левой клавиши.
Рисунок 3.2 Изменение толщины линии.
Рисунок 4.1 Прикладная программа с ресурсом меню.
Рисунок 4.2 Прикладная программа с системой помощи
Рисунок 5.1 Группа связанных родительских и дочерних окон
Рисунок 5.2 Прикладная программа с блоком файлового диалога.
Рисунок 6.1 Система помощи созданной нами прикладной программы
Рисунок 7.1 Иерархия классов ObjectWindows
Рисунок 8.1 Функция-компонента осуществляет вызовы, организуя поток
            прикладной программы
Рисунок 8.2 Главное окно
Рисунок 8.3 Последовательная инициализация экземпляров приложения
Рисунок 10.1 Атрибуты окна
Рисунок 10.2 Программа, использующая класс IBeamWindow.
Рисунок 10.3 Окно редактирования
Рисунок 11.1 Прикладная программа с диалогом
Рисунок 11.2 Диалог, проверяющий допустимость ввода пользователя
Рисунок 11.3 Диалог ввода
Рисунок 11.4 Файловый диалог
Рисунок 12.1 Некоторые простые управляющие элементы
Рисунок 12.2 Заполненный блок списка
Рисунок 12.3 Реакция на выбор пользователем элемента блока списка
Рисунок 12.4 Три типа комбинированных блоков и блок списка
Рисунок 12.5 Выпадающий списковый комбинированный блок
Рисунок 12.6 Статичные управляющие элементы
Рисунок 12.7 Окно с редактируемыми управляющими элементами
Рисунок 12.8 Редактируемый управляющий элемент создается без рамки
Рисунок 12.9 Окно с различными кнопками
Рисунок 12.10 Управляющий элемент линейки прокрутки
Рисунок 12.11 Окно с различными линейками прокрутки
Рисунок 14.1 Компоненты примера приложения MDI
Рисунок 15.1 Иерархия классов, имеющих возможность участвовать в потоках и
             использующихся в ObjectWindows
Рисунок 16.1 Пример уплотнения
Рисунок 18.1 Стили линий для средств карандаша
Рисунок 18.2 Стили штриховки для средств кисти
Рисунок 18.3 Результат работы функции TextOut
Рисунок 18.4 Результат работы функции LineTo
Рисунок 18.5 Результат работы функции Polyline
Рисунок 18.6 Результат работы функции Arc
Рисунок 18.7 Результат работы функции Rectangle
Рисунок 18.8 Результат работы функции RoundRect
Рисунок 18.9 Результат работы функции Ellipse
Рисунок 18.10 Результат работы функции Pie
Рисунок 18.11 Результат работы функции Chord
Рисунок 18.12 Результат работы функции Polygon
Рисунок 19.1 Шаблон с полосками, заполняющий область дисплея
Рисунок 19.2 Ресурс побитового отображения для создания кисти для шаблона,
             показанного на Рис. 19.1
Рисунок 19.3 Меню, использующее побитовое отображение в качестве одного из
             своих пунктов
Рисунок 20.1 Стандартная линейка меню CUA
Рисунок 20.2 Пункты меню Файл, рекомендованные CUA
Рисунок 20.3 Пункты меню Редактирование, рекомендованные CUA
Рисунок 20.4 Пункты меню Просмотр, рекомендованные CUA
Рисунок 20.5 Пункты меню Опции, рекомендованные CUA
Рисунок 20.6 Пункты меню Подсказка, рекомендованные CUA




    ВЕДЕНИЕ

    ObjectWindows обеспечивает совершенно новый великолепный способ
создания прикладных программ для Microsoft Windows. До последнего времени
программирование для среды Windows требовало компилятора Microsoft C и
большого числа отдельных, и достаточно сложных, прикладных утилит. В
результате чего, создание программ под Windows являлось относительно
медленным, сложным и кропотливым делом. С появлением прикладного
программного продукта ObjectWindows программирование для Windows стало
намного более приятным занятием.
    Программирование в Windows требует от вас знания многих новых
тонкостей, о которых, возможно, вы и не должны были думать до этого.
Например, манипулирование с текстом и графикой в изменяющихся по размеру
окнах, взаимодействие с другими программами в многозадачной среде и
манипулирование более чем 600 функциями в Интерфейсе Программирования
Приложения Windows (Windows Application Programming Interface API).
Возможно, что большая часть всего этого может только прояснить, какие
основные вещи ваша программа должна делать для того, чтобы функционировать
как прикладная программа Windows и затем убедиться, что вы реализовали все
из них.
    ObjectWindows позволяет все это выполнить. Это объектно ориентированная
библиотека классов, которая формирует механизм (на уровне прикладных
программ и на уровне окон), реализуемый обычными прикладными программами
Windows. ObjectWindows упрощает создание программ под Windows из-за
    - согласующегося, интуитивного и упрощенного интерфейса с Windows;
    - обеспечения механизма управления окнами и обработки сообщений;
    - базового набора оконных элементов для структурирования прикладной
программы под Windows.
    Вы автоматически получаете эти базовые функциональные возможности,
которые позволяют вам сосредоточить основные усилия на специфических
требованиях вашей прикладной программы.
    ObjectWindows не предоставляет классы для исчерпывающего формирования
всех логических элементов прикладной программы для Windows. Но, посредством
модификации имеющихся в ObjectWindows классов, вы существенно сократите
время создания вашей прикладной программы и обеспечите получение гораздо
более компактного кода.

         Комплект поставки ObjectWindows

    Комплект поставки ObjectWindows состоит из набора дискет и двух
руководств:
    - ObjectWindows для С++. Руководство программиста (Данное руководство).
    - ObjectWindows для С++. Справочное руководство.
    На дисках имеются все необходимые программы ObjectWindows для начала
написания приложений для Windows с использованием объектно-ориентированного
подхода ObjectWindows. На них содержаться и все примеры программ, на
которые имеются ссылки в книге ObjectWindows для С++ Руководство
программиста, а также другие полезные программы, которые вы можете изучить
и использовать в дальнейшем. На дисках содержаться два файла Помощи (Help):
    - OWLWHELP.HLP для системы помощи Windows Help.
    - OWLTHELP.OWH для ДОСовского THELP.

         Что представляет собой руководства по ObjectWindows

    Так как ObjectWindows использует некоторые новые технические методы, то
данное Руководство программиста включает в себя большое количество
пояснительного материала. Оно состоит из трех частей:
    - Часть 1, Знакомство с ObjectWindows, описывает принципы написания
прикладных программ для Windows. Она представляет собой учебник,
прослеживающий весь процесс написания и расширения возможностей приложения
ObjectWindows.
    - Часть 2, Использование ObjectWindows, посвящена в основном
детальному описанию элементов самого ObjectWindows. Она включает в себя
обзор иерархии классов и объясняет как взаимодействовать со средой Windows.
    - Часть 3, Программирование в Windows, дает описание аспектов
программирования в Windows, которые ObjectWindows не поддерживает, такие
как, графика, связь между процессами, использование ресурсов, управление
памятью, разделение кодов и данных и управляющие строки в Windows.
    В "ObjectWindows для С++ Справочное руководство" вы найдете справочные
материалы по классам ObjectWindows, типам ObjectWindows, константам
ObjectWindows, функциям Windows, типам Windowsи константам Windows. Все
материалы приводятся в алфавитном порядке и для удобства пользования имеют
закладки.

         Требования по программному и аппаратному обеспечению

    Так как ObjectWindows является графическим инструментальным средством
для создания прикладных программ под Windows, то основные требования по
аппаратному обеспечению для прикладных программ ObjectWindows совпадают с
аналогичными требованиями для Windows:
    - жесткий диск;
    - 2 Мб и более оперативной памяти;
    - совместимый с Windows графический дисплей;
    - Windows 3.0 и выше в стандартном или расширенном режиме процессора
386.
    Вы можете компилировать приложения ObjectWindows (и исходные коды
самого ObjectWindows) с помощью любого компилятора Borland C++, который
создает программы для Windows.

         Установка ObjectWindows

    ObjectWindows поставляется с программой автоматической установки
INSTALL. Так как для хранения файлов на дистрибутивных дискетах мы
используем технологии их сжатия, то вы обязательно должны использовать эту
программу; вы не можете просто скопировать файлы ObjectWindows с дискет на
ваш жесткий диск. Программа INSTALL автоматически копирует и производит
декомпрессию файлов ObjectWindows. На установочной дискете в файле
FILELIST.DOC для справки приводится списки всех дистрибутивных файлов.
    До начала инсталляции сделайте полные рабочие копии всех ваших
дистрибутивных дисков, и положите оригинальные диски в безопасное место.
    Найдите время для изучения соглашения, включаемого в пакет поставки
ObjectWindows, и вышлите нам заполненную регистрационную карточку; это
гарантирует вам получение информации об изменениях и новых версиях
ObjectWindows.

    Использование INSTALL

    Программа INSTALL создает каталоги для хранения файлов ObjectWindows и
перемещает эти файлы с дистрибутивных дисков на ваш жесткий диск. Все что
вам необходимо знать приводится ниже.
    Для установки ObjectWindows:
    1. Установите инсталляционный диск (диск 1) в дисковод А. Наберите
следующую команду и нажмите Ввод (Enter):
     A:INSTALL
    2. Нажмите Ввод (Enter) при появлении установочного экрана.
    3. Следуйте выдаваемым приглашениям.

         Файл README для ObjectWindows

    Файл README содержит последнюю информацию, которая может быть и не
включена в прилагаемые руководства.
    Для получения доступа к файлу README сделайте следующее:
    1. Установите инсталляционный диск ObjectWindows в дисковод А. Если вы
уже установили ObjectWindows, то перейдите к пункту 3.
    2. Наберите А: и нажмите Ввод (Enter).
    3. Наберите README и нажмите Ввод (Enter). Вы окажитесь в программе
просмотра файла, используйте ключи управления курсором Стрелка Вверх и
Стрелка Вниз для перемещения по файлу.
    4. Нажмите Esc для выхода.

         Файлы помощи ObjectWindows

    Мы поставляет два файла помощи для ObjectWindows: OWLWHELP.HLP и
OWLTHELP.OWH. OWLWHELP.HLP обеспечивает помощь в Windows, а OWLTHELP.OWH
предоставляет помощь в среде ДОС. Информация в этих файлах является
полностью идентичной, за исключением формата хранения.

    Помощь в Windows

    OWLWHELP.HLP является файлом помощи для прикладной программы помощи в
Windows WINHELP.EXE. Может оказаться полезным создать иконку в Program
Manager со следующими описателями:
    - Description: ObjectWindows Help
    - Command line: WINHELP.EXE C:\BORLANDC\OWL\OWLWHELP.HLP
    Двойное нажатие кнопки "мыши" на этой иконке автоматически загружает
файл OWLWHELP.HLP под управлением WINHELP.EXE. Если вы установили
ObjectWindows не в каталоги по умолчанию, то вам необходимо изменить имя
каталога, в котором находится файл OWLWHELP.HLP.

    Помощь в ДОС

    Программа THELP представляет собой резидентную утилиту, обеспечивающую
доступ к помощи по ObjectWindows во время выполнения прикладных программ в
ДОС. Например, когда вы пишите свою прикладную программу ObjectWindows в
интегрированной среде Borland C++ вы можете загрузить THELP и в любой
момент работы с прикладной программой вам будет доступна помощь по
ObjectWindows.
    Загрузка THELP осуществляется из командной строки ДОС путем набора
команды:
    THELP /FC:\BORLANDCOWL\OWLHELP.OWH
    Эта команда загружает THELP и задает файл OWLTHELP.OWH, как файл для
использования в Turbo Help. Для вызова THELP после ее загрузки, нажмите
клавишу 5 на блоке цифровой клавиатуры. THELP активизируется и пытается
найти в индексе помощи слово, на которое (или около которого) установлен
курсор. Если она находит слово, то высвечивается экран помощи,
соответствующий этому топику; в противном случае, появляется индекс помощи
для выбора нужного вам топика.
    !!! Вы можете изменять "горячие" клавиши, задаваемые по умолчанию; смотри
файл помощи THELP.DOC.
    Ниже приводится краткая справки по возможностям THELP:
-------------------------------------------------------------------
 Клавиша   Действие
-------------------------------------------------------------------
 Tab       перемещение курсора к следующему ключевому слову
 Shift+Tab перемещение курсора к предшествующему ключевому слову
 Home      перейти к началу строки
 End       перейти к концу строки
 Enter     выбрать элемент Помощи для подсвеченного ключевого слова
           на текущем экране Помощи
 Esc       выйти из THELP
 F1        вывести Индекс Помощи
 Alt+F1    вывести в обратном порядке последние 20 экранов Помощи,
           которые вы уже просматривали
 Ctrl+F1   выдать экран помощи по "горячим" клавишам THELP
 Ctrl+P    поместить текст примера в прикладную программу
-------------------------------------------------------------------
    Вы можете изменять опции THELP путем задания различных параметров в
командной строке. Более подробно эта процедура описана в файле THELP.DOC.

         Соглашения, используемые в данных книгах

    Все шрифты и иконки, используемые в данном руководстве, были созданы с
помощью Borland's Sprint: The Professional Word Processor на лазерном
принтере PostScript.
    Тонкий шрифт - этот шрифт используется для представления текста,
который появляется на экран или в программе. Он также используется для
указания на то, что вы должны набрать на клавиатуре.
    ВСЕ ЗАГЛАВНЫЕ - все заглавные буквы используются для наименований
констант (например, WM_MOUSEMOVE).
    МАЛЕНЬКИЕ ЗАГЛАВНЫЕ - маленькие заглавные буквы используются для имен
файлов и каталогов (например, STEP1.CPP).
    [] - квадратные скобки в тексте и командных строках ДОС указывают на
необязательные элементы, которые зависят от вашей системы. Текст такого
типа не должен набираться дословно.
    Жирный (Bold) - зарезервированные слова С++, имена функций, классов и
структур появляются в тексте (но не в примерах программ) выделенные жирным
шрифтом. Этот шрифт используется и для опций утилит командной строки (такой
как -W).
    Италик (Italic) - шрифт италик указывает на идентиикаторы данных,
которые появляются в тексте. Идентификаторы данных включают в себя имена
переменных и компонент данных класса. Этот тип используется и для выделения
определенных слов, например новых терминов.
    Выделенный италик (Keycaps) - этот шрифт указывает на клавиши вашей
клавиатуры. Он обычно выделяет определенную клавишу или набор клавиш,
которые вы должны нажать; например, "Нажмите Shift+Ins для вставки данных
из буфера Clipboard".
    Иконка с указывающим пальцем - эта иконка используется для выделения
важной информации , на которую вы обязательно должны обратить внимание.
    Примечание переводчика !!!
    Вместо этой иконки мы будем использовать слово "Внимание !!!".
    Примечание переводчика !!!
    Мы будем выделять тремя восклицательными знаками (!!!) замечания,
вынесенные на поля книги.

    Соглашения по именам в ObjectWindows

    Многие классы и структуры в иерархии ObjectWindows имеют имена,
начинающиеся с буквы Т (например TWindow). Для всех всех классов с Т
ObjectWindows так же определяет следующие связанные типы:
    - Pclass, тип указатель на класс class. (Например, PTWindow
определяется как указатель на ТWindow).
    - Rclass, тип ссылки на класс class. (Например, RTWindow определяется
как ссылка на TWindow).
    - RРclass, ссылка на указатель на класс class. (Например, RРTWindow
определяется как ссылка на указатель на TWindow).
    - РСclass, тип указатель на константу const классa class. (Например,
PCTWindow определяется как указатель на const TWindow).
    - RClass, тип ссылки на константу const классa class. (Например,
RCTWindow определяется как ссылка на const TWindow).
    Мы включили эти типы для облегчения некоторых сложных процедур
программирования в Windows. Например, в противном случае динамически
связываемые библиотеки в Windows (DLL) требовали бы объявлений, подобных
    TWindow _FAR *           !!! Это эквивалентно РТWindow.
, где требовался бы указатель на ТWindow, или
    TWindow _FAR * _FAR &    !!! Это эквивалентно RРТWindow.
для ссылки на указатель на ТWindow.
    Использование типов P, R, RP, PC и RC облегчает преобразование вашей
прикладной программы, если позднее вы решите сделать ее частью DLL. Мы
широко используем эти типы в примерах программ в ObjectWindows.

         Как связаться с фирмой Borland

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

    Возможности поставляемого пакета

    Данный продукт содержит широкие возможности для помощи:
    - руководства предоставляют исчерпывающую информацию по всем аспектам
программ. Используйте их как главный источник информации;
    - вы можете использовать файлы интерактивной помощи.

    Возможности фирмы Borland

    Отдел технической поддержки фирмы Borland публикует информационные
беллютени по различным темам и доступен для ответа на любые ваши вопросы.
    !!! 800-822-4269 (звуковой) TechFax
    TechFax является круглосуточной автоматической справочной службой,
которая посылает техническую информацию на ваш факс бесплатно. Вы можете
запросить по телефону запрос на три документа за один звонок.
    !!! 408-439-9096 (модем) File Download BBS
    Электронная почта фирмы Borland File Download BBS имеет простые файлы,
прикладные программы и техническую информацию, к которой вы можете получить
доступ с помощью вашего модема. При этом не требуется никаких специальных
настроек.
    Абоненты информационных служб CompuServe, GEnie или BIX могут получать
техническую поддержку посредством модема. Для контактов с фирмой Borland с
получением доступа к информационному серверу используйте команды из
следующей таблицы.
-------------------------
 Сервис      Команда
-------------------------
 CompuServe  GO BORLAND
 BIX         JOIN BORLAND
 GEnie       BORLAND
-------------------------
    Адресуйте электронное сообщение к SYSOP или All. Не включайте ваш
серийный номер; сообщения не являются закрытыми, если не посылаются
электронной почтой. Включайте как можно больше информации по интересующему
вас вопросу; отдел технической поддержки ответит на ваше сообщение в
течении одного рабочего дня.
    !!! 408-438-5300 (звуковой) Техническая Поддержка (Technical Support)
    До звонка в Техническую Поддержку (Technical Support), просмотрите
внимательно ваш файл README; возможно это поможет разрешить проблему. В нем
содержится достаточно много информации по различным возникающим проблемам.
Если вы все же решили позвонить, то вы можете связаться с отделом
Технической Поддержки фирмы Borland с 6.00 до 17.00. Пожалуйста, звоните
недалеко от вашего компьютера. На руках вам следует иметь следующую
информацию:
    - имя продукта, серийный номер и номер версии;
    - типы и модели всех аппаратных частей вашей системы;
    - операционную систему и ее версию (используйте команду ДОС VER для
установления номера версии);
    - содержимое ваших файлов AUTOEXEC.BAT и CONFIG.SYS (расположенных в
корневом каталоге (\) вашего загрузочного диска);
    - содержимое ваших файлов WIN.INI и SYSTEM.INI (расположенных в
каталоге для Windows);
    - номер телефона, по которому с вами можно связаться в дневное время;
    - если звонок касается некоторой проблемы, постарайтесь воспроизвести
ее по шагам.

    ЧАСТЬ I. ЗНАКОМСТВО С ObjectWindows
    -----------------------------------
    Глава 1. Наследовать окно

    В этой части описывается программирования для Microsoft Windows с
упором на объектоно-ориентированное программирование. Вы узнаете что
является составляющими частями Windows программы и как она работает. Вы
изучите требуемые свойства прикладной программы для Windows и как
объектно-ориентированное программирование с помощью ObjectWindows
автоматизирует решение многих задач и облегчает выполнение других.

         Что такое прикладная программа для Windows

    На Рисунке 1.1 показаны основные компонента приложения Windows. Для
успешного понимания и использования ObjectWindows вы должны быть знакомы с
этими компонентами.

    Рисунок 1.1 Экранные компоненты приложения Windows

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

    Достоинства Windows

    Windows предоставляет многие преимущества и для пользователей и для
создателей программ. Преимущества для пользователя включают в себя:
    - стандартные и предопределенные операции. Если вы знаете как
использовать одну прикладную программу для Windows, то вы знаете как
использовать их все.
    - нет необходимости устанавливать устройства и драйверы для каждой
программы. Windows предоставляет драйверы для поддержки переферийных
устройств.
    - взаимодействие и связь между процессами.
    - многозадачность: возможность выполнять несколько программ сразу.
    - доступ к большей памяти. Windows поддерживает защищенный режим на
процессорах 80286, 80386 и 80486; она поддерживает виртуальную память на
процессорах 80386 и 80486.
    Достоинства для разработчиков включают в себя следующие:
    - независимую от устройств графику, поэтому графические прикладные
программы могут работать со всеми стандартными дисплейными адаптерами.
    - встроенную поддержку широкого круга принтеров, мониторов и
указательных устройств, таких как "мышь" и т.п.
    - богатую библиотеку графических подпрограмм;
    - больший объем памяти для больших программ;
    - поддержку меню, иконок, побитовых изображений и т.д.

    Требования

    Список достоинств, предоставляемых Windows, требует достаточно сложного
аппаратного обеспечения: Windows обычно требует для выполнения программ,
сравнимых по производительности с ДОС программами, лучшее графическое
аппаратное обеспечение, больше памяти и более быстрый процессор. Если у
вас машина с 80286 процессором или лучше с по крайней мере с 2Мб памяти, то
Windows будет работать превосходно.

         Объектно-ориентированное программирование в Windows

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

    Лучший интерфейс с Windows

    ObjectWindows использует объектно-ориентирование возможности Borland
C++ для формирования частей API Windows, изолируя вас от деталей
программирования в Windows. В результате, вы можете писать Windows
программы с гораздо меньшими затратами времени и энергии. Проще говоря,
ObjectWindows обеспечивает три полезные возможности:
    - формирование оконной информации;
    - абстракцию многих функцийAPI Windows;
    - автоматический ответ на сообщения.

    Формирование оконной информации

    ObjectWindows содержит объекты, которые определяют свойства и хранение
данных для окон, диалоговых блоков и управляющих средств прикладных
программ под Windows. В прикладной программе ObjectWindows интерфейсный
объект выступает как представитель явного интерфейсного элемента Windows.
Хотя интерфейсный объект и интерфейсный элемент работают в тесной
взаимосвязи, важно понимать различие между ними.
    Интерфейсный объект связывается с новым интерфейсным объектом
посредством вызова (неявно) его функции-компоненты Create. Create вызывает
соответствующую функцию Windows для создания нового интерфейсного элемента.
Компонента данных интерфейсного объекта, передаваемые вызову Windows,
определяют физические атрибуты создаваемого интерфейсного элемента. Windows
возвращает дескриптор, или идентификатор, созданного интерфейсного объекта.
Интерфейсный объект хранит этот дескриптор как его компоненту данных
HWindow.
    Функции Windows, которые оперируют с окном, требуют передачи его
дескриптора; хранение дескриптора как компоненты данных делает его легко
доступным. Аналогично, компоненты данных могут использоваться для хранения
инструментальных средств рисования или информации о состоянии для
определенного окна.

         Абстрагирование функций Windows

    Прикладные программы в Windows формируют свой внешний вид и поведение
путем вызова набора из более 600 функций , составляющих Windows API. Каждая
функция может иметь различное число параметров многих типов. Хотя вы можете
вызывать любую функцию Windows непосредственно из Borland C++,
ObjectWindows облегчает эту задачу, путем предоставления функций-компонент
объектов, которые абстрагируют многие вызовы функций.
    Как замечалось выше, многие параметры для функций Windows уже хранятся
в компонентах данных интерфейсных объектов. Поэтому, функции-компоненты
могут использовать эти данные для передачи их функциям Windows в качестве
параметров. В дополнение, ObjectWindows группирует связанные вызовы функций
в одни функции-компоненты, которые обслуживают задачи более высокого
уровня. Результатом этого является направленный, легкий в использовании
интерфейс с Windows.
    Хотя этот подход достаточно сильно снижает вашу зависимость от сотен
функций API Windows, он не исключает возможности их прямого вызова из API.
ObjectWindows содержит лучшее из двух миров: высокоуровневое,
объектно-ориентированное написание программ плюс максимальный контроль за
графической средой.

    Автоматический ответ на сообщения

    Большему числу Windows программ в дополнение к тому, что они должны
указывать среде Windows что ей делать, необходимо уметь реагировать на
сотни сообщений Windows, которые являются результатом действий пользователя
(например, нажатие кнопки "мыши"), других программ или других источников.
Помним, что Windows посылает вашей программе сообщение каждый раз, когда
пользователь нажимает кнопку "мыши" или передвигает ее указатель. Ваша
прикладная программа возможно будет получать сотни сообщений в течении
всего нескольких минут. Обработка и корректный ответ на сообщения является
важным фактором правильного функционирования вашей программы.
    Объекты, с их способностью наследовать и переопределять поведение
(функции-компоненты), являются наиболее удобными для решения задачи ответа
на входящие стимуляторы (сообщения Windows). ObjectWindows берет сообщения
Windows и превращает их в вызовы функций-компонент Borland C++. Поэтому,
используя ObjectWindows, вы просто определяете функцию-компоненту для
ответа на каждое сообщение, на которое ваша программа должна реагировать.
Например, когда пользователь нажимает левую кнопку "мыши", то Windows
генерирует сообщение WM_LBUTTONDOWN. Если вы хотите, чтобы окно или
управляющий блок в вашей программе ответил на это нажатие, вы просто
определяете функцию-компоненту WMLButtonDown, являющеюся ключевой для
сообщения WM_LBUTTONDOWN. Затем, при посылке Windows этого сообщения, ваш
объект автоматически вызовет функцию-компоненту, определенную ранее.
    Компоненты-функции такого тип называются функциями ответа на сообщения.
Вы коммутируете определенное сообщение Windows с функцией ответа на
сообщение использованием возможности компиляторов Borland C++, называемой
виртуальными таблицами динамического диспетчирования (DDVT). При объявлении
динамически диспетчируемой функции-компоненты вы также задаете и
целочисленный индекс диспетчирования. Например, в
    virtual void Test() = [100];
Test определяется как динамически диспетчируемая функция-компонента класса
с индексом диспетчирования 100.
    Если вы просмотрите файл WINDOWS.H, то вы увидите, что сообщениям
Windows даются описательные имена типа WM_LBUTTONDOWN, но в
действительности используются только целочисленные значения, поэтому вы
можете использовать DDVT с сообщениями Windows.
    ObjectWindows направляет сообщение Windows той функции, чей индекс
диспетчирезации равен значению сообщения. Например,
    virtual void WMLButtonUp(RTMessage Msg) = [WM_FIRST + WM_LBUTTONUP];
     !!! Сумма констант WM_FIRST и WM_LBUTTONUP является целочисленной,
         поэтому Windows использует его для идентификации сообщения.
объявляет функцию void, которая отвечает на сообщение Windows WM_FIRST +
WM_LBUTTONUP. Вам больше нечего делать - ObjectWindows знает когда вы
добавили новую функцию ответа на сообщение и автоматически начинает
отслеживание сообщений для нее. ObjectWindows ожидает, что функции ответа
на сообщения имеют тип параметра RTMessage. Помним, что RTMessage является
ссылкой на TMessage. Объекты TMessage содержат всю информацию, которую
Windows передает вместе с сообщением.
    Внимание !!!
    Существует несколько ограничений на использование динамически
диспетчируемых функций-компонент:
    - При переопределении в производном классе динамически диспетчируемой
функции базового класса должен обязательно использоваться тот же индекс
диспетчирования. Например, следующее определение ошибочно:
    class TestBase
    {
      virtual void sample()  = [100];
    };

    class TestDerived : public TestBase
    {
      virtual void sample()  = [299];
    };
    !!! Это определение ошибочно, так как индексы диспетчирования различны.

    - Каждая динамически диспетчируемая функция в иерархии классов должна
иметь уникальный индекс диспетчирования. Например, следующее определение
ошибочно:
    class TestBase
    {
      virtual void sample1()  = [100];
      virtual void sample2()  = [200];
      virtual void sample3()  = [100];
    };
    !!! Это определение ошибочно, так как тут пытаются присвоить двум
различным функциям одинаковые индексы диспетчирования.

    - "Обычная" (не динамически диспетчируемая) виртуальная функция в
базовом классе не может быть переопределена в производном классе
динамически диспетчируемой функцией. Например, следующее определение
ошибочно:
    class TestBase
    {
      virtual void sample();
    };

    class TestDerived : public TestBase
    {
      virtual void sample()  = [100];
    };
    !!! Это определение ошибочно, так как тут пытаются переопределить
обычную функцию динамически диспетчируемой.

    - Динамически диспетчируемая функция в базовом классе не может быть
переопределена в производном классе "обычной" виртуальной функцией.
Например, следующее определение ошибочно:
    class TestBase
    {
      virtual void sample() = [100];
    };

    class TestDerived : public TestBase
    {
      virtual void sample();
    };
    !!! Это определение ошибочно, так как тут пытаются переопределить
динамически диспетчируемую функцию обычной.

    - Когда класс является производным из более чем одного базового класса,
то говорят, что этот производный класс имеет множественное наследование.
Только первый базовый класс для производного класса со множественным
наследованием может иметь динамически диспетчируемые функции. Например,
следующее определение класса является допустимым:
    class A
    {
      virtual void AA() = [100];
    };

    class B
    {
    };

    class C : public A, public B
    {
    };
    !!! Здесь динамически диспетчируемые функции находятся в первом базовом
класс и все нормально.

    Но это определение является недопустимым:
    class A
    {
    };

    class B
    {
      virtual void ВВ() = [100];
    };

    class C : public A, public B
    {
    };
    !!! Это определение ошибочно, так как второй базовый класс, класс В,
имеет динамически диспетчируемые функции-компоненты.

    - Когда объект класса указывается посредством указателя и не
используется конкретного переопределения контекста, вы не можете вызывать
динамически диспетчируемые функции. Например, следующий пример является
ошибочным:
    class Base
    {
      virtual void sample()  = [100];
    };

    int main(void)
    {
      Base b;
      Base *BasePtr = &b;

      BasePtr->sample();
    }
    !!! Это выражение является ошибочным, так как вы не можете вызывать
динамически диспетчируемые функции-компоненты непосредственно через
указатель.

    - Виртуальный базовый класс не может иметь динамически диспетчируемых
функций-компонент. Например, следующий пример является ошибочным:
    class Base
    {
      virtual void sample()  = [100];
    };

    class derived : virtual public Base
    {
    };
    !!! Это определение является ошибочным, так как виртуальный базовый
класс не может иметь динамически диспетчируемых функций-компонент.

    Альтернативным методом ответа на сообщения Windows, которого
ObjectWindows избегает (но при программировании на Microsoft C требуется
обязательно), является использование длинных операторов switch с
ветвью case для каждого сообщения Windows, требующего обработки вашей
программой. Следующий пример приложения Windows взят из программы на С; как
вы можете видеть он имеет операторы switch внутри операторов switch.

    switch (message)
    {
      case WMU_DDE_CLOSE:
        CloseMsgWnd();
        break;

      case WM_COMMAND:
        switch (wParam)
        {
          case IDM_QUIT:
            // Пользователь выбрал QUIT из меню
            PostMessage(hWnd, WM_CLOSE, 0, 0L);
            break;

         case IDM_HOME:
            // Нажата клавиша Home
            SendMessage(hWnd, WM_HSCROLL, SB_TOP, 0L);
            SendMessage(hWnd, WM_VSCROLL, SB_TOP, 0L);
            break;

         case IDM_ABOUT:
            // Пользователь выбрал ABOUT из меню
            lpproc = MakeProcInstance(About, hInst);
            DialogBox(hInst, MAKEINTRESOURCE(ABOUT), hWnd, lpproc);
            FreeProcInstance(lpproc);
            break;
            ...

    !!! В этом примере показан не-объектно-ориентированный подход к
реализации ответов на сообщения Windows.

    Очевидно, что с 200 различными сообщениями Windows эти операторы switch
станут быстро практически неуправляемыми. Используемый ObjectWindows DDVT
является более простым.

         Структура Windows программы

    Так как много элементов программного обеспечения, такого как ДОС,
Windows, ваша прикладная программа и другие прикладные программы,
одновременно взаимодействуют друг с другом, будет полезным знать, какая
часть вашей Windows программы взаимодействует с окружающей ее средой. В
этой части рассматривается структура Windows программы и приводится пример
типичной Windows программы, написанный на Botland C++ с помощью
ObjectWindows.

    Структура Windows

    Ко времени исполнения выполняемые функции Windows и его API находятся в
трех внешних библиотечных модулях, которые вызываются выполняемым в
настоящий момент приложением:
    - KERNEL.EXE - отвечает за управление памятью и ресурсами, планирование
и взаимодействие с ДОС.
    - GDI.EXE - отвечает за вывод графики на экран и принтер.
    - USER.EXE - отвечает за управление окнами, ввод пользователя и
взаимодействия.
    Эти модули являются частью поставляемой версии Windows. Мы
предполагаем, что ваши пользователи имеют Windows и данные модули уже
загружены на их компьютеры. Вы будете поставлять программы, которые
используют эти библиотечные модули, но не содержат их в себе.

    Взаимодействие с Windows и с ДОС

    Из-за ограниченной области действия операционной системы ДОС легко
упустить из виду тот вклад, который она вносит в успешное функционирование
ее прикладных программ. Тем не менее, ДОС программа работает благодаря
взаимодействию между вашей прикладной программой и элементами операционной
системы. То же самое относится и Windows программам. Так как Windows
предоставляет гораздо больше функций операционной системы, то гораздо
сложнее упустить взаимосвязь между Windows и прикладной программой. Ваша
программа должна постоянно взаимодействовать с операционной системой (ДОС
плюс Windows).
љЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋ‰
ѓ   Прикладная программа Windows  ^                         ѓ
ѓ           љЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЏЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋ„
ѓ           ѓ                     V                         ѓ
ѓ    ^      ѓ Модули Windows: GDI, USER и KERNEL            ѓ                                ѓ
ЌЋЋЋЋЏЋЋЋЋЋЋ‹ЋЋЋЋЋЋЋЋЋЋЋЋ‰                               ^  ѓ
ѓ ^  V   ДОС             ЌЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЏЋЋ„
ЌЋЏЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋ„                               V  ѓ
ѓ VДрайверы устройсв ДОС ѓ ^ Драйверы устройств Windows  ^  ѓ
ЌЋЏЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋ‹ЋЏЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЏЋЋ„
ѓ V                        V                             V  ѓ
ѓ        Компьютер и перефирийное оборудование              ѓ
ЉЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋ™

    "Hello, Windows" ("Здравствуй Windows")

    Традиционным способом презентации нового языка или среды является
написание на этом языке или для этой среды программы "Hello, World". Эта
программа содержит только несколько команд, необходимых для вывода на
экран строки "Hello, World".
    Конечно в Windows вам придется сделать достаточно много операций, чтобы
достигнуть результата. Вы должны высветить окно, написать в нем, и затем
задать порядок взаимодействия окна с окружающей его средой, по крайней мере
пока, достаточно просто закрыть его и удалить. Если вы будете это делать в
лоб, то решение этой простой задачи приведет к созданию достаточно длинной
программы.
    Это происходит из за того, что Windows имеет ряд требований, которым
должна удовлетворять прикладная программа прежде чем она может быть
запущена. Даже простейшая программа реализуется достаточно большим исходным
кодом. К счастью, программы, написанные с помощью ObjectWindows,
автоматически удовлетворяют большему числу этих требований, включая
создание и вывод главного окна. Поэтому программа HelloWorld упрощается до
приведенной ниже:

    !!! Это программа HELLOAPP.CPP
    #include 

    // Определяем класс, являющийся производным от TApplication
    class THelloApp :public TApplication
    {
    public:
      THelloApp(LPSTR AName, HANDLE hInstance, HANDLE hPrevInstance,
                LPSTR lpCmdLine, int nCmdShow) : TApplication(AName,
                hInstance, hPrevInstance, lpCmdLine, NCmdShow) {};
      virtual void InitMainWindow();
    };

    // Конструктор компоненты данных MainWindow класса THelloApp
    void THelloApp::InitMainWindow()
    {
       MainWindow = new TWindow(NULL, "Hello World!");
    }
    int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
      LPSTR lpCmdLine, int nCmdShow)
    {
      THelloApp HelloApp ("HelloApp", hInstance, hPrevInstance,
                          lpCmdLine, nCmdShow);
      HelloApp.Run();
      return Hello.App.Status;
    }

    Характеристики запуска прикладной программы

    В начале исполнения прикладной программы, Windows передает ей четыре
параметра. В программе ObjectWindows эти параметры передаются конструктору
объекта прикладная программа, производного от класса TApplication. Эти
параметры также заносятся в компоненты данных объекта прикладная программа,
для обеспечения удобного доступа к ним из кода программы.
    Компоненты данных приложения имеют следующее назначение:
    - hInstance хранит дескриптор экземпляра приложения.
    - hPrevInstance хранит дескриптор предыдущего экземпляра этого же
приложения.
    - lpCmdLine хранит LPSTR на строку, соответсвующее командной строке
запуска приложения, включающую имена файлов и опции. Например, "CALC.EXE
/M" или "WORDPROC.EXE LETTER1.DOC".
    - nCmdShow хранит целое число, представляющее начальный режим дисплея
для главного окна. Оно используется при вызове функции-компоненты Show.
    Первым делом прикладная программа ObjectWindows должна сконструировать
объект прикладная программа из базового класса TApplication. В нашем
примере мы используем THelloApp.
    Для первого экземпляра прикладной программы ObjectWindows, Run вызывает
InitApplication для проведения необходимых инициализаций для всего
приложения. Run инициализирует все экземпляры (включая первый) вызовом
InitInstance. Затем InitInstance вызывает функцию-компоненту InitMainWindow
для конструирования и инициализации объекта главное окно.
    В заключении, Run запускает цикл главного сообщения вашей прикладной
программы ObjectWindows. Возврат Run происходит только при завершении
программы. Код возврата приложения заносится в компоненту данных Status,
которую вы должны возвратить в Windows, аналогично тому, как мы делали в
показанной ранее функции WinMain.

    Характеристики главного окна

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

         Процесс создания прикладной программы

    Поскольку существуют определенный набор требований для любой Windows
программы (например, инициализация главного окна), то проще всего начинать
создание вашей прикладной программы с переделки для ваших целей уже
существующих Windows программ. ObjectWindows предоставляет широкий набор
простых программ; выберете из них наиболее подходящую для вас. Затем вам
придется пройти следующие этапы:
    1. Создание нового программного кода.
    2. Создание ресурсов для меню, блоков диалога и так далее.
    3. Компиляция программы.
    4. Отладка программы.

    Глава 2. Шагаем по Windows

    Теперь, когда вы познакомились с библиотекой ObjectWindows, вы готовы
к написанию первых простых программ ObjectWindows. В следующих нескольких
частях вы узнаете как создавать графические, интерактивные Windows
программы, поддерживающие меню, загрузку и сохранение файлов, вывод графики
и текста и , даже, имеющие собственные простейшие системы помощи. Вместе с
этим, мы расскажем вам об основных принципах создания приложений под
Windows, например, обработка сообщений, управление связями между
родительскими и "дочерними" окнами и автоматическая перерисовка графики.
    Наше путешествие по Windows будет состоять из 10 шагов, описываемых в
частях со 2 по 6:
    - шаг 1: простейшая Windows программа;
    - шаг 2: класс главного окна;
    - шаг 3: вывод текста в окно;
    - шаг 4: рисование линий в окне;
    - шаг 5: изменение толщины линий;
    - шаг 6: вывод графики;
    - шаг 7: меню для главного окна;
    - шаг 8: добавление всплывающего окна;
    - шаг 9: занесение изображения в файл;
    - шаг 10: всплывающее окно помощи.
    Если вы не поменяли имена каталогов, задаваемые по умолчанию, то
исходные тексты программ будут находится в подкаталоге EXAMPLES\STEPS
главного каталога OWL. Они имеют названия STEP1.CPP, STEP2.CPP и так далее,
соответствую номеру описываемого в данной книге шага.
    На рисунке 2.1 показан внешний вид приложения, получаемого в конце 10
шага нашего путешествия по Windows.

    Рисунок 2.1 Полное приложение ObjectWindows.

         Создание прикладной программы ObjectWindows: предварительные
         рассуждения

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

    Библиотека контейнерных классов

    Библиотека классов ObjectWindows зависит от библиотеки контейнерных
классов в подкаталоге CLASSLIB. Все объекты ObjectWindows совместно
используют класс Object (определяемый в библиотеке контейнерных классов)
как свой базовый класс. В классах ObjectWindows также используются
некоторые другие классы из этой библиотеки. В ваших программах
ObjectWindows вы можете использовать любые из контейнерных классов.

    Каталоги

    Помимо стандартных для С++ файлов заголовка , как STDIO.H, вашей
прикладной программе возможно потребуются дополнительные файлы заголовков
ObjectWindows, например OWL.H. В этом случае для вспомогательных утилит вы
будете обязаны указывать их местонахождение. Для написания программ с
использованием библиотеки ObjectWindows вы обязательно должны включать, по
крайней мере, файл OWL.H. Более сложные приложения требуют включения и
других файлов заголовков. Например, программа, использующая класс TButton,
должна включать BUTTON.Н. Прикладные программы ObjectWindows и сама
ObjectWindows основываются на библиотеке контейнерных классов, поставляемой
с ObjectWindows, поэтому вы обязательно должны включать путь поиска
включаемой библиотеки контейнерных классов ObjectWindows.
    Всем прикладным программам необходим доступ к стандартным библиотекам
Borland C++, плюс, к созданным вами самими и полученным от третьих лиц.
Кроме этого, приложения ObjectWindows требуют доступа к поставляемым
библиотекам контейнерных классов ObjectWindows и стандартным библиотекам
ObjectWindows.
    В следующей таблице приведены создаваемые установочными утилитами по
умолчанию имена каталогов ObjectWindows или Borland C++. Если в
установочной утилите вы зададите другие имена, то вам нужно будет внести
соответствующие изменения в MAKE файлы и файлы проектов.

    Таблица 2.1 Каталоги INCLUDE по умолчанию
------------------------------------------------------------------------
 Тип                   Каталог INCLUDE            Каталог библиотек
                       по умолчанию               по умолчанию
------------------------------------------------------------------------
 ObjectWindows         \BORLANDC\OWL\INCLUDE      \BORLANDC\OWL\LIB
 Библиотека            \BORLANDC\CLASSLIB\INCLUDE \BORLANDC\CLASSLIB\LIB
 контейнерных классов
 Borlan C++            \BORLANDC\INCLUDE          \BORLANDC\LIB
------------------------------------------------------------------------

    Указание необходимой библиотеки

    Существует три различных библиотеки ObjectWindows и библиотеки
контейнерных классов - одна для каждой из моделей памяти: small (малой),
medium (средней) и large (большой). Эти библиотеки располагаются в каталогах
библиотек и приведены в следующей таблице:

    Таблица 2.2 Библиотеки для каждой модели памяти
------------------------------------------------------
 Модель памяти  Библиотека     Библиотека контейнерных
                ObjectWindows  классов
------------------------------------------------------
 Small          OWLWS.LIB      TCLASSWS.LIB
 Medium         OWLWM.LIB      TCLASSWM.LIB
 Large          OWLWL.LIB      TCALSSWL.LIB
------------------------------------------------------

    Программы ObjectWindows, использующие DLL

    Динамически связываемые библиотеки (DLL), библиотеки импорта для
ObjectWindows и библиотека времени выполнения Borland C++ доступны как
показано в таблице 2.3. Если вы хотите, чтобы ваша прикладная программа
использовала одну из этих DLL, то вы должны связать ее с соответствующей
библиотекой импорта.

    Таблица 2.3 DLL и библиотеки импорта
---------------------------------------------
 Тип            DLL        Библиотека импорта
---------------------------------------------
 ObjectWindows  OWL.DLL    OWL.LIB
 Borland C++    BCRTL.DLL  CRTLL.LIB
---------------------------------------------
    Внимание !!!
    Вы можете использовать одну, обе или ни одну из этих DLL; однако, если
вы используете по крайней мере одну DLL, то вы обязательно должны
компилировать вашу программу в модели памяти large (-ml) и с опцией
интеллектуальных обратных вызовов (-WS). Вы также должны связать вашу
прикладную программу с библиотекой контейнерных классов TCLASSDL.LIB. Не
существует DLL для библиотеки контейнерных классов.

    Создания файла ресурсов

    Хотя обычно для создания файлов ресурсов используют редактор ресурсов,
вы можете использовать и компилятор ресурсов командной строки RC. Файл
ресурсов ObjectWindows аналогичен любому другому файлу ресурсов в Windows,
за исключением возможного присутствия в нем включаемых файлов, специфичных
для ObjectWindows. Некоторые файлы ресурсов *с расширениями .DLG или .RC)
поставляются вместе с ObjectWindows. Четыре класса в ObjectWindows
ссылаются ни эти файлы для получения ресурсов, используемых в этих классах.

    Таблица 2.4 Классы ObjectWindows, которые требуют файлов ресурсов
----------------------------------------------------
 Классы        Необходимый файл(ы) ресурсов
----------------------------------------------------
 TInputDialog  INPUTDIA.DLG
 TFileDialog   FILEDIAL.DLG
 TEditWindow   STDWNDS.DLG, EDITACC.RC, EDITMENU.RC
 TFileWindow   STDWNDS.DLG, FILEACC.RC, FILEMENU.RC
----------------------------------------------------
    Если приложение ObjectWindows использует один из этих классов, то
убедитесь, что файл ресурсов для этого приложения включает в себя
соответствующие файлы ресурсов ObjectWindows. Простейший файл ресурсов для
прикладной программы, использующей классы TInputDialog и TFileDialog, может
быть следующим:
    #include 
    #include 
    rcinclude inputdia.dlg
    rcinclude filedial.dlg
    ...
    Другие необходимые вашему приложению ресурсы будут следовать за
привиденными выше директивами. Файлы ресурсов ObjectWindows и файл OWLRC.H
располагаются в каталоге включаемых файлов ObjectWindows; этот каталог
должен обязательно задаваться в RC как опция командной строки. Поскольку
файл WINDOWS.H находится в каталоге включаемых файлов Borland C++, то этот
каталог также обязательно задается для RC как опция командной строки. Для
задания включаемых каталогов используйте опцию командной строки RC -i.
Несколько включаемых каталогов задается повторением опции -i для каждого из
них.

         Создание прикладной программы ObjectWindows: специфика

    Обсудив предварительные замечания к созданию Windows программ с помощью
библиотеки ObjectWindows (мы будем называть их ObjectWindows программы или
приложения), мы можем перейти к специфическим моментам этого создания.
    Существует три способа создания прикладных программ ObjectWindows.
Первый (наиболее простой) - это изменить для своих целей один из
поставляемых MAKE-файлов. Второй - использовать менеджер проектов
Интегрированной Среды Разработки (ИСР) Borland C++. Третий, и последний, -
напрямую использовать утилиты командной строки. Здесь мы обсудим только
второй и третий способы, так как использование MAKE-файла почти совпадает с
прямым использованием утилит командной строки.
    В этой документации мы предполагаем, что вы знакомы с утилитами,
входящими в Borland C++ (ИСР и утилиты командной строки). Вы также должны
быть знакомы с созданием Windows программ в общем. Более подробная
информация по этим вопросам приводится в документации по Borland C++.

    Использование ИСР для создания приложения ObjectWindows

    Для создания проложения ObjectWindows в ИСР Borland C++, вы должны
использовать файл проекта. Подробная информация о файлах проекта приводится
в документации по ИСР. Общие шаги построения проекта приложения
ObjectWindows включают в себя следующие:
    - Для повышения скорости компиляции (и очень часто достаточно большого)
многих требуемых файлов заголовков вы должны выбрать опцию
предкомпилированных заголовков.
    - Открыть файл проекта.
    - Выбрать тип приложения ЕХЕ для Windows.
    - Если вместе с вашей прикладной программой вы используете DLL,
DLL библиотеки времени выполнения Borland C++ или пользовательскую DLL, то
вы должны выбрать опцию интеллектуального обратного вызова и модель памяти
large.
    - Вы рать любые другие требуемые опции компилятора и компоновщика.
    - Задать включаемые каталоги. По умолчанию берутся следующие имена:
      C:\BORLANDC\OWL\INCLUDE,
      C:\BORLANDC\CLASSLIB\INCLUDE и
      C:\BORLANDC\INCLUDE
    - Задать каталоги библиотек. По умолчанию берутся следующие имена:
      С:\BORLANDC\OWL\LIB
      С:\BORLANDC\CLASSLIB\LIB и
      С:\BORLANDC\LIB
    - Добавить в проект имена исходных файлов вашего приложения (с
расширениями .CPP, .C, .ASM и т.д.).
    !!! Предупреждение ! Не добавляйте здесь файлы заголовков, имеющие
расширение .Н !
    - Добавьте в проект файл ресурсов .RC вашего приложения. Смотри более
подробную информацию о файлах проектов выше. В приводимом примере вы должны
добавить файл ресурсов STEPS.RC.
    !!! Предупреждение ! Не добавляйте здесь файлы ресурсов диалога .DLG !
    - Если вы хотите использовать ObjectWindows статически связанную с
вашим приложением, то добавьте библиотеку ObjectWindows так, чтобы она
соответствовала используемой модели памяти. Например, добавьте OWLWS.LIB
для модели памяти приложения small. В противном случае, если вы хотите
использовать динамически связываемую библиотеку (DLL) ObjectWindows
(OWL.DLL), то добавьте библиотеку импорта DLL ObjectWindows, OWL.DLL.
    - Добавьте допустимую для модели памяти вашего приложения библиотеку
контейнерных классов ObjectWindows. Например, для приложения с моделью
памяти medium добавьте TCALSSWM.LIB. Допустимой библиотекой контейнерных
классов для приложения, которое использует DLL ObjectWindows, DLL
библиотеки времени выполнения Borland C++ или пользовательскую DLL
ObjectWindows, является TCLASSDL.LIB.
    - Вы можете захотеть динамически связать библиотеку времени выполнения
Borland C++ с вашим приложением. Это уменьшит размер вашего .ЕХЕ файла. Для
этого просто добавьте в проект библиотеку импорта BCRTL.LIB.
    - Создайте вашу прикладную программу. Менеджер проектов ИСР проверит
ваш проект на наличие подлежащих компиляции исходных файлов вашего
приложения и файла ресурсов .RC; затем компоновщик скомпонует промежуточные
объектные файлы .OBJ и библиотеки; и в конце, в файл ЕХЕ или DLL будут
добавлены соответсвующие ресурсы.

    Использование утилит командной строки для создания прикладных программ
    ObjectWindows

    Если для компиляции и связывания вашего приложения ObjectWindows вы
хотите использовать компилятор командной строки Borland C++, то возможно
самым простым будет отредактировать ваш файл конфигурации (или создать его,
если еще не создали) для задания нужных каталогов включаемых файлов и
библиотек. Для задания требуемых каталогов в файле конфигурации используйте
директивы -i или -I.
    Компиляция и связывание вашего приложения производится следующей строкой
команд и опций:
    BCCX -WE myprog.cpp owlws.lib tclassws.lib
    Этой командой вначале компилируется программа MYPROG.CPP и затем
происходит связывание ее с OWLWS.LIB и TCLASSWS.LIB.
    Внимание !!!
    Если вы используете вместе с вашим приложением DLL ObjectWindows, DLL
библиотеки времени выполнения Borland C++ или пользовательскую DLL
ObjectWindows, то вы должны обязательно производить компиляцию с опцией
командной строки -WS. Заметим, что для использования DLL ObjectWindows вы
должны создавать ваше приложение в модели памяти large. Например,
    BCCX -WS -ml myprog.cpp owl.lib tclassdl.lib bcrtl.lib
компилирует MYPROG.CPP и затем связывает ее с библиотеками импорта OWL.LIB
и BCRTL.LIB и библиотекой статического связывания TCLASSDL.LIB.
    Вы должны обязательно откомпилировать файлы ресурсов приложения.
Командная строка для этого будет иметь следующий вид:
    RC -i c:\borlandc\owl\include -i c:\borlandc\include myprog.rc
       myprog.exe
    Заметим, что вы должны, или явно задать имена каталогов включаемых
файлов в командной строке RC, или создать переменную среды с именем INCLUDE
и задать в ней имена соответствующих каталогов. Например, вы можете
заменить предыдущую команду следующими:
    SET INCLUDE=c:\borlandc\owl\include;c:\borlandc\include
    RC myprog.rc myprog.exe
    Оператор SET может быть размещен в вашем файле AUTOEXEC.BAT.
    В обеих случаях RC компилирует файл ресурсов .RC в двоичный файл
ресурсов .RES и затем добавляет его в ЕХЕ файл прикладной программы.
Результатом всей работы является создание исполняемого ЕХЕ файла прикладной
программы.

         Шаг 1: Простая Windows программа

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

    Требования прикладной программы

    Все Windows имеют главное окно, которое появляется при старте
программы. Пользователь завершает работу прикладной программы закрытием
главного окна. В приложении ObjectWindows главное окна обычно является
оконным объектом. Его владельцем является объект прикладная программа,
который создает и выводит главное окно, обрабатывает сообщения Windows и
завершает работу прикладной программы. Объект прикладная программа
действует как объектоно-ориентированный заменитель самой прикладной
программы. Аналогично этому, ObjectWindows предоставляет классы окон,
диалогов и т.п. для ухода от деталей программирования в Windows.
    Каждая программа ObjectWindows должна определить новый класс прикладная
программа, который происходит от поставляемого класса TApplication.
Экземпляр этого производного класса (объект прикладная программа)
конструируется в WinMain (точка входа в Windows программу). По соглашению,
типы (классы и экземпляры классов) обычно начинаются с буквы Т, а указатели
на типы - с букв РТ. В создаваемом нами приложении этот класс называется
TMyApp.
    Ниже приводится главная функция нашего приложения:

    int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
      LPSTR lpCmdLine, int nCmdShow)
    {
      TMyApp MyApp("Simple ObjectWindows Program", hInstance,
                   hPrevInstance, lpCmdLine, nCmdShow);
      MyApp.Run();
      return MyApp.Status;
    }
    !!! Это фрагмент из файла STEP1.CPP.

    В первом операторе WinMain конструирует объект прикладная программа
создаваемого нами приложения. Строка "Simple ObjectWindows Programm"
("Простая программа ObjectWindows") передается конструктору для занесения в
качестве значения его компоненты данных Name. Другие параметры,
передаваемые WinMain, также задаются конструктору и сохраняются как
компоненты данных объекта прикладная программа. MyApp.Run вызывается для
запуска приложения ObjectWindows. В последнем операторе возвращается
итоговый статус (компонента данных Status объекта прикладная программа).

    Определение класса прикладная программа

    Ваше приложение должно из стандартного класса ObjectWindows
TApplication породить новый класс (или несколько классов порожденных от
TApplacation). Этот новый класс должен переопределять по крайней мере одну
функцию-компоненту InitMainWindow. InitMainWindow конструирует объект
главное окно во время инициализации приложения ObjectWindows. Приведем
определение класса TMyApp:

    class TMyApp : public TApplication
    {
    public:
      TMyApp(LPSTR AName, HANDLE hInstance, HANDLE hPrevInstance, LPSTR
             lpCmdLine, int nCmdShow) : TApplication(AName, hInstance,
             hPrevInstance, lpCmdLine, nCmdShow) {};
      virtual void InitMainWindow();
    };
    !!! Это фрагмент из файла STEP1.CPP.

    Этот объект главное окно заносится в компоненту данных MainWindow
объекта прикладная программа. Говорят, что объект прикладная программа
владеет объектом главное окно, но эти два объекта не связаны иерархически.
Эта взаимоотношение владения называется экземплярное связывание.
Определение
TMyApp::InitMainWindow следующее:

    void TMyApp::InitMainWindow()
    {
      MainWindow = new TWindow(NULL, Name);
    }
    !!! Это фрагмент из файла STEP1.CPP.

    Объект главное окно класса TMyApp конструируется выше как экземпляр
класса TWindow. Передаваемый параметр NULL указывает, что окно должно быть
главным окном вашего приложения; второй параметр определяет титул окна. Мы
передаем компоненту данных Name объекта прикладная программа, которая к
этому времени установлена в значение "Simple ObjectWindows Programm". Ваш
объект главное окно будет сконструирован как экземпляр классов, производных
от классов ObjectWindows. В Шаге 2 мы заменим сконструированное здесь окно
на более интересное окно производного класса. На Рисунке 2.2 показан
внешний вид полученного окна.

    Рисунок 2.2 Реализация первого Шага вашего приложения ObjectWindows

    На этом шаге приложение просто выводит пустое окно, которое можно
перемещать, изменять размеры, максимизировать, минимизировать и закрывать.
Ниже приводится полный листинг программы в Шаге 1:

    #include 

    class TMyApp : public TApplication
    {
    public:
      TMyApp(LPSTR AName, HANDLE hInstance, HANDLE hPrevInstance, LPSTR
             lpCmdLine, int nCmdShow) : TApplication(AName, hInstance,
             hPrevInstance, lpCmdLine, nCmdShow) {};
      virtual void InitMainWindow();
    };

    void TMyApp::InitMainWindow()
    {
      MainWindow = new TWindow(NULL, Name);
    }

    int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
      LPSTR lpCmdLine, int nCmdShow)
    {
      TMyApp MyApp("Simple ObjectWindows Program", hInstance,
                   hPrevInstance, lpCmdLine, nCmdShow);
      MyApp.Run();
      return MyApp.Status;
    }

         Шаг 2: Класс главного окна

    Программа, созданная на Шаге 1, состоит из двух объектов: объекта
прикладная программа и объекта окно. Объект прикладная программа, MyApp,
является экземпляром класса TMyApp, производного от TApplication. Указатель
на объект окно, содержащийся в компоненте данных MainWindow объекта
прикладная программа, указывает на экземпляр TWindow, групповое окно
ObjectWindows. Обычно вы определяете для главного окна ваш собственный
класс окно, который отражает специфичные для вашей прикладной программы
характеристики. В этом разделе вы создадите объект главное окно, путем
определения специального оконного класса, производного от TWindow.

    Что такое оконный объект ?

    В Шаге 1 мы видели, что объект прикладная программа инкапсулирует
стандартные характеристики Windows программы, включая конструкцию главного
окна. Класс TApplication реализует основные характеристики вашей прикладной
программы ObjectWindows.
    Аналогично, оконный объект инкапсулирует необходимые характеристики
окна, включая способность к
    - выводу и изменению размера;
    - реакции на события;
    - управлению дочерними окнами;
    - закрытие в упорядоченном режиме.
    Оконные классы ObjectWindows поддерживают все эти характеристики.
    Для обеспечения большей гибкости и полезности вашей программы, вы
должны будете создавать новые оконные классы, производные от поставляемых в
ObjectWindows. Новые классы будут наследовать функции-компоненты и
компоненты данных имеющихся классов, а также добавлять некоторые новые.
Объектно-ориентированный подход оградит вас от постоянного "выдумывания
окон".

    Дескрипторы

    Все оконные объекты имеют, по крайней мере, три компоненты данных:
HWindow, Parent и список его "дочерних" окон. HWindow содержит дескриптор
окна. Дескриптор связывает интерфейсный объект, такой как окно, блок
диалога, или управляющий объект с с соответствующим ему интерфейсным
элементом. Он является в некотором смысле аналогом номерка к вашему пальто,
при оставлении его в гардеробе. Как вы предъявляете ваш номерок для
идентификации вашего пальто, так вы задаете дескриптор для идентификации
окна.
    В работе с оконными объектами вы не обязательно должны манипулировать
непосредственно с дескриптором окна. Однако, это необходимо при
непосредственном вызове функций Windows. Например, для посылки блока
сообщения вы вызываете функцию API Windows MessageBox. Функция MessageBox
требует задания дескриптора родительского окна блока сообщения. Ниже
приводится вызов MessageBox как это возможно в функции-компоненте
производного оконного класса. Информация о функциях Windows API содержится
в Главе 4 "Функции Windows" руководства "ObjectWindows для С++. Справочное
руководство".
    MessageBox(HWindow, "Do you want to save?", "Drawing has changed",
               MB_YESNO | MB_ICONQUESTION);

    Родительские и дочерние окна

    Большинство окон не существует независимо от других: она связаны вместе
и действуют взаимосвязанно. Например, когда вы завершаете работу вашей
прикладной программы, то все управляемые ей окна должны быть уничтожены.
Windows устанавливает взаимоотношения между окнами как отношения между
"родителем" и "дочерьми". Родительское окно ответственно за свои дочерние
окна. ObjectWindows предоставляет компоненты данных для каждого оконного
объекта для сохранения связи со своим родительским окном и своими дочерними
окнами.
    Компонента данных Parent содержит оконный объект родительского окна.
Это не родитель, в прямом смысле этого слова, а скорее владелец окна.
    Третьей компонентой данных окна является список его дочерних окон,
который содержит список указателей на дочерние окна данного окна, если они
есть. Мы будем добавлять дочерние окна на Шаге 8.

         Создание объекта главное окно.

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

    _CLASSDEF(TMyWindow)
    class TMyWindow : public TWindow
    {
    public:
      TMyWindow(PTWindowsObject AParent, LPSTR ATitle) :
        TWindow(AParent, ATitle) {};
    };
    !!! Это фрагметн программы из файла STEP2.CPP.

    Заметим, что конструктор для нового производного класса не определяет
новых характеристик, а просто вызывает конструктор базового класса
(TWindow).
    Второе, изменяем TMyApp::InitMainWindow так, чтобы он конструировал
TMyWindow , а не TWindow, как главное окно прикладной программы.

    void TMyApp::InitMainWindow()
    {
      MainWindow = new TMyWindow(NULL, Name);
    }
    !!! Это фрагметн программы из файла STEP2.CPP.

    Объявление нового класса и реализация его в InitMainWindow является
тем, что необходимо для определения нового класса для главного окна TMyApp.
Объект прикладная программа вызывает компоненты-функции оконного объекта
для создания оконного интерфейсного элемента и для вывода его на экран. Вы
не должны об этом беспокоится.
    Однако, TMyWindow не определяет новых свойств помимо тех, которые
наследуются из TWindow и TWindowsObject. Другими словами этот прием не
делает нашу прикладную программу более интересной. В следующем разделе вы
добавите несколько интересных свойств.

         Ответы на сообщения

    Наиболее быстрым способом извлечь пользу из оконного объекта - это
научить его реагировать на сообщения Windows. Например, при нажатии
пользователем в главном окне нашей прикладной программы на левую клавишу
"мыши" соответствующий оконный объект получает от Windows сообщений
WM_LBUTTONDOWN. Оно сообщает оконному объекту, что в нем пользователь нажал
на соответствующую клавишу "мыши". В этой ситуации также передается
координата точки, где нажата кнопка. (Эта информация используется в Шаге 6
данного руководства.) Аналогично, когда пользователь нажимает правую
клавишу "мыши", объект главное окно получает от Windows сообщение
WM_RBUTTONDOWN.
    Следующим шагом мы должны научить главное окна, являющееся экземпляром
TMyWindow, как отвечать на эти сообщения и делать что-либо полезное. Для
перехвата и реагирования на сообщения Windows вы должны определить
функцию-компоненту вашего оконного класса для каждого типа получаемого
сообщения на которое необходимо ответить. Эти функции-компоненты называются
функциями-компонентами ответа на сообщения. Вы определяете
функцию-компоненту ответа на сообщение для каждого сообщения на которое вы
хотите прореагировать. (Если вы не определите ответ на определенное
сообщение, то ObjectWindows будет обрабатывать его процедурой, принятой по
умолчанию). Для указания на то, что данная функция-компонента является
функцией-компонентой ответа на сообщение, добавьте к ее объявлению индекс
диспетчирования как обсуждалось выше в данном руководстве. Заметим, что имя
функции не играет никакого значения, важен только индекс диспетчирования.
Для имен функций ответа на сообщения мы берем наименования соответствующих
сообщений Windows и выбрасываем из них символы подчеркивания.

    class TMyWindow : public TWindow {
    public:
    ...
    virtual void WMLButtonDown(RTMessage Msg) =
      [WM_FIRST + WM_LBUTTONDOWN];
    ...
    };
    !!! Это фрагметн программы из файла STEP2.CPP.

    Msg является ссылкой на структуру TMessage (укороченная до типа
RTMessage ObjectWindows), содержащей информацию о происшедшем событии. (Об
этой структуре более подробно вы узнаете в Шаге 3). Всем
функциям-компонентам ответа на сообщения передается RTMessage и они имеют
тип возврата void.
    Для начала, определим функции-компоненты ответа на сообщения только
осуществляющие вывод блока сообщения, говорящего о нажатии соответствующей
клавиши "мыши". Позднее, вы добавите более полезные ответы.
Приведем определение функции-компоненты ответа на нажатие левой клавиши:

    void TMyWindow::WMLButtonDown(RTMessage)
    {
      MessageBox(HWindow, "You have pressed the left mouse button",
                 "Message Dispatched", MB_OK);
    }
    !!! Это фрагметн программы из файла STEP2.CPP.

    Рисунок 2.3 Наша прикладная программа отвечает на событие

    !!! Полный исходный код программы этого Шага приведен в конце данной
главы.

         Завершение работы прикладной программы

    Созданная нами программа прекращает работу при двойном нажатии кнопки
"мыши" на блоке управляющего меню главного окна, это маленький квадрат в
его верхнем левом углу. Окно и прикладная программа закрываются немедленно
после выполнения этого действия. Такое поведение является удобным лишь для
простых программ, но для более сложных является примитивным.
    Например, вы когда-нибудь завершаете работу прикладной программы без
сохранения результатов вашей работы. Хорошая прикладная программа просто
обязана предложить пользователю сохранить результаты его работы, если они
не были еще сохранены. Вы легко добавите эту возможность в вашу программу
ObjectWindows. Просто добавте действия программы на двойное нажатие кнопки
"мыши" для выхода.
    При попытке закрыть ваше ObjectWindows приложение, вызывается
виртуальная функция-компонента CanClose вашего класса прикладная программа.
Функция CanClose является функцией BOOL, которая указывает на согласие
(TRUE) пользователя закрыть прикладную программу. Как определено по
умолчанию, наследуемая из TApplication функция-компонента CanClose вызывает
функцию-компоненту объекта главное окно CanClose.
    В большинстве случаев объект главное окно решает, закрывать ли
приложение.
    Ваш класс главное окно, TMyWindow, наследует функцию-компоненту
CanClose из TWindowsObject. В случае наличия у окна дочерних окон, она
вызывает функцию-компоненту CanClose каждого из них. Если дочерние окна
отсутствуют (как в нашем случае), то она просто возвращает TRUE. Для
модификации поведения закрытия нашей прикладной программы вы должны
переопределить функцию-компоненту CanClose для класса главное окно:

    BOOL TMyWindow::CanClose()
    {
      return MessageBox(HWindow, "Do you want to save?",
        "Drawing has changed", MB_YESNO | MB_ICONQUESTION) == IDNO;
    }
    !!! Это фрагметн программы из файла STEP2.CPP.

    Теперь при попытке закрыть вашу прикладную программу она выдаст вам
блок сообщения "Do you want to save?" ("Хотите ли вы сохранится?"). Нажатие
кнопки Yes возвращает FALSE и предотвращает закрытие окна и прикладной
программы. Нажатие кнопки No возвращает TRUE и завершает работу программы.
На Рисунке 2.4 показан результат работы модифицированной программы.

    Рисунок 2.4 Шаг приложения с переопределенной реакцией на завершение
                работы

    Ниже приводится полный листинг нашей прикладной программы для Шага 2:

    #include 

    class TMyApp : public TApplication
    {
    public:
      TMyApp(LPSTR AName, HANDLE hInstance, HANDLE hPrevInstance, LPSTR
             lpCmdLine, int nCmdShow) : TApplication(AName, hInstance,
             hPrevInstance, lpCmdLine, nCmdShow) {};
      virtual void InitMainWindow();
    };

    _CLASSDEF(TMyWindow)
    class TMyWindow : public TWindow
    {
    public:
      TMyWindow(PTWindowsObject AParent, LPSTR ATitle) :
        TWindow(AParent, ATitle) {};
      virtual BOOL CanClose();
      virtual void WMLButtonDown(RTMessage Msg)
        = [WM_FIRST + WM_LBUTTONDOWN];
      virtual void WMRButtonDown(RTMessage Msg)
        = [WM_FIRST + WM_RBUTTONDOWN];
    };

    BOOL TMyWindow::CanClose()
    {
      return MessageBox(HWindow, "Do you want to save?",
        "Drawing has changed", MB_YESNO | MB_ICONQUESTION) == IDNO;
    }

    void TMyWindow::WMLButtonDown(RTMessage)
    {
      MessageBox(HWindow, "You have pressed the left mouse button",
                 "Message Dispatched", MB_OK);
    }

    void TMyWindow::WMRButtonDown(RTMessage)
    {
      MessageBox(HWindow, "You have pressed the right mouse button",
                 "Message Dispatched", MB_OK);
    }

    void TMyApp::InitMainWindow()
    {
      MainWindow = new TMyWindow(NULL, Name);
    }

    int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
      LPSTR lpCmdLine, int nCmdShow)
    {
      TMyApp MyApp("Simple ObjectWindows Program", hInstance,
                   hPrevInstance, lpCmdLine, nCmdShow);
      MyApp.Run();
      return MyApp.Status;
    }
    !!! Это STEP2.CPP

         Глава 3. Вывод информации в окно

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

         Что такое контекст вывода ?

    Вы можете понимать контекст вывода как представление поверхности окна,
предназначенной для вывода информации. Контекст вывода необходим Windows
для отрисовки в определенном окне графической и текстовой информации.
Дескриптор контекста вывода, его идентификатор, должен обязательно
передаваться функции "отрисовки" Интерфейса Графических Устройств Windows
(GDI).
    Перед выводом в окно, вы должны получить контекст вывода. До начала
процесса вывода на экран, вызовете в одной из функций-компонент оконного
объекта функцию Windows GetDC:
    TheDC = GetDC(HWindow);
    GetDC возвращает дескриптор контекста вывода (определяемого в Windows
типа HDC), который может теперь передаваться функциям Windows GDI. Приведем
несколько примеров вызовов GDI:
    LineTo(TheDC, 30, 45);
    Rectangle(TheDC, 100, 100, 200, 200);
    TextOut(TheDC, 20, 20, "Sample text", 11);
    После завершения вывода текста или графики вы обязаны освободить
контекст вывода. Обеспечение освобождения контекста вывода полностью
ложится на вас. Как только вы закончили вывод, освобождайте полученный
контекст.
    ReleaseDC(HWindow, TheDC);
    Внимание !!!
    Если вы не освободите все полученные контексты вывода, то вскоре вы
исчерпаете их (вся среда Windows имеет лимит в пять контекстов), и ваша
программа закончится аварийно, часто с зависанием компьютера. При аварийном
завершении работы вашей прикладной программы в течении 4-5 раз подряд при
осуществлении вывода на экран, самой вероятной причиной является
неосвобожденные контексты вывода.
    Контекст вывода выполняет несколько важных функций. Первое, он
гарантирует, что вы осуществляете вывод текста или графики в пределах
границ заданного окна. Текст и графика выводимая функциями GDI урезается до
границ окна для которого получен контекст вывода.
    Контекст вывода также поддерживает набор инструментальных средств
вывода (карандаш, кисточка, шрифт и палитра), используемых при вызове
функций GDI. В предыдущих примерах, выбранный в контексте вывода карандаш
будет использоваться для рисования линии при вызове функции LineTo;
нарисованная линия будет иметь характеристики выбранного карандаша (включая
цвет и ширину). Аналогично, выбранная в контексте вывода кисточка будет
использоваться функцией Rectangle для закраски заданной прямоугольной
области дисплея при . Выбранный шрифт будет использоваться функцией TextOut
для вывода строки "Sample Text".
    Вы можете выбрать в контексте вывода новые инструментальные средства
для изменения внешнего вида выводимой информации. Например, вы можете
захотеть использовать расраску по шаблону, карандаш другого цвета или
различные стили шрифта. Выбор нового карандаша демонстрируется на Шаге 5.

         Шаг 3 : Вывод в окно текстовой информации

    Вывод текста в главное окно нашей прикладной программы мы будем
осуществлять согласно описанному выше плану: Вначале получим контекст
вывода, затем произведем собственно операции вывода, и, наконец, освободим
контекст вывода. С целью сделать наше программу более интересной, будем
выводить текст в ответ на нажатие левой клавиши "мыши". Вместо посылки
блока сообщения (что мы делали в Шаге 2), вы будете выводить координаты
точки на которой находился указатель "мыши" при нажатии левой клавиши.
    Этот пример поможет вам разобраться и в системе координат Windows.
Например, "(20,30)" выводится в случае, когда клавиша "мыши" нажата в
точке, расположенной в 20 пикселах справа и 30 пикселах вниз от верхнего
левого угла области вывода окна (это область пользователя). Текст выводится
в точке нахождения указателя "мыши" в момент нажатия.

    Рисунок 3.1 Вывод координат точки нахождения указателя "мыши" в момент
                нажатия левой клавиши.

    Помним, что событие нажатия левой клавиши приводит к генерации
сообщения WM_LBUTTONDOWN, которое обрабатывается функцией-компонентой
ответа на сообщение WMLButtonDown.

    Структура сообщения

    В Шаге 2 вы узнали, что аргумент Msg, передающаяся функции-компоненте
ответа на сообщение ссылка на структуру TMessage (укороченная до типа
RTMessage), содержит информацию о происшедшем событии. Эти данных хранятся
в компонентах структуры LParam и WParam (типа LONG и WORD соответственно).
    Msg.LParam часто содержит две куска информации, первый - в старшем,
второй - в младшем слове. LParam объявляется как компонента объединения с
собой структуры LP, которая объявляет компоненты Hi и Lo типа WORD. Поэтому
вы можете использовать Msg.LP.Hi и Msg.LP.Lo для ссылки на старшие и
младшие слова Msg.LParam. Аналогично, WParam объявляется как компонента
объединения с собой cтруктуры WP. Однако Windows редко использует
компоненты Hi и Lo структуры WP.
    В случае WMLButtonDown Msg.LP.Lo содержит координату х точки нажатия, а
Msg.LP.Hi - координату у. Чтобы заставить WMLButtonDown выводить координаты
точки нажатия вы должны преобразовать Msg.LP.Lo и Msg.LP.Hi в текст и
добавить знаки пунктуации. В этом примере для построения выводимой строки
используется функция sprintf.
    Как только вы создали выводимую строку вы можете выдать ее на экран в
точку, где была нажата кнопка. Для этого вы вызываете Windows функцию
TextOut с передачей ей контекста вывода, координат точки (Msg.LP.Lo и
Msg.LP.Hi), строки S и длины строки strlen(S). Не забудем также получить и
освободить контекст вывода.

    void TMyWindow::WMLButtonDown(RTMessage Msg)
    {
      HDC DC;
      char S[10];
      sprintf(S, "(%d,%d)", Msg.LP.Lo, Msg.LP.Hi);
      DC = GetDC(HWindow);
      TextOut(DC, Msg.LP.Lo, Msg.LP.Hi, S, strlen(S));
      ReleaseDC(HWindow, DC);
    }
    !!! Это фрагмент программы из файла STEP3.CPP.

    Очистка экрана

    Вы можете добавить еще одну функцию в вашу прикладную программу вывода
текста - функцию очистки экрана. Заметим, что если вы однажды изменили
размер окна или накладываете и закрываете его, то выведенный текст
удаляется. Однако, вы можете захотеть принудительно очистить экран в ответ
на выбор пункта меню или некоторых действий пользователя, например нажатие
клавиши "мыши".
    Мы будем очищать окно в ответ на нажатие провой клавиши "мыши". Для
реализации этого мы переопределим функцию-компоненту WMRButtonDown так,
чтобы она вызывала Windows функцию InvalidateRect чьи аргументами задают
закраску всего окна. Поскольку ваше окно еще не умеет закрашивать само
себя, то оно только очищает область пользователя:

    void TMyWindow::WMLButtonDown(RTMessage)
    {
      InvalidateRect(HWindow, NULL, TRUE);
    }
    !!! Это фрагмент программы из файла STEP3.CPP.

         Шаг 4 : Рисование линий в окне

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

    Модель перемещения

    Будет полезным напомнить модель реакции Windows на события "мыши". Вы
уже видели Главе 2, что благодаря использованию поддержки DDVT, нажатие
лесой клавиши "мыши" приводит к выдаче сообщения WM_LBUTTONDOWN и вызову
функции-компоненты WMLButtonDoown. Ранее в этом руководстве мы реализовали
ответ на это сообщение в виде выдачи блока сообщения или выводе на экран
текстовой информации. Вы также видели, что нажатие на правую клавишу "мыши"
приводит к выдаче сообщения WM_RBUTTONDOWN и вызову функции-компоненты
WMRButtonDoown. Вы отвечали на это нажатие очисткой экрана. Но эти ответы
реагируют только на начальные нажатия клавиш "мыши". Но многие
интерактивные прикладные Windows программы требуют, чтобы вы, нажав на
клавишу "мыши", перемещали ее указатель по экрану для рисования линий или
прямоугольников, или размещения в определенном месте графических объектов.
В создаваемой нами графической программе мы должны перехватить события
перемещения и ответить на них рисованием линий.
    Это реализуется ответом на некоторые новые для вас сообщения. Когда
пользователь перемещает "мышь" в новую точку окна, то принимается сообщение
WM_MOUSEMOVE. Когда пользователь отпускает левую клавишу "мыши", то
происходит прием сообщения WM_LBUTTONUP. Обычно окно получает одно
сообщение WM_LBUTTONDOWN, затем серию сообщений WM_MOUSEMOVE и за ними одно
сообщение WM_LBUTTONUP. Типичная графическая Windows программа отвечает на
сообщение WM_LBUTTONDOWN инициализацией процесса вывода (включая получение
контекста вывода). На сообщение WM_MOUSEMOVE она реагирует выводом или
перемещением графической информации, и, наконец, на сообщение WM_LBUTTONUP
она реагирует завершением процесса вывода (освобождение контекста вывода).
    В следующей таблице приводятся наиболее часто получаемые сообщения о
событиях "мыши".

    Таблица 3.1 Наиболее часто получаемые сообщения "мыши".
-------------------------------------------------------------------
 Сообщение        Событие
-------------------------------------------------------------------
 WM_LBUTTONDOWN   Пользователь нажал левую клавишу "мыши".
 WM_RBUTTONDOWN   Пользователь нажал правую клавишу "мыши".
 WM_MOUSEMOVE     Пользователь переместил "мышь".
 WM_LBUTTONUP     Пользователь отпустил левую клавишу "мыши".
 WM_RBUTTONUP     Пользователь отпустил правую клавишу "мыши".
 WM_LBUTTONDBLCLK Пользователь дважды нажал левую клавишу "мыши".
 WM_RBUTTONDBLCLK Пользователь дважды нажал правую клавишу "мыши".
-------------------------------------------------------------------

    Реакция на сообщения перемещения

    В следующей таблице приводятся ответы на сообщения перемещения в
создаваемой нами программе рисования линий:

    Таблица 3.2 Сообщения, используемые в Шаге 4.
-------------------------------------------------------------------------
 Сообщение      Реакция
-------------------------------------------------------------------------
 WM_LBUTTONDOWN Очистить экран. Затем получить контекст вывода и занести
                его в DragDC. Позиционировать карандаш рисования в точку,
                на которой нажата клавиша.
 WM_MOUSEMOVE   Нарисовать линию от предыдущей точки до текущей точки,
                если клавиша "мыши" все еще нажата.
 WM_LBUTTONUP   Освободить DragDC.
-------------------------------------------------------------------------
    Как говорилось ранее, за сообщением WM_LBUTTONDOWN всегда следует
сообщение WM_LBUTTONUP, с или без сообщений WM_MOUSEMOVE между ними. Когда
вы получаете сообщение WM_LBUTTONDOWN вы должны получить контекст вывода и
освободить его по сообщению WM_LBUTTONUP. Вы будете использовать один
контекст вывода для всех операций вывода пока пользователь нажимает левую
клавишу "мыши".
    Сочетание получения контекста вывода с его освобождением является
обязательным условием правильного функционирования вашей программы. Однако,
вы можете добавить еще одну меру безопасности. Для TMuWindow, класса
главное окно, определите новую компоненту данных BOOL с именем ButtonDown.
WMLButtonDown установит ее в TRUE, а WMLButtonUp в FALSE. После этого вы
можете проверять значение ButtonValue до получения и освобождения контекста
вывода.
    Ниже приводится три функции-компоненты ответа на события "мыши":

    void TMyWindow::WMLButtonDown(RTMessage Msg)
    {
      InvalidateRect(HWindow, NULL, TRUE);
      if ( !ButtonDown )
      {
        ButtonDown = TRUE;
        SetCapture(HWindow);
        DragDC = GetDC(HWindow);
        MoveTo(DragDC, Msg.LP.Lo, Msg.LP.Hi);
      }
    }

    void TMyWindow::WMMouseMove(RTMessage Msg)
    {
      if ( ButtonDown )
        LineTo(DragDC, Msg.LP.Lo, Msg.LP.Hi);
    }

    void TMyWindow::WMLButtonUp(RTMessage)
    {
      if ( ButtonDown )
      {
        ButtonDown = FALSE;
        ReleaseCapture();
        ReleaseDC(HWindow, DragDC);
      }
    }
    !!! Это фрагмент программы из файла STEP4.CPP.

    Внимание !!!
    MoveTo и LineTo являются графическими функциями Windows API, которые
перемещают текущую позицию вывода и рисуют линию к текущей позиции,
соответственно. Они требуют передачи указателя на контекст вывода, поэтому
TMyWindow сохраняет его в своей компоненте данных DragDC. (Помним, что мы
осуществляем вывод не непосредственно в окно, а его контекст вывода).
    Если указатель "мыши" перемещается за пределы TMyWindow, сообщение
WM_MOUSEMOVE посылается ему и в этом случае, даже кода указатель
позиционируется на соседнее окно. Это происходит из-за того, что TMyWindow
захватывает входные данные "мыши" через обращение к SetCapture. (Вводные
данные "мыши" освобождаются через вызов ReleaseCapture).
    Убедитесь, что в определении объекта для TMyWindow измены заголовки
функций-компонет WMMouseMove и WMLButtonUp:

    virtual void WMLButtonDown(RTMessage Msg)
     = [WM_FIRST + WM_LBUTTONDOWN];
    virtual void WMLButtonUp(RTMessage Msg)
     = [WM_FIRST + WM_LBUTTONUP];
    !!! Это фрагмент программы из файла STEP4.CPP.

         Шаг 5 : Изменение толщины линий

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

    Рисунок 3.2 Изменение толщины линии.

    Выбор нового карандаша

    Для реализации изменения толщины рисуемой линии вам необходимо, в
начале, узнать немного нового о графике Windows и контекстах вывода в
частности.
    Инструментальными средствами рисования, используемыми для отображения
текстовой и графической информации, являются карандаши, кисточки и шрифты.
Описание этих элементов рисования хранится в памяти Windows как
интерфейсные элементы. Поэтому оба типа элементов идентифицируются с
помощью дескрипторов. Однако, элементы рисования не представлены объектами
ObjectWindows. Следовательно, только на вашей программе лежит
ответственность за создание и удаление используемых инструментальных
средств рисования.
    Внимание !!!
    Оставлении инструментальных средств ведет к значительному
непроизводительному расходу памяти Windows.
    Представляйте инструментальные средства рисования как кисти художника,
а контекст вывода как холст. Как только рисующий имеет все инструментальные
средства (кисточки) и контекст вывода (холст), то он выбирает нужное
средство и начинает рисовать. Аналогично, Windows программа должна выбрать
инструментальное средство рисования в контексте вывода. Как же иначе вы
можете рисовать в окне текст и линии без выбора инструментального средства
рисования ? Все контекста вывода имеют в своем составе набор
инструментальных средств, принятый по умолчанию: тонкий черный карандаш,
толстую черную кисть и системный шрифт. В нашем приложении мы выберем для
рисования линий в окне более тонкий карандаш.
    Первым шагом реализуем в нашем приложении поддержку диалога ввода.
Поддержка диалога ввода определяется в файле заголовка INPUTDIA.H. Добавим
его в наш исходный файл .СРР. Диалоги ввода предполагают, что вы определили
ресурс диалогов в файле .RC. Ресурс диалога ввода определяется в файле
INPUTDIA.DLG. Включим этот файл в наш файл .RC. Файл STERS.RC содержит все
определения ресурсов, используемых в создаваемой нами прикладной программе.
На этом шаге файл ресурсов .RC выглядит так
    #include "windows.h"
    #include "owlrc.h"
    rcinclude inputdia.dlg
    Следующим шагом добавим к TMyWindow компоненту данных для хранения
дескриптора инструментального средства карандаш, который мы будем
использовать для вывода графики. В нашей программе ограничимся только
рисованием и отображением линий, имеющих только одно значение толщины в
каждый текущий момент времени. Карандаш, соответствующий этой толщине,
будет хранится в новой компоненте данных TMyWindow, называемой ThePen. Вы
также создадите функцию-компоненту SetPenSize, отвечающую за создание
нового карандаша и удаление старого. Объявление класса TMyWindow теперь
будет выглядеть так:

    _CLASSDEF(TMyWindow)
    class TMyWindow : public TWindow
    {
    public:
      HDC DragDC;
    BOOL ButtonDown;
    HPEN ThePen;
    int PenSize;
    TMyWindow(PTWindowsObject AParent, LPSTR ATitle);
    ~TMyWindow();
    virtual BOOL CanClose();
    void SetPenSize(int NewSize);
    virtual void WMLButtonDown(RTMessage Msg)
     = [WM_FIRST + WM_LBUTTONDOWN];
    virtual void WMLButtonUp(RTMessage Msg)
     = [WM_FIRST + WM_LBUTTONUP];
    virtual void WMMouseMove(RTMessage Msg)
     = [WM_FIRST + WM_MOUSEMOVE];
    virtual void WMRButtonDown(RTMessage Msg)
     = [WM_FIRST + WM_RBUTTONDOWN];
    };
    !!! Это фрагмент программы из файла STEP5.CPP.

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

    TMyWindow(PTWindowsObject AParent, LPSTR ATitle)
       : TWindow(Aparent, ATitle)
    {
       ButtonDown = FALSE;
       PenSize = 1;
       ThePen = CreatePen(PS_SOLID, PenSize, 0);
    }

    TMyWindow::~TMyWindow()
    {
      DeleteObject(ThePen);
    }
    !!! Это фрагмент программы из файла STEP5.CPP.

    Теперь изменим функцию-компоненту WMLButtonDown так, чтобы она выбирала
текущий карандаш (ThePen) в последнем полученном контексте вывода.

    void TMyWindow::WMLButtonDown(RTMEssage Msg)
    {
      InvalidateRect(HWindow, NULL, TRUE);
      if ( !ButtonDown)
      {
        ButtonDown = TRUE;
        SetCapture(HWindow);
        DragDC = GetDC(HWindow);
        SelectObject(DragDC, ThePen);
        MoveTo(DragDC, Msg.LP.Lo, Msg.LP.Hi);
      }
    }
    !!! Это фрагмент программы из файла STEP5.CPP.
    !!! Подобно MoveTo и MessageBox, SelectObject является функцией Windows
API.

    Замена карандаша

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

    void TMyWindow::SetPenSize(int NewSize)
    {
      DeleteObject(ThePen);
      ThePen = CreatePen(PS_SOLID, NewSize, 0);
      PenSize = NewSize;
    }
    !!! Это фрагмент программы из файла STEP5.CPP.

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

    Реализация диалога ввода

    Нажатие правой клавиши "мыши" является удобным способом активизации
средства изменения толщины линии. Давайте переопределим функцию-компоненту
WMRButtonDown активизирующую блок диалога ввода, одного из диалоговых
средств ObjectWindows. Блок диалога ввода выступает как просто блок
диалога, получающий одну текстовую строку от пользователя.
    Как только объект диалога ввода сконструирован, вы можете запускать его
вызовом TModule::ExecDialog как модельный диалог. ExecDialog, используемая
для диалоговых объектов, аналогична MakeWindow, которая используется для
оконных и диалоговых объектов представляющих немодельные блоки диалога.
Обе функции создают интерфейсный элемент, только если интерфейсный объект
может быть проверен. ExecDialog, однако, возвращает значение только после
того, как пользователь закрыл диалог нажатием ОК или Cancel. Если
пользователь нажал ОК, то InputText заполняется введенной пользователем
информацией. Поскольку вы запрашиваете числовую толщину линии, то вы должны
преобразовать возвращенный текст в число, и передать его вызову SetPenSize.
Каждый раз, когда пользователь выбирает новую толщину линии, удаляется
старый карандаш и создается новый.

    void TMyWindow::WMRButtonDown(RTMessage)
    {
      char InputText[6];
      int NewPenSize;

      sprintf(InputText, "%d", PenSize);
      if ( GetApplication()->ExecDialog(new TInputDialog(this, "Line
                                        Thickness", "Input a new
                                        thickness:", InputText, sizeof
                                        InputText)) == IDOK )
      {
        NewPenSize = atoi(InputText);
        if ( NewPenSize < 0 )
          NewPenSize = 1;
        SetPenSize(NewPenSize);
      }

         Шаг 6 : Вывод графической информации

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

    Модель рисования

    Когда изображение одного из ваших окон требует обновления, или
осуществления рисования, Windows посылает вашей прикладной программе
сообщение WM_PAINT. Например, сообщение WM_PAINT посылается когда одно из
ваших окон по требованию пользователя изменяет свой размер или
раскрывается. ObjectWindows перехватывает сообщение WM_PAINT и вызывает
функцию-компоненту вашего окна WMPaint.
    ТObjectWindows определяет функцию-компоненту WMPaint, которая вызывает
виртуальную функцию Paint, осуществляющую необходимые оконные установки и
очистки до и после вызова. Переопределите Paint в классах, производных от
TWindows, с целью рисования вашего оконного изображения.
    Существует одно существенное различие между выводом графикм в
функции-компоненте Paint и в других случаях, это ответ на действия "мыши".
Используемый для рисования контекст вывода передается в параметре PaintDC,
поэтому вашей программе не надо получать и освобождать его. Однако, вам
надо будет выбрать заново в PaintDC инструментальное средство рисования.
    Для отрисовки содержимого вашего окна, повторите действия
осуществляемые при оригинальном рисовании на DragDC, но с использованием
PaintDC. Видимый эффект будет как и при рисовании в первый раз, которое
осуществлялось пользователем. Это будет очень похоже на перезапись
аудиопленки: это реальность или это ObjectWindows ? Но сперва вам надо
сохранить графические данные, необходимые для вашей функции-компоненты
Paint.

    Хранение графики как объекта

    В Шаге 4 мы видели, что линия изображалась первоначально в ответ на
сообщения WM_MOUSEMOVE. Эти сообщения содержали текущее положение указателя
"мыши", как х и у координаты точки на изображении окна. Линия рисовалась
соединением точек, посылаемых вместе с этим сообщением. Получаемые
координаты точек и являются графическими данными, необходимыми для
перерисовки линии.
    Для представления этих точек мы будем использовать объекты TPoint,
класс, производный от Object. (Вы могли бы использовать здесь и простую
структуру, а не объект, но вы будете хранить эти объекты TPoint в одном из
контейнеров библиотеки контейнерных классов, а все они, в свою очередь,
требуют объекты, производные от Object. Более того, на последующих шагах вы
добавите к TPoint функции-компоненты).
    Мы будем хранить объекты TPoint в компоненте данных TMyWindow,
называемой Points. Points будет являться экземпляром типа TPointArray,
который мы получим из контейнерного класса Array. Мы будем использовать
TPointArray, а не Array, так как в будущем мы добавим в TPointArray новые
возможности, недоступные в Array. Так как объекты Array (и производные от
Array классы) являются динамически увеличивающимися, то они очень удобны
для хранения переменного числа точек. Объекты Array и другие контейнерные
классы описаны в документации по вашему компилятору. Приведем часть
определения TPoint:

    class TPoint: public Object {
    public:
      int X, Y;
      TPoint(int AX, int AY) {X = AX, Y = AY;}
    ...
    };

    Ниже приводится часть определения TPointArray:

    _CLASSDEF(TPointArray)
    class TPointArray : public Array
    {
      TPointArray(int upper, int lower = 0, sizeType aDelta = 0) :
                  Array(upper, lower, aDelta) {};
      void DeleteAll();
    ...
    };
    !!! Это фрагмент программы из файла STEP6.CPP.

    Взглянем теперь на измененное объявление TMyWindow c добавленной
компонентой данных Points:

    _CLASSDEF(TPointArray)
    class TPointArray : public Array
    {
    ...
      PPointArray Points;
    ...
    };
    !!! Это фрагмент программы из файла STEP6.CPP.

    Убедимся, что Points конструируется в конструкторе TMyWindow и
удаляется в деструктуре TMyWindow.

    TMyWindow::TMyWindow(PTWindowsObject AParent, LPSTR ATitle)
      : TWindow(AParent, ATitle)
    {
      ButtonDown = FALSE;
      PenSize = 1;
      ThePen = CreatePen(PS_SOLID, PenSize, 0);
      Points = new TPointArray(50, 0 , 50);
    }

    TMyWindow::~TMyWindow()
    {
      delete Points;
      DeleteObject(ThePen);
    }
    !!! Это фрагмент программы из файла STEP6.CPP.
    Вы должны изменить WMLButtonDown и WMMouseMove для установки Points.

    void TMyWindow::WMLButtonDown(RTMessage Msg)
    {
      Points->DeleteAll();
      InvalidateRect(HWindow, NULL, TRUE);
      if ( !ButtonDown )
      {
        ButtonDown = TRUE;
        SetCapture(HWindow);
        DragDC = GetDC(HWindow);
        SelectObject(DragDC, ThePen);
        MoveTo(DragDC, Msg.LP.Lo, Msg.LP.Hi);
        Points->add(* (new TPoint(Msg.LP.Lo, Msg.LP.Hi)));
      }
    }

    void TMyWindow::WMMouseMove(RTMessage Msg)
    {
      if ( ButtonDown )
      {
        LineTo(DragDC, Msg.LP.Lo, Msg.LP.Hi);
        Points->add(* (new TPoint(Msg.LP.Lo, Msg.LP.Hi)));
      }
    }

    !!! Смотри описание библиотеки контейнерных классов для получения
информации о работе ArrayIterator.
    WMLButtonUp не требует модификации. В TMyWindow::WMLButtonDown до
начала новой линии компонента данных Points очищается от объектов TРoint
вызовом функции-компоненты TPointArray::DeleteALL:

    void TPointArray::DeleteAll()
    {
      RArrayIterator PoitIterator = (RArrayIterator)initIterator();

      while ( int(PointIterator) != 0 )
      {
        RObject AnObject = PointIterator++;
        if ( AnObject != NOOBJECT )
          detach(AnObject, 1);      // разъединение и удаление
      }
      delete &PointIterator;
    }
    !!! Это фрагмент программы из файла STEP6.CPP.

    Перерисовка сохраненной графической информации

    Теперь когда Points заполнен, вы должны написать программу для
изображения TMyWindow перерисовкой линий между каждыми соседними точками.
Мы создадим для TMyWindow функцию-компоненту Paint, которая будет повторять
действия ее функций-компонент WMLButtonDown и WMMouseMove, используя
компоненту данных Points. Ниже приводится определение функции-компоненты
Paint:

    void TMyWindow::Paint(HDC DC, PAINTSTRUCT&)
    {
      RArrayIterator PointIterator =
                     (RArrayIterator)(Points->initIterator());
      BOOL First = TRUE;

      SelectObject(DC, ThePen);
      while ( int(PointIterator) != 0 )
      {
        RObject AnObject = PointIterator++;
        if ( AnObject != NOOBJECT )
        {
          if ( First )
          {
            MoveTo(DC, ((PTPoint)(&AnObject))->X,
                       ((PTPoint(&AnObject))->Y);
            First = FALSE;
          }
          else
            LineTo(DC, ((PTPoint)(&AnObject))->X,
                       ((PTPoint(&AnObject))->Y);
        }
      }
      delete &PointIterator;
    }
    !!! Это фрагмент программы из файла STEP6.CPP.
    Для осуществления перерисовки главное окно нашей прикладной программы
хранит в компоненте донных Points указатель на TPointArray. При рисовании
пользователем линии, окно получает сообщение WM_MOUSEMOVE, содержащее
координаты указателя "мыши". Вы должны сконструировать объекты TPoint,
содержащие эти координаты, и добавить каждый TPoint в массив Points. Затем,
при необходимости перерисовки окна, вы можете заново нарисовать нужную
линию, соединяя точки TPoint.



    Глава 4. Дабавление меню

    Большинство прикладных Windows программ поддерживают меню в составе
своего главного окна. Меню обеспечивает пользователю разнообразные
возможности выбора, такие как File|Save (Файл|Сохранить), File|Open
(Файл|Открыть) и Help (Помощь). В этой главе вы добавите в вашу прикладную
программу стандартное меню.
    В оконной среде выбор из меню аналогичен нажатию клавиши "мыши": Они
являются событиями, инициализируемыми пользователем. Ответ на выбор из меню
очень напоминает ответ на другие действия пользователя. В этой части мы
опишем следующие обязательные шаги добавления меню в прикладную программу:
    1. Описание меню как ресурса меню.
    2. Загрузка ресурса меню в объект главное окно.
    3. Определение ответа на выбор из меню.
    4. Компиляция прикладной программы и получение исполняемого файла.
    5. Добавление ресурса меню в исполняемый файл.

         Ресурсы меню

    В прикладной программе, реализующей какое-либо меню, обязательно должна
находится секция его спецификации, которая содержит текст пунктов меню и
структуру этих пунктов и их подпунктов. Многие приложения также
поддерживают "горячие" клавиши (называемые акселераторами) для пунктов
меню. Вместо выбора с помощью "мыши" пункта меню Edit|Paste, для
осуществления тех же действий, вы можете просто нажать Shift+Ins ("горячие"
клавиши для этого пункта).
    Меню и их акселераторы не являются частью исходного кода программ на
С++. Они являются частью отдельной спецификации, называемой ресурсом.
Windows хранит ресурсы в компактном и удобном для нее виде.
    Прикладная программа осуществляет к своим прикрепленным ресурсам
заданием идентификатора ресурсов. Этот идентификатор представляет собой
целое число, например 100, или строку, например "MyMenu". Прикладная
программа различает пункты меню по идентификатору меню, присваиваемому
каждому его пункту.
    Для создания ресурса меню используйте редактор ресурсов или RC,
Компилятор Ресурсов командной строки. В файле STEPS.RC содержаться в
исходном формате все необходимые нашей прикладной программе ресурсы меню.
Если вы используете ИСР Borland C++, то вы можете добавить файл STEPS.RC в
проект вашей прикладной программы, и затем ИСР автоматически транслирует
его с использованием RC. Приведем содержимое файла STEPS.RC:

    #include 
    #include 
    #include "steps.h"

    rcinclude inputdia.dlg
    rcinclude filedia.dlg

    COMMANDS MENU LOADONCALL MOVEABLE PURE DISCARDABLE
    BEGIN
      POPUP "&File"
      BEGIN
        MenuItem "&New", CM_FILENEW
        MenuItem "&Open", CM_FILEOPEN
        MenuItem "&Save", CM_FILESAVE
        MenuItem "&Save&As", CM_FILESAVEAS
      END
      MenuItem "&Help", CM_HELP
    END

    а рисунке 4.1 показан внешний вид этого меню (идентификатор ресурсов
"COMMANDS"), включающего пункты HELP (идентификатор меню CM_HELP) и File,
последний с подпунктами New, Open, Save и SaveAs (идентификаторы меню
CM_FILENEW, CM_FILEOPEN, CM_FILESAVE и CM_FILESAVEAS, соответственно).
Пункты меню верхнего уровня имеющего подпункты, как например File, не имеют
своего идентификатора меню, и выбор такого пункта приводит только к показу
его попунктов. Если пункт меню верхнего уровня не имеет собственных
подпунктов, как например Help, то он имеет свой идентификатор меню и может
вызывать выполнение некоторые действий.

    Рисунок 4.1 Прикладная программа с ресурсом меню.

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

         Шаг 7 : Меню для главного окна

    Как отмечалось выше, меню прикладной программы не является отдельным
объектом, владельцем которого является главное окно. Фактически, это вообще
не объект, а просто атрибут главного окна. Он хранится в поле Menu
компоненты данных оконного объекта Attr. В этой компоненте хранятся
атрибуты создания окна. Вы устанавливаете атрибут меню, среди остальных
атрибутов, вызовом AssignMenu в конструкторе вашего окна.
    Ресурс меню, хранимого в файле STEPS.RC, имеет идентификатор ресурса
"COMMANDS". В нашем случае вызов AssignMenu будет выглядеть следующим
образом:
    AssignMenu("COMMANDS");
    Если для ресурса меню вы используете целочисленный идентификатор
ресурсов, то вы можете использовать перегруженную функцию-компоненту
AssignMenu:
    AssignMenu(100);
    иже мы приведем описание конструктора TMyWindow. Заметим, что
конструктор TMyWindow вызывается для осуществления инициализации,
необходимой для всех оконных объектов.

    TMyWindow::TMyWindow(PTWindowsObject AParent, LPSTR ATitle)
      : TWindow(AParent, ATitle)
    {
      AssignMenu("COMMANDS");
      ButtonDown = FALSE;
      PenSize = 1;
      ThePen = CreatePen(PS_SOLID, PenSize, 0);
      Points = new TPointArray(50, 0 , 50);
    }
    !!! Это фрагмент программы из файла STEP7.CPP.
    Теперь при высвечивании главного окна оно имеет меню, показанное на
Рисунке 4.1. Однако, чтобы выбор пунктов меню приводил к осуществлению
некоторых действий, вы должны реализовать оставшиеся шаги.

         Перехват сообщений меню

    Когда пользователь выбирает пункт из оконного меню, или использует
акселератор, то окно получает командное сообщение Windows. Для обработки
посылаемых командных сообщений, в ObjectWindows определяются
функции-компоненты ответа на команду. Для функций-компонент ответа на
команду используется специальное объявление, имеющее вид:
    virtual void CMFileNew(RTMessage Msg) = [CM_FIRST + CM_FILENEW];
, где CM_FIRST является константой, определяемой в ObjectWindows, и
константа, представляющая идентификатор соответствующего пункта меню. В
этом примере это CM_FILENEW, определяемая в OWLRC.H (поэтому вам не надо ее
определять). Однако, если вы определяете ваши собственные пункты меню, как
это будет для Help, вы должны определить и константу, например #define
CM_FIRST 201. Поместите все эти константы идентификаторов ресурсов в файл
заголовка, с тем, чтобы вы могли включать его в файлы .СРР и .RC. Заметим,
что для идентификации функции-компоненты ответа на команду используется
специальное расширение CM_FIRST. е путайте его с расширением WM_FIRST,
используемым для идентификации функций-компонент ответа на сообщения окон.
    Всем функциям-компонентам ответа передается в качестве параметра ссылка
на структуру TMessage (типа RТMessage). Однако, эта структура в них
используется довольно редко. Msg.WParam содержит идентификатор выбранного
пункта меню, но вы его уже знаете. В обычных случаях игнорируется и
Msg.LP.Hi, содержащая 1, если сообщение послано с использованием
акселератора. Msg.LPLo всегда содержит 0.
    Теперь вы можете объявить все функции-компоненты ответа командные
сообщения с использованием констант идентификаторов ресурсов, описанных
выше. Заметим, что CM_HELP определяется в файле STEPS.H, а не в OWLRC.H.

    virtual void CMFileNew(RTMessage Msg) = [CM_FIRST + CM_FILENEW];
    virtual void CMFileOpen(RTMessage Msg) = [CM_FIRST + CM_FILEOPEN];
    virtual void CMFileSave(RTMessage Msg) = [CM_FIRST + CM_FILESAVE];
    virtual void CMFileSaveAs(RTMessage Msg) = [CM_FIRST + CM_FILESAVEAS];
    virtual void CMFileHelp(RTMessage Msg) = [CM_FIRST + CM_FILEHELP];
    !!! Это фрагмент программы из файла STEP7.CPP.

         Ответ на сообщение меню

    Теперь вы имеете для каждого пункта меню функцию-компоненту, которая
вызывается при его выборе. апример, при выборе пункта меню Help вызывается
функция-компонента CMHelp. Пока она будет выводить блок сообщения:

    void TMyWindow::CMHelp(RTMessage)
    {
      MessageBox(HWindow, "Feature not implemented", "Help", MB_OK);
    }
    !!! Это фрагмент программы из файла STEP7.CPP.
    а Рисунке 4.2 показан ответ нашей прикладной программы на выбор пункта
меню Help.

    Рисунок 4.2 Прикладная программа с системой помощи

    а этом этапе вы можете реагировать на выбор пункта меню File|New
очисткой экрана. Для этого добавим следующую функцию-компоненту CMFileNew:

    void TMyWindow::CMFileNew(RTMessage)
    {
      Points->DeleteAll();
      InvalidateRect(HWindow, NULL, TRUE);
    }
    !!! Это фрагмент программы из файла STEP7.CPP.
    Эта функция-компонента удаляет все хранимые точки и перерисовывает
экран. Окно становится пустым, так как ни одной точки не остается.
    Для сообщений CM_OPEN, CM_SAVE и CM_SAVEAS напишите пустые
функции-компоненты, аналогичные CMHelp. Позднее мы переопределим их для
выполнения полезных действий.
    Полная программа реализации действий этого шага находится в файле
STEP7.CPP.

    Глава 5. Поддержка диалога

    Развитые Windows программы могут иметь, ассоциированные с ними,
различные типы оконных объектов (окна, управляющие элементы и диалоги). За
исключением главного окна, каждое окно, называемое дочерним, имеет
единственное родительское (порождающее) окно. Одно родительское окно может
иметь несколько дочерних окон.
    Эта родствееные взаимоотношения между окнами выливаются для каждой
приклажно программы в сообщество связанных родительских и дочерних окон с
главным окном, как первичным родителем. В этом сообществе большинство окон
выступают как дочерние для одного окна, и как родитель для других. Группа
связанных дочерних и родительских окно для нашей прикладной программы,
которая к настоящему моменту реализована только частично, показана на
Рисунке 5.1.
    Существует два типа дочерних окон. Один - это независимые дочерние
окна. Этот тип сам управляет своим внешним видом и местоположением. Блоки
сообщений и всплывающие окна являются представителями этого типа. Другой
тип - это зависимые дочерние окна. Этот тип появляется на поверхности
своего родительского окна и передвигается вместе с ним. Управляющие
элементы, такие как кнопки на блоках сообщений, и некоторые другие типы
дочерних окон являются представителями этого типа. а этом Шаге вы
создадите два типа независимых дочерних окон: всплывающие окна и блоки
диалога.
                  прикладная программа
                    (TMyApplication)
                          ^
                          ѓ
                     главное окно <ЋЋЋЋЋЋЋфайловый диалог
                     (TMyWindow)           (TFileDialog)
                          ^
                          ѓ
 редактируемыйЋЋЋЋЋ> окно помощи  <ЋЋЋЋЋЋЋблок списка
 управляющий         (THelpWindow)         (TListBox)
 элемент               ^       ^
 (TEdit)               ѓ       ЉЋЋЋ‰
                       ѓ           ѓ
             кнопкиЋЋЋЋ™        статичный
            (TButton)           управляющий элемент
                                  (TStatic)

    Рисунок 5.1 Группа связанных родительских и дочерних окон

    Между родительским и дочерним окном существует отношение владения.
апример, в Шаге 8 главное окно владеет окном помощи. Концепция владения в
Windows почти идентична концепции владения, или экземплярной взаимосвязи, в
объектно-ориентированном программировании. Фактически, в этом руководсте
объекты дочернее окно будут обычно хранится в компонентах данных объектов
родительское окно. Хотя это и не является обязательным, это является
хорошим стилем программирования, и приводит к лучшему пониманию и более
простой поддержке программ.

         Шаг 8 : Добавление всплывающего окна

    Давайте добавим к наше программе всплывающее окно, которое появляется
при выборе пункта меню Help. Это окно может служить основой нашей системы
помощи, которая будет реализована в дальнейших Шагах. Здесь мы создадим
всплывающее окно, являющееся экземпляром ТWindow. В Шаге 10 вы создадите
для него новый класс с некоторыми полезными возможностями.
    Поскольку это окно будет "новым" добавляемым окно (отличным от главного
окна), давайте посмотрим, как оконный объекты и элементы окон создаются и
выводятся.

    Создание всплывающего окна

    В Шаге 7 вы реагировали на выбор пункта меню Help выдачей блока
сообщения. а этом шаге, мы заменим его на пустой объект всплывающее окно
HelpWindow. Хотя промежуточные результаты будут не очень интересны с точки
зрения наглядности, вы добавите множество новых возможностей к окну помощи
в Шаге 10.
    Пока только переопределим CMHelp для создания окна помощи:

    void TMyWindow::CMHelp(RTMessage)
    {
      PTWindow HelpWindow;

      HelpWindow = new TWindow(this, "Help system");
      HelpWindow->Attr.Style |= WS_POPUPWINDOW | WS_CAPTION;
      HelpWindow->Attr.X = 100;
      HelpWindow->Attr.Y = 100;
      HelpWindow->Attr.W = 300;
      HelpWindow->Attr.H = 300;
      GetApplication()->MakeWindow(HelpWindow);
    }
    !!! Это фрагмент программы из файла STEP8.CPP.
    Смотри секцию "Инициализация оконных объектов" для более подробной
информации о создании оконных объектов.

    Функция MakeWindow

    Объект TModule определяет очень важную функцию-компоненту MakeWindow.
MakeWindow осуществляет все необходимые действия для безопасного связывания
интерфейсного элемента с оконным объектом. По "безопасным" мы понимаем, что
MakeWindow проверяет на ошибки до возникновения серьезных проблем, подобных
зависанию системы. MakeWindow осуществляет два важных шага.
    1. Проверка на допустимость интерфейсного объекта. Для этого вызыватся
функция ValidWindow, которая проверяет на гарантирование невыхода вашей
прикладной программы за границы памяти, и в противном случае, полностью
отказывается конструировать оконный объект.
    2. Если интерфейсный объект сконструирован успешно, то MakeWindow
пытается создать и ассоциировать элемент окна, путем вызова
функции-компоненты объекта Create. Create использует информацию в
компонентах данных объекта для уведомления Windows о типе создаваемого
интерфейсного элемента. Если элемент не может быть создан, то выдается блок
сообщения об ошибке.
    Если или проверка на допустимость, или создание интерфейсного элемента,
закончились неудачно, то MakeWindow удаляет недопустимый оконный объект и
возвращает NULL. В противном случае, возвращается указатель на интерфейсный
объект, передаваемый ей как параметр.
    Вызов MakeWindow является наиболее безопасным способом создания
интерфейсных элементов. Вызов Create напрямую, без проверки расхода памяти и
отслеживания ошибок, является опасным, и не рекомендуется.

         Добавление блока диалога

    Блок диалога подобен вплывающему окну, но обычно он остается на экране
в течении короткого промежутка времени, и предназначен для осуществления
одной определенной задачи ввода, как например выбор принтера или установка
страницы документа. Здесь мы добавим в нашу прикладную программу блок
диалога для открытия и сохранения файлов.
    Как и всплывающее окно, блок диалога является независимым дочерним
окном. Концептуально, добавление блока диалога совпадает с описанным ранее
добавлением всплывающего окна. Блок диалога подобен окну, но имеет
некоторые существенные различия:
    - Классы диалога происходят от класса TDialog, а не от ТWindow. Оба
класса TDialog и ТWindow происходят от TWindowsObject.
    - Обычно блоки диалога требуют наличия ресурсов, описывающих их размер,
местоположение и внешний вид.
    - Блоки диалога обычно выполняют конкретную небольшую задачу и
возвращают значение. апример, блок сообщения CanClose, созданный на Шаге
2, является ограниченным типом диалога, возвращающего ответ пользователя
"да" или "нет". Посмотрите также на блок диалога, запрашивающий тлщину
линии в Шаге 5.
    Вы добавите один из реализованных в ObjectWindows блоков диалога, а
именно, блок файлового диалога. Он определяется классом TFileDialog,
являющимся производным от TDialog. Блок файлового диалога является полезным
в ситуациях, когда вы запрашиваете пользователя о выборе сохраняемого или
загружаемого дискового файла. апример, текстовые процессоры используют
подобный диалог для задания открываемого или сохраняемого документа. Вы
будете выдавать блок файлового диалога в ответ на выбор опций меню
File|Open или File|SaveAs. Блок файлового диалога заменит блок сообщения
"Feature not implemented" ("Средство не реализовано"). а Шаге 9 вы
заполните его реальными файлами и сможете сохранять и открывать их для
хранения и перезаписи реальных данных. Пока реализует только показ блоков
диалога. а Рисунке 5.2 показан внешний вид блока файлового диалога.

    Рисунок 5.2 Прикладная программа с блоком файлового диалога.

    Добавление компоненты данных

    Вместо хранения целого объекта блока файлового диалога как компоненты
данных его родительского окна, каждый раз конструируется новый объект блока
файлового диалога. Вы будете хранить только данные, которые вы будете
использовать с блоком файлового диалога: имя файла. Это удобный прием.
Вместо хранения всех объектов, хотя возможно они вам и не понадобятся,
просто хранятся данные, которые являются необходимыми при инициализации
соответствующего объекта.
    Для конструирования блока файлового диалога необходимы три параметра:
родительское окно, идентификатов ресурса и имя файла. Передаваемый
идентификатор ресурсов указывает на стандартный тип файлового диалога:
"открытия" или "закрытия" (SD_FILEOPEN или SD_FILESAVE). Имя выбранного
файла будет возвращено в передаваемый параметр имени файла. Этот параметр
также используется для передачи имени файла по умолчанию для файловых
диалогов сохранения, и для передачи шаблона файлов по умолчанию для
файловых диалогов открытия.
    Теперь описание TMyWindow выглядит примерно так:

    class TMyWindow : public TWindow
    {
    ...
      char FileName[MAXPATH];
    ...
    };

    Инициализация блока диалога

    В зависимости от передаваемого конструктору TFileDialog идентификатора
ресурса, блок диалога может поддерживать открытие или сохранение файлов.
Каждая опция реализует блок диалога аналогичный, показанному на Рисунке
5.2. Между диалогами открытия и сохранения файлов существует два различия:
    1. Диалог открытия файла имеет кнопки ОК и Cancel (Отменить), а диалог
сохранения файла кнопки Save и Cancel.
    2. Диалог сохранения файла первоначально показывает текущее имя файла в
области редактирования комбинированного блока диалога.
    Приведем измененный текст функицй-компонент СMFileOpen и CMFileSaveAs:

    void TMyWindow::CMFileOpen(RTMessage)
    {
      if ( GetApplication()->ExecDialog(new TFileDialog(this,
           SD_FILEOPEN, strcpy(FileName, "*.PTS"))) == IDOK )
        MessageBox(HWindow, FileName, "Open the file:", MB_OK);
    }

    void TMyWindow::CMFileSaveAs(RTMessage)
      if ( GetApplication()->ExecDialog(new TFileDialog(this,
           SD_FILESAVE, FileName)) == IDOK)
        MessageBox(HWindow, FileName, "Save the file:", MB_OK);
    }
    !!! Это фрагмент программы из файла STEP8.CPP.
    Полный текст нашей прикладной программы на Шаге 8 приводится в файле
STEP8.CPP.
    Как вы использовали TApplication::MakeWindow для "безопасного" создания
интерфейсных элементов, так используйте TApplication::ExecDialog для
безопасного создания и исполнения блоков диалога. Подобно MakeWindow,
ExecDialog вызывает ValidWindow для получения гарантий, что конструирование
объекта блока диалога прошло успешно. И если это так, то ExecDialog
вызывает функцию-компоненту объекта блока диалога Execute. После отработки
блока диалога и если не возникло ошибок, ExecDialog возвращает значение,
возвращенное Execute. Если же произошла ошибка, то возвращается IDCANCEL, и
ваша программа информируется как если бы пользователь отконсолил блок
диалога. В любом из этих случаев, ExecDialog удаляет объект блока диалога
из памяти.
    Использование ExecDialog является самым безопасным способом
инициализации блоков диалога. Вы можете вызвать Execute напрямую, но это
сопряжено с определенным риском и может завесить вашу систему.

         Шаг 9 : Сохранение графической информации в файле

    Теперь, когда TMyWindow имеет представление его текущей линии в виде
данных (компонента данных Points), вы можете поместить эти данные в файл
(фактически, в файловый поток) и считать их обратно. В этом Шаге вы
добавите компоненту данных для хранения информации о статусе, и
модифицируете функции-компоненты для открытия и сохранения файла .PTS.

    Отслеживание статуса

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

    class TMyWindow : public TWindow
    {
    ...
       BOOL IsDirty, IsNewFilw;
    ...
    };

    IsDirty принимает значение TRUE, если текущее изображение является
"запачканным". ("Запачканное" означает, что его необходимо сохранить, из-за
внесенных в него изменений с момента последнего сохранения, или из-за того,
что оно никогда не сохранялось). Когда пользователь начинает рисование
(WMLButtonDown), вы должны установить IsDirty в TRUE. Когда пользователь
открывает новый файл, или сохраняет в существующий, вы должны установить
IsDirty в FALSE. При закрытии пользователем прикладной программы
(CanClose), вы должны проверить статус IsDirty и вывести блок сообщения
только в случае, когда она равна TRUE.
    IsNewFile принимает значение TRUE только в случае, когда окно выведено
впервые и сразу после выбора пользователем меню File | New (CMFileNew). Она
устанавливается в FALSE всякий раз, когда файл открыт (CMFileOpen) или
сохранен (CMFileSave или CMFileSaveAs). CMFileSave использует IsNewFile для
определения, может ли файл быть сохранен немедленно (FALSE), или, нужно ли
пользователю выбирать имя файла из файлового диалога (TRUE).
    Ниже приводятся описания функции-компоненты CanClose и
функций-компонент сохранения и загрузки файлов. К этому моменту она делают
все, за исключением загрузки и сохранения файлов. Возможности сохранения
файлов реализуются в новой функции-компоненте SaveFile, а возможности
открытия - в OpenFile.

    BOOL TMyWindow::CanClose()
    {
      if ( !IsDirty )
        return TRUE;
      return MessageBox(HWindow, "Do you want to save?", "Drawing has
                        changed", MB_YESNO | MB_ICONQUESTION ) == IDNO;
    }

    void TMyWindow::CMFileNew(RTMessage)
    {
      Points->DeleteAll();
      InvalidateRect(HWindow, NULL, TRUE);
      IsDirty = FALSE;
      IsNewFile = TRUE;
    }

    void TMyWindow::CMFileOpen(RTMessage)
    {
      if ( GetApplication()->ExecDialog(new TFileDialog(this,
           SD_FILEOPEN, strcpy(FileName, "*.PTS"))) == IDOK )
        OpenFile();
    }

    void TMyWindow::CMFileSave(RTMessage)
    {
      if ( IsNewFile )
        SaveFileAs();
      else SaveFile();
    }

    void TMyWindow::CMFileSaveAs()
      if ( IsNewFile )
        strcpy(FileName, "");
      if ( GetApplication()->ExecDialog(new TFileDialog(this,
           SD_FILESAVE, FileName)) == IDOK)
        SaveFile();

    void TMyWindow::CMFileSaveAs(RTMessage)
    {
      SaveFileAs();
    }

    void TMyWindow::SaveFile()
    {
      ofpstream os(FileName);

      os << Points;
      os.close();
      IsNewFile = IsDirty = FALSE;
    }

    void TMyWindow::OpenFile()
    {
      ifpstream is(FileName);
      if ( is.bad() )
        MessageBox(HWindow, "Unable to open file", "File Error", MB_OK |
                   MB_ICONEXCLAMATION);
      else
      {
        Points->DeleteAll();
        is >> Points;
        is.close();
        IsNewFile = IsDirty = FALSE;
        InvalidateRect(HWindow, NULL, 1);
      }
    }
    !!! Это фрагмент программы из файла STEP8.CPP.

    Открытие и сохранение файлов

    Вы создали средство для открытия и сохранения файлов .PTS. Но чтение и
запись массива Points остается той работой, которая делается по старому. Вы
можете выполнить эту работу быстрее, если сделаете Points потоковым
массивом.
    Первый шаг, это множественное наследование от Array и TStreamable.

    _CLASSDEF(TPointArray)
    class TPointArray : public Array, public TStreamable
    {
    public:
      TPointArray(int upper, int lower = 0, sizeType aDelta = 0)
        : Array(upper, lower, aDelta){};
    ...
    protected:
      virtual void write( Ropstream );
      virtual Pvoid read( Ripstream);
    ...
    };
    !!! Это фрагмент программы из файла STEP9.CPP.
    Функции-компоненты write и read класса TPointArray вызываются при
выдаче запроса на чтение или запись TPointArray. Эти функции-компоненты,
приведенные ниже, осуществляют действительное чтение и запись точек в
TPointArray:

    Pvoid TMyWindow::read(Ripstream is)
    {
      TWindow::read(is);
      is >> Points;
      return this;
    }

    void TMyWindow::write(Ropstream os)
    {
      TWindow::write(os);
      os << Points;
    }
    !!! Это фрагмент программы из файла STEP9.CPP.
    Различные другие функции-компоненты должны определятся до того, как
TPointArray будет подлинно потоковым. Оставшиеся методы можно найти в файле
STEP9.CPP, находящемся в подкаталоге \EXEMPLES\STEPS. Вы рассмотрим
подробно все потоковые функции-компоненты в Главе 15. На данном этапе нам
достаточно понять только схему этих методов.

         Глава 6. Всплывающие окна

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

         Шаг 10 : Вывод всплывающего окна помощи

    На Шаге 8 вы добавили в главное окно нашей прикладной программы меню
Help. Выбор этого меню приводит к выдаче пустого окна, являющегося
экземпляром TWindow. На этом Шаге вы замените пустое окно на полезное
THelpWindow, которое дает информацию об использовании управляющих элементов
Windows, поддерживаемых в ObjectWindows: блоков списков, линеек прокрутки,
редактируемых управляющих средств, статичных управляющих средств, кнопок и
комбинированных блоков. В процессе создания, вы определите систему помощи в
отдельном модуле. При этом, вы внесете лишь незначительные изменения в
тексты существующих модулей. На Рисунке 6.1 показана полученная система
помощи, которая предоставляет короткие описания некоторых управляющих
элементов, поддерживаемых ObjectWindows.

    Рисунок 6.1 Система помощи созданной нами прикладной программы

         Использование модулей с ObjectWindows

    Модуль является удобным механизмом накопления определений
функций-компонент класса. Это особенно важно при программировании в
Windows, где вы часто предполагаете передачу окон (оконных объектов в
ObjectWindows) от одного приложения к другому. Здесь вы построите систему
помощи как класс THelpWindow и сохраните ее определение в файле
HELPWIND.CPP.

    Модификация главной программы

    Наша прикладная программа нуждается только в двух изменениях:
    - добавление модуля HELPWIND.CPP в файл проекта приложения или
make-файл;
    - перемещение всех команд установки атрибутов в HELPWIND.CPP из
TMyWindow::CMHelp. Они будут находится теперь в конструкторе THelpWindow.
    Теперь, при выборе пользователем пункта меню Help, наша прикладная
программа отвечает созданием THelpWindow. Это означает, что вы должны
определить класс THelpWindow. Вы это сделаете в модуле HELPWIND.CPP.

    Создание модуля

    Модуль HelpWind осуществляет только определение интерфейса и реализации
класса THelpWindow. Созданные один раз, этот модуль может использоваться
другими прикладными программами ObjectWindows. Это, по сути дела, окно в
модуле.Ниже приводится текст модуля HelpWind (пробелы будут заполнены
позже):

    #ifndef __HELPWIND_H
    #define __HELPWIND_H

    #ifndef __OWL_H
    #include 
    #endif

    #ifndef __LISTBOX_H
    #include 
    #endif

    #ifndef __EDIT_H
    #include 
    #endif

    #define ID_LISTBOX 101
    #define ID_BUTTON1 102
    #define ID_BUTTON2 103
    #define ID_EDIT 104

    _CLASSDEF(THelpWindow)
    class THelpWindow : public TWindow
    {
    public:
      PTListBox ListBox;
      PTEdit Edit;
      THelpWindow(PTWindowsObject AParent);
      virtual void SetupWindow();
      virtual void HandleListBoxMsg(RTMessage Msg) =
         [ID_FIRST + ID_LISTBOX];
      virtual void HandleButton1Msg(RTMessage Msg) =
         [ID_FIRST + ID_BUTTON1];
      virtual void HandleButton2Msg(RTMessage Msg) =
         [ID_FIRST + ID_BUTTON2];
      virtual void FillEdit(Pchar SelString);
    };
    #endif
    !!! Это фрагмент программы из файла HELPWIND.H

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include "helpwind.h"

    THelpWindow::THelpWindow(PTWindowsObject AParent) :
       TWindow(AParent, "Help System")
    {
    ...
    }
    void THelpWindow::SetupWindow()
    {
    ...
    }
    void THelpWindow::HandleListBoxMsg(RTMessage Msg)
    {
    ...
    }
    void THelpWindow::HandleButton1Msg(RTMessage)
    {
    ...
    }
    void THelpWindow::HandleButton2Msg(RTMessage)
    {
    ...
    }
    void THelpWindow::FillEdit(Pchar SelString)
    {
    ...
    }

    !!! Это фрагмент программы из файла HELPWIND.CPP

         Добавление управляющих элементов к окну

    На Шаге 8 вы узнали два типа дочерних окон: зависимые и независимые. Вы
также добавили к главному окну эти два типа окон: диалоги и всплывающие
окна. Теперь вы добавите управляющие элементы, которые относятся к типу
зависимых дочерних окон. Эти управляющие элементы будут иметь окно помощи в
качестве родительского окна. Помним, что окно помощи является независимым
дочерним окном, родительским окном для которого является главное окно вашей
прикладной программы. Поэтому, окно помощи будет одновременно дочерним и
родительским окном.

    Что такое управляющие элементы

    Управляющие элементы представляют собой видимые устройства, занимающие
часть или весь пользовательский интерфейс окна или блока диалога. Например,
кнопки ОК или Cancel на блоках файлового диалога являются управляющими
элементами, как и блоки списков, используемые для показа файлов. Главное
окна нашей прикладной программы не имеет управляющих элементов, но вы
добавите некоторые из них к окну помощи.
    Существует много типов управляющих элементов, включающие блоки списков,
линейки прокрутки, редактируемые управляющие элементы, статичные
управляющие элементы, кнопки и комбинированные блоки. Ваше окно помощи,
показанное на Рисунке 6.1, содержит блок списка (большой блок в верхней
половине окна), редактируемый управляющий элемент (большой блок в нижней
половине окна), статичный управляющий элемент (текст "Help Information:") и
две кнопки (Help и Cancel). Полосы прокрутки являются частью редактируемого
управляющего элемента и блока списка, но не являются объектами полосы
прорутки. Полосы прокрутки наиболее часто используются как необязательная
часть других управляющих элементов и окон, как самостоятельное средство оно
используется крайне редко.
    Класс ObjectWindows TControl реализует характеристики, являющиеся
общими для всех управляющих оконных объектов. Производные от него классы
определяют характеристики, являющиеся уникальными для типа управляющих
окон, которые данные экземпляры представляют. Например, TEdit и TListBox
определяют поведение, характерное для редактируемых управляющих элементов и
блоков списков, соответственно. Вы должны помнить, что TControl происходит
от TWindow. Управляющие элементы, в действительности, являются просто
специализированными окнами.

    Создание управляющих элементов окна

    Хотя управляющие элементы ведут себя одинаково, но при
программировании, существует важное отличие между управляющими элементами в
блоках диалога, например в файловом диалоге, и управляющих элементах в
окнах, например окне помощи. Вы задаете характеристики управляющих элементов
диалога в определении ресурсов диалога. Windows создает управляющие
элементы диалога используя определение ресурсов; их не обязательно
связывать с объектами ObjectWindows. Когда управляющие элементы диалога не
связаны с объектами ObjectWindows, диалог должен управлять ими
непосредственно через вызовы функций Windows.
    !!! Вы можете связать управляющий элемент, определенный в файле
ресурсов, с объектом ObjectWindows. Смотри Главу 11 "Связывание управляющих
объектов".
    Управляющие элементы окна, однако, всегда представляются объектами
ObjectWindows. Родительское окно манипулирует своими управляющими
элементами посредством богатого набора функций-компонет, определяемых
управляющими объектами ObjectWindows. Например, для получения текстовой
информации, которую пользователь выбрал из блока списка, вызывается
функция-компонента объекта блок списка GetSelString. Подобно оконным или
диалоговым объектам, управляющий объект имеет соответствующий ему видимый
элемент. Управляющий объект и его управляющие элементы связываются
посредством компоненты данных управляющего объекта HWindow, как и окна и
диалоги. Каждый управляющий элемент имеет уникальный идентификатор, который
используется родительским окном для его идентификации при обработке событий
управляющих элементов, таких как нажатии пользователем кнопки. Для ясности,
вы должны определить константы, соответствующие каждому идентификатору
управляющих элементов:

    #define ID_LISTBOX 101   // идентификатор для 1-го блока списка
    #define ID_BUTTON1 102   // идентификатор для 1-й кнопки (ОК)
    #define ID_BUTTON2 103   // идентификатор для 2-й кнопки (Cancel)
    #define ID_EDIT 104      // идентификатор для редактир.управ.эл-та

    Упраляющие объекты как компоненты данных

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

    _CLASSDEF(THelpWindow)
    class THelpWindow : public TWindow
    {
    public:
      PTListBox ListBox;
      PTEdit Edit;
      ...
    };
    !!! Это фрагмент программы из файла HELPWIND.H
    Как только управляющие объекты сконструированы, вы можете обрабатывать
их с помощью функций-компонент. Например, вызовом ListBox->AddString вы
добавляете строку к списку в ListBox. Вы можете получить доступ к
управляющим объектам для дочерних окон которые не хранятся как компоненты
данных с помощью функций-компонент Next и Previous из TWindowsObject. Но
более удобным, будет использование для этого компонент данных.

    Организация работы управляющих элементов

    Любой оконный класс, имеющий управляющие объекты, должен определять
конструктор, создающий эти управляющие объекты. Он должен переопределять
также SetupWindow, которая наследуется из ТWindow, и служит для начальной
установки его управляющих объектов. THelpWindow переопределяет SetupWindow
для начальной установки списка, и устанавливает выбор его дочернего блока
списка. Заметим, что THelpWindow::SetupWindow вначале вызывает
TWindow::SetupWindow, она вызывает TWindowsObject::SetupWindow, она, в свою
очередь, вызывает TWindowsObject::SetupWindow, которая и создает окна,
конструируемые как дочерние окна THelpWindow.
    Ниже приводится определение конструктора окна помощи. Первым шагом
конструктор вызывает DisableAutoCreate, функцию, которую вы возможно хотели
бы вызвать в конструкторе ваших собственных всплывающих окно. Она
устанавливает флаг, указывающий, что создание всплывающего окна не связано
с созданием его родительского окна. Такое поведение обычно не свойственно
всплывающим окнам.
    После этого, конструктор THelpWindow устанавливает атрибуты своего
местоположения и размеров. Так как THelpWindow является функционально не
полным без своих дочерних окон, поэтому конструирование THelpWindow не
является законченным до окончания конструирования его дочерних окон.
THelpWindow передает this как первый параметр конструктора каждого его
управляющего элемента. (this является указателем на конструируемый объект
THelpWindow). Вторым параметром конструктора каждого управляющего элемента
является идентификатор этого управляющего элемента. Дополнительные
параметры задает его местоположение и размеры. Заметим, что Listbox и Edit
зарезервированы для последующего использования.

    THelpWindow::THelpWindow(PTWindowsObject AParent)
    {
      DisableAutoCreate();
      Attr.Style |= WS_POPUPWINDOW | WS_CAPTION;
      Attr.X = 100;
      Attr.Y = 100;
      Attr.W = 300;
      Attr.H = 300;
      ListBox = new TListBox(this, ID_LISTBOX, 20, 20, 180, 80);
      new TButton(this, ID_BUTTON1, "Help", 220, 20, 60, 30, TRUE);
      new TButton(this, ID_BUTTON2, "Cancel", 220, 70, 60, 30, FALSE);
      Edit = new TEdit(this, ID_EDIT, "", 20, 180, 260, 90, 40, TRUE);
      new TStatic(this, -1, "Help Information:", 20, 160, 160, 20, 0);
    }
    !!! Это фрагмент программы из файла HELPWIND.CPP
    После создания окна помощи, для выполнения начальных установок окна
помощи вызывается THelpWindow::SetupWindow. Установка производится путем
создания его управляющих элементов и инициализации блока списка.
    В начале, THelpWindow::SetupWindow вызывает TWindow::SetupWindow,
которая создает дочерние окна окна помощи. Затем, она вызывает
функции-компоненты ListBox для инициализации и выбора списка. (Управляющие
элементы при создании выводятся автоматически, так как по умолчанию
установлен стиль создания WS_VISIBLE).

    void THelpWindow::SetupWindow()
    {
      TWindow::SetupWindow();
      ListBox->AddString("List Boxes");
      ListBox->AddString("Buttons");
      ListBox->AddString("Check Boxes");
      ListBox->AddString("Radio Buttons");
      ListBox->AddString("Group Boxes");
      ListBox->AddString("Scroll Bars");
      ListBox->AddString("Edit Controls");
      ListBox->AddString("Static Controls");
      ListBox->AddString("Combo Boxes");
      ListBox->SetSelIndex(0);
    };
    !!! Это фрагмент программы из файла HELPWIND.CPP
    Вызов конструктора THelpWindow и функции-компоненты SetupWindow
является достаточным для правильного вывода управляющих элементов в окно
помощи. Блок списка будет прокручиваться и кнопки будут нажиматься, но
действий никаких происходить не будет. В следующем разделе вы определите
реакцию вашей программы на события управляющих элементов.

    Реакция на события управляющих элементов

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

    _CLASSDEF(THelpWindow)
    class THelpWindow : public TWindow
    {
    public:
    ...
      virtual void HandleListBoxMsg(RTMessage Msg) =
         [ID_FIRST + ID_LISTBOX];
    ...
    };
    !!! Это фрагмент программы из файла HELPWIND.CPP
    ID_FIRST является константой, определяемой в ObjectWindows. В этом
примере, ID_LISTBOX является идентификатором дочернего управляющего
элемента блок списка родительского окна TMyWindow. HandleListBoxMsg
определяет ответы на все сообщения, посылаемые TMyWindow от
управляющего элемента блок списка.
    Управляющее средство посылает сообщение своему родителю всякий раз,
при возникновении соответствующего события. Эти события зависят от типа
дочернего управляющего элемента. Код информационного сообщения извещает
родительское окно о типе возникшего события. Например, блоки списков
посылают сообщение LBN_SELCHANGE при выборе изменений, а кнопки сообщение
BN_CLICKED при нажатии.
    Подобно функциям-компонентам ответа на команды, функциям-компонентам
ответа на управляющие события передается параметр Msg типа RTMessage.
Msg.LP.Hi содержит код типа возникшего события. Msg.LP.Lo содержит
дескриптор окна управляющего элемента. Msg.WParam содержит идентификатор
управляющего элемента.
    Список сообщений, передаваемых с сообщения управляющих элементов
приводится в книге "ObjectWindows для С++. Справочное руководство.". Для
наглядности назовем нашу функцию-компоненту ответа на управляющие события
HandleListBoxMsg. Ниже приводится определение класса THelpWindow,
показывающее портотипы функций-компонент:

    _CLASSDEF(THelpWindow)
    class THelpWindow : public TWindow
    {
    public:
      PTListBox ListBox;
      PTEdit Edit;
      THelpWindow(PTWindowsObject AParent);
      virtual void SetupWindow();
      virtual void HandleListBoxMsg(RTMessage Msg) =
         [ID_FIRST + ID_LISTBOX];
      virtual void HandleButton1Msg(RTMessage Msg) =
         [ID_FIRST + ID_BUTTON1];
      virtual void HandleButton2Msg(RTMessage Msg) =
         [ID_FIRST + ID_BUTTON2];
      virtual void FillEdit(Pchar SelString);
    };
    !!! Это фрагмент программы из файла HELPWIND.H
    Теперь надо решить, как THelpWindow должна реагировать на сообщения от
своих управляющих элементов. При нажатии кнопки Cancel (ID_BUTTON2) окно
должно закрываться. Реализации HandleButton2Msg будет выглядеть совсем
просто:
    void THelpWindow::HandleButton2Msg(RTMessage Msg)
    {
      CloseWindow();
    }
    Кнопки посылают только два типа сообщений, BN_CLICKED и BN_DBLCLICKED.
В нашем случае нет необходимости проверять код полученного сообщения, так
как окно должно закрываться в любом случае.
    При нажатии кнопки ОК (ID_BUTTON1) вы должны проверить какой пункт
блока списка выбран, и заполнить редактируемый управляющий элемент
(ID_EDIT) соответствующим текстом. Поэтому, HandleButton1Msg вызывает для
блока списка (ID_LISTBOX) GetSelString, которая выдает текст выбранного
пункта списка. Этот текст передается в определенную вами функцию-компоненту
FillEdit. Определение FillEdit находится в файле HELPWIND.CPP.

    void THelpWindow::HandleButton1Msg(RTMessage Msg) {
      char SelString[25];

      ListBox->GetSelString(SelString, sizeof (SelString));
      FillEdit(SelString);
    }

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

    void THelpWindow::HandleListBoxMsg(RTMessage Msg) {
      char SelString[25];

      if (Msg.LP.Hi == LBN_DBLCLK)
      {
        ListBox->GetSelString(SelString, sizeof (SelString));
        FillEdit(SelString);
      }
      else DefWndProc(Msg);
    }

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

    ЧАСТЬ 2. ИСПОЛЬЗОВАНИЕ ObjectWindows
    Глава 7. Обзор ObjectWindows

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

         Соглашения ObjectWindows

    Многие классы и структуры в иерархии ObjectWindows имеют имена,
начинающиеся с буквы Т (например TWindow). Для всех всех классов с Т
ObjectWindows так же определяет следующие связанные типы:
    - Pclass, тип указатель на класс class. (Например, PTWindow
определяется как указатель на ТWindow).
    - Rclass, тип ссылки на класс class. (Например, RTWindow определяется
как ссылка на TWindow).
    - RРclass, ссылка на указатель на класс class. (Например, RРTWindow
определяется как ссылка на указатель на TWindow).
    - РСclass, тип указатель на константу const классa class. (Например,
PCTWindow определяется как указатель на const TWindow).
    - RClass, тип ссылки на константу const классa class. (Например,
RCTWindow определяется как ссылка на const TWindow).
    Мы включили эти типы для облегчения некоторых сложных процедур
программирования в Windows. Например, в противном случае динамически
связываемые библиотеки в Windows (DLL) требовали бы объявлений, подобных
    TWindow _FAR *           !!! Это эквивалентно РТWindow.
, где требовался бы указатель на ТWindow, или
    TWindow _FAR * _FAR &    !!! Это эквивалентно RРТWindow.
для ссылки на указатель на ТWindow.
    Использование типов P, R, RP, PC и RC облегчает преобразование вашей
прикладной программы, если позднее вы решите сделать ее частью DLL. Мы
широко используем эти типы в примерах программ ObjectWindows.
    Функции-компоненты ответа на сообщения, по принятому соглашению,
именуются именами сообщений, на которые они отвечают, но без символов
подчеркивания. Например, функция-компонента, отвечающая на сообщение
WM_KEYDOWN, будет иметь имя WMKeyDown, а функция, отвечающая на сообщение
CM_FILEOPEN, CMFileOpen.

         Иерархия ObjectWindows

    ObjectWindows представляет собой библиотеку, состоящую из иерархии
классов, которые вы можете использовать, модифицировать или добавлять. В
Главе 1 книги "ObjectWindows для С++. Справочное руководство." вы найдете
полное описание классов, их компонент данных и функций-компонент.

    Рисунок 7.1 Иерархия классов ObjectWindows

    Object                                          TStreamable
       ^                                               ^
   љЋЋЋ‹ЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋ‰   љЋЋЋЋЋЋЋЋЋ‹ЋЋЋЋ‰
   ѓ                                     ѓ   ѓ              ѓ
 TModule        TWindowsObjectЋ‰      TScroller             ѓ
   ^                       ^   ѓ                            ѓ
   ѓ              љЋЋЋЋЋЋЋЋ„   ЉЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋ™
 TApplication     ѓ        ѓ
                TDialog   TWindow
                   ^          ^
                   ѓ      љЋЋЋ‹ЋЋЋЋЋЊЋЋЋЋЋЋЋЋЋЋЋЋЋЋЊЋЋЋЋЋЋЋЋЋЋЋЋ‰
    TFileDialogЋЋЋЋ„   TControl   TMDIFrame   TEditWindow   TMDIClient
                   ѓ    ^                          ^
    TInputDialogЋЋЋ„    ѓ                          ѓ
                   ѓ    ЌЋЋTScrollBar         TFileWindow
    TSearchDialogЋЋ™    ѓ
                        ѓ
                        ЌЋЋTStatic
                        ѓ    ^
                        ѓ    ѓ
                        ѓ  TEdit
                        ѓ
                        ЌЋЋTListBox
                        ѓ    ^
                        ѓ    ѓ
                        ѓ  TComboBox
                        ѓ
                        ЌЋЋTGroupBox
                        ѓ
                        ЉЋЋTButton
                             ^
                             ѓ
                           TCheckBox
                             ^
                             ѓ
                           TRadioButton

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

    TModule
    Динамически связываемые библиотеки ObjectWindows (DLL) конструируют
экземпляр TModule, который выступает как объектно-ориентированная основа
для библиотечного модуля (DLL). Приложения ObjectWindows конструируют
экземпляр TApplication, являющийся производным от TModule. TModule
определяет общие характеристики обеих модулей: библиотеки и приложения.
Функции-компоненты TModule обеспечивают поддержку управления оконной
памятью и обработку ошибок.

    TApplication
    Этот класс определяет характеристики, обязательные для всех приложений
ObjectWindows. Каждое приложение ObjectWindows будет производить из
TApplication свой собственный класс прикладная программа. Он отвечает,
помимо других вещей, за инициализацию объекта главное окно. Объекты
прикладная программа детально описываются в Главе 8 "Модули и объекты
прикладная программа".

    Интерфейсные объекты

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

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

    Оконные объекты

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

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

    TEditWindow
    Производный от ТWindow, TEditWindow определяет класс, который позволяет
редактировать текст внутри окна.

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

    Диалоговые объекты

    Диалоговые объекты служат для описания интерактивных групп, в
особенности, групп управляющих элементов, таких как кнопок, блоков списков и
линеек прокрутки. Диалоговые объекты детально описываются в Главе 11
"Диалоговые объекты".

    TDialog
    Этот класс является базовым для производных классов, управляющих
блоками диалога Windows. Диалоговые объекты связываются с ресурсами
диалога, и могут запускаться в модальных или немодальных диалоговых
блоках. Функции-компоненты обеспечивают взаимосвязь между диалогом и
его управляющими элементами.
    !!! Модальные и немодальные диалоговые блоки описываются ниже.

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

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

    Управляющие объекты

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

    TControl
    TControl является абстрактным классом, который выступает как общий
базовый класс для всех управляющих объектов, включая блоки списков и
кнопки. Он определяет функции-компоненты, создающие управляющие элементы и
обрабатывающие сообщения для его производных классов.
    !!! Смотри Рисунок 12.1 с примерами некоторых управляющих элементов.

    TButton
    Экземпляры TButton представляют кнопки Windows.

    TCheckBox
    Производный от TButton, экземпляры TCheckBox представляют блоки
проверки Windows. Функции-компоненты этого класса управляют их состоянием.

    TRadioButton
    Производный от TCheckBox, экземпляры TRadioButton управляют созданием и
функционированием селективных кнопок Windows.

    TListBox
    Экземпляры TListBox представляют блоки списков Windows. Этот класс
управляет созданием и выбором из блоков списков Windows, и определяет
функции-компоненты для манипуляции их пунктами.

    TComboBox
    Производный от TListBox, TComboBox определяет поведение комбинированных
блоков Windows. Комбинированный блок представляет собой связанные между
собой блок списка и редактируемый управляющий элемент.

    TGroupBox
    Экземпляры TGroupBox представляют групповые блоки Windows.

    TStatic
    TStatic предоставляет функции-компоненты, которые устанавливают, выдают
и очищают текст статического (только для вывода) управляющего элемента.

    TEdit
    Производный от TStatic, TEdit поддерживает расширенные возможности
обработки текста для редактируемых управляющих элементов Windows.

    TScrollBar
    TScrollBar определяет функции-компоненты, которые управляют размерами и
расположением указателя стандартной полосы прокрутки.

    Объекты MDI

    Windows реализует стандарт управления несколькими окнами внутри одного
окна. Этот стандарт называется Мультидокументальный Интерфейс (MDI).
ObjectWindows предоставляет средства установки и управления окнами MDI. MDI
объекты подробно описываются в Главе 14 "MDI объекты".

    TMDIFrame
    TMDIFrame обеспечивает правильное функционирование окон для главного
окна прикладной программы в соответствии со спецификацией MDI Windows.

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

    Прокручивающиеся объекты

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

         Функции Windows API.

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

    ObjectWindows вызывает функции Windows

    Многие функции-компоненты ObjectWindows вызывают функции Windows. Но
ObjectWindows не дублирует их; она только перераспределяет некоторые
функции Windows на основе объектно-ориентированного подхода. И в
дополнении, ObjectWindows сильно упрощает задачу задания многочисленных
параметров, требуемых функциям Windows. Чаще всего ObjectWindows
автоматически задает параметры, такие как дескрипторы окон и идентификаторы
дочерних окон, сохраняя их как компоненты данных в экземплярах объектов.
    Например, многие функции Windows требуют задания дескриптора окна над
которым они будут производить манипуляции. Эти функции обычно вызываются из
функций-компонент оконного объекта. Объект хранит дескриптор связанного с
ним окна в компоненте данных HWindow, поэтому она может передаваться в
качестве параметра, освобождая вас от необходимости задавать этот его
каждый раз. Дескриптор окна (и многая другая требуемая Windows информация)
инкапсулируется объектом. Поэтому, классы ObjectWindows выступают как
объектно-ориентированный интерфейс с не объектно-ориентированным Windows
API.

    Доступ к функциям Windows

    Все функции Windows API доступны в программах ObjectWindows. (Смотри
Главу 6 "Функции Windows" в книге "ObjectWindows для С++. Справочное
руководство." или в файле WINDOWS.H, где приводится список этих функций).
Например, этот оператор вызывает функцию Windows MessageBox для вывода
блока сообщения:

    Reply = MessageBox(HWindow, "Do you want to save?",
                       "File has changed", MB_YESNO | MB_ICONQUESTION);

    Многие функции Windows имеют важные возвращаемые значения. В случае с
MessageBox, возвращаемым значением является целое число, которое
сохраняется в Reply. Значение этого целого числа указывает на действие,
которое произвел пользователь для закрытия блока сообщения. Если
пользователь нажал кнопку Yes, то результат равен константе IDYES,
определяемой в Windows. Если пользователь нажал кнопку No, то результат
равен константе IDNO.
    Функции Windows требуют передачи в качестве аргументов различных
констант WORD и DWORD. Эти константы соответствуют различным стилям,
возвращаемым значениям, идентификаторам и т.д. Используйте эти константы в
ваших программах. Это сделает их более читаемыми и понятными, а также
независимыми от изменений новых версий Windows. Например, гораздо более
информативным является определение блока сообщения с типом MB_YESNO |
MB_ICONQUESTION, а не 0х0024. MB_YESNO, IDYES и другие константы
определяются в файле WINDOWS.H.
    Если вы посмотрите в WINDOWS.H, то вы увидите, что функция MessageBox
объявляется как

    int FAR PASCAL MessageBox(HWND, LPSTR, LPSTR, WORD);

    !!! Вам не надо включать WINDOWS.H в вашу прикладную программу; он
автоматически включается в файле OWL.H.
    Слово int должно быть вам знакомо. Но что значат FAR, PASCAL, HWND,
LPSTR и WORD ? Они являются новыми типами, определяемыми в WINDOWS.H. FAR и
PASCAL совпадают с far и pascal (WINDOWS.H также определяет эквиваленты с
заглавными буквами для nera, void и long). Все функции Windows API являются
функциями FAR PASCAL за исключением функции wsprintf, имеющей тип FAR cdecl.
    Некоторые функции требуют более сложных структур данных, например,
описатели шрифтов (LONGFONT) или классов окон (WNDCLASS). Обычно в качестве
параметра функций Windows передается указатель на эти структуры. Список
доступных структур приведен в книге "ObjectWindows для С++. Справочное
руководство.".

    Комбинирование стилевых констант

    Функции Windows, создающие интерфейсные элементы, обычно требуют
задания некоторых стилевых параметров типа WORD или DWORD. Windows
определяет сотни стилевых констант, и их полный список приведен в Главе 7
"Константы Windows" в книге "ObjectWindows для С++. Справочное
руководство.". Идентификаторы констант стилей состоят из двухбуквенного
мнемонического префикса, следующими за ними символом подчеркивания и
описательного имени. Например, WS_POPUP является константой стиля окна для
всплывающих окон ("WS_" означает "стиль окна").
    Часто, для создания новых стилей, они комбинируются посредством
использования оперетора связывания OR - |. В примере MessageBox, вы
передаете MB_YESNO | MB_ICONQUESTION как параметр стиля. Этот стиль создает
блок сообщения с двумя кнопками, Yes и No, и иконкой вопроса.
    Вы должны помнить, что некоторые стили являются взаимоисключающими.
Комбинация таких стилей приведет к выдаче непредсказуемых и, возможно,
нежелательным результатам.

    Типы функций Windows

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

    Интерфейсные функции управления окнами
    Эти функции управляют сообщениями, манипулируют окнами и блоками
диалога, и создают системный вывод. Эта категория включает функции для
меню, курсоров и Буфера информационного обмена (Clipboard).

    Функции интерфейса с графическими устройствами (GDI)
    Эти функции выводят текст, графику и побитовые отображения на различные
устройства, включая экран и принтер. Функции не связаны с каким-либо
конкретным типом устройства, и являются независимыми от них.

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

    Функции с обратным вызовом

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

    FARPROC ThunkPtr = MakeProcInstance((FARPROC) MyCallback,
                                         GetApplication()->hInstance);
    Первый аргумент функции MakeProcInstance является указателем на вашу
функцию с обратным вызовом. Второй - дескриптром экземпляра вашей
прикладной программы.
    Все функции с обратными вызовами должны экспортироваться с
использованием ключевого слова _export. Например, вы можете объявить
функцию с обратным вызовом как

    BOOL FAR PASCAL _export MyCallBack(HWND hWnd, DWORD lParam);

    Все Windows функции с обратными вызовами предполагают использование
последовательность вызова Паскаля, и поэтому объявляются с ключевым словом
PASCAL.
    Поскольку Windows функции с обратными вызовами вызываются извне среды
ObjectWindows и не вызывается из среды С++, то нельзя использовать
нестатические функции-компоненты С++ в качестве функций с обратными
вызовами. Нестатические функции-компоненты используют скрытый параметр
(this), который не передается Windows. Статические функции-компоненты,
однако, не используют скрытого указателя this и, поэтому, могут
использоваться как функции с обратными вызовами. Обязательно используйте
ключевое слово PASCAL при объявлении статических функций-компонент с
обратными вызовами, если они будут вызываться Windows.

    Функции подсчета

    Наиболее употребимые Windows функции с обратными вызовами - это те,
которые вызываются как результат функций подсчета Windows. Эти функции
позволяют вам нумеровать, или зацикливать, определенные типы элементов в
системе Windows. Например, вы можете перенумеровать все шрифты в системе, и
распечатать контрольный текст каждым из них. Для использования этих функций
вы должны передать Windows адрес вашей функции с обратным вызовом описанным
выше способом. Windows функции, которые требуют функций с обратным вызовом,
включают EnumChildWindows, EnumClipboardFormats, EnumFonts, EnumMetaFile,
EnumObjects, EnumProps, EnumTaskWindows и EnumWindows.
    Пусть, например, у вас есть статическая функция-компонента С++
TMyWindow::ActOnWindow, которая объявляется следующим образом:

    class TMyWindow
    {
    ...
      static BOOL FAR PASCAL _export TMyWindow::ActOnWindow(HWND hWnd,
             DWORD lParam);
    ...
    };

    Вы можете передать эту функцию как функцию с обратным вызовом в вызов
Windows функции EnumWindows:

    FARPROC lpEnumWinFunc;
    ...
    lpEnumWinFunc = MakeProcInstance((FARPROC) TMyWindow::ActOnWindow,
                    GetApplication()->hInstance);
    ReturnVal = EnumWindow(lpEnumWinFunc, aLongVal);
    FreeProcInstance(lpEnumWinFunc);

    Обратные вызовы для функций подсчета должны возвращать не ноль на
продолжение подсчета, и ноль при его окончании. В нашем примере,
статическая функция-компонента TMyWindow::ActOnWindow будет производить
некоторые действия с окном, задаваемым передаваемым дескриптором. Параметр
aLongVal представляет собой любое значение, которое вызов EnumWindows
выбирает для передачи в функцию с обратным вызовом.

    Использование интеллектуальных обратных вызовов

    Вы можете избежать дополнительных действий по вызову MakeProcInstance и
FreeProcInstance, если вы откомпилируете вашу программу с опцией
"интеллектуальные обратные вызовы". Эта опция модифицирует пролог и эпилог
вашего кода, предполагая DS == SS; другими словами, по умолчанию сегмент
данных совпадает с сегментом стека; это избавляет вас от вызова функций
MakeProcInstance и FreeProcInstance. Предыдущий пример при использовании
интеллектуальных обратных вызовов будет выглядеть так:

    ReturnVal = EnumWindow((FARPROC) TMyWindow::ActOnWindow, aLongVal);

         Сообщения Windows

    Когда Windows определяет, что произошло событие, которое влияет на
прикладную программу, то она посылает этой прикладной программе сообщение.
Существует огромное множество различных типов сообщений, исходящих от
многочисленных источников:
    - пользователь, перемещением или нажатие "мыши" или нажатием клавиш
клавиатуры, генерирует сообщения события пользователя;
    - ваша программа мажет посылать сообщения самой себе;
    - ваша программа может вызывать функции Windows, результатом которых
является получение других сообщений Windows;
    - прикладная программа может посылать сообщения другой прикладной
программе через динамический обмен данными (DDE);
    - сама система Windows может посылать различные сообщения вашей
прикладной программе.
    Прикладная программа Windows отвечает за прием и ответы на посланные ей
сообщения. Во время этого процесса приложение может вызывать функцию,
определенную для ответа на возникающее заданное событие, которое затрагивает
заданное окно.
    ObjectWindows управляет для вас приемом и обработкой сообщений.
ObjectWindows также предоставляет для получаемых сообщений обработку по
умолчанию. Вам надо только определить ответы не совпадающие для вашего
приложения с принятыми по умолчанию. Для этого в классах, производных от
поставляемых с ObjectWindows, вы объявляете и определяете специальные
функции-компоненты ответа на сообщения.
    Ниже приводится простое определение функции-компоненты, которая
отвечает на нажатие левой клавиши "мыши". Это определение взято из
определения класса TMyWindow, производного от класса ObjectWindows ТWindow.

    class TMyWindow : public TWindow {
    public:
    ...
       virtual void WMLButtonDown(RTMessage Msg) =
         [WM_FIRST + WB_LBUTTONDOWN];
    ...
    };

    Такая форма объявления является общей для всех функций-компонент ответа
на сообщения. Метод С++ объявления функций-компонент был расширен для
разрешения использования целочисленного идентификатора, связанного с
компонентами-функциями виртуального метода. Из-за этого, ObjectWindows
позволяет отображать сообщения вашей прикладной программы в
фуункции-компоненты ответа.
    Например, если вы хотите, чтобы экземпляр TMyWindow издавал звук при
нажатии клавиши "мыши", просто определите это свойство в функции-компоненте
ответа на сообщение:

    void TMyWindow::WMLButtonDown(RTMessage Msg)
    {
      MessageBeep(0);  // вызов функции Windows
    }

    Все очень просто.

    Параметры сообщений Windows

    При приеме приложением Windows предназначенного ему сообщения вместе с
ним принимаются 4 параметра. Два из них являются: дескриптором
(идентификатором Windows) затронутого окна и идентификатором сообщения. Эти
два параметра не нужны вашей прикладной программе, так как ObjectWindows
управляет отображением входящих сообщений в соответствующие
функции-компоненты ответа оконного объекта. Другие два параметра, значения
WORD и DWORD, содержат полезные данные. Эти параметры хранятся как
компоненты WParam и LParam в определяемой в ObjectWindows структуре
TMessage, ссылка на которую (типа RTMessage) передается вашим
функциям-компонентам ответа на сообщения. Заметим, что структура TMessage
также содержит устанавливаемую самой ObjectWindows компоненту Result,
которая вами обычно будет игнорироваться.
    WParam имеет тип WORD и обычно содержит одну порцию информации, части
дескриптор, идентификатор или код. Например, выбор из меню генерирует
сообщение WM_COMMAND, чей WParam равен идентификатору выбранного пункта
меню.
    LParam может содержать значение LONG, например дальний (far) указатель.
Или содержать две порции информации, одну в старшем слове, другую в
младшем. Например, LParam, передаваемая функции-компоненте ответа на
сообщение WM_LBUTTONDOWN ("нажата левая клавиша"), содержит х- и у-
координаты позиции в которой произошло нажатие.
    Для обеспечения удобного доступа к старшим и младшим словам, LParam
объявляется как компонента объединения с собой структуры LP, содержащей
компоненты Lo и Hi типа WORD. Предположим, что вы объявили Msg как
структуру TMessage, теперь вы можете использовать Msg.LP.Hi и Msg.LP.Lo для
ссылки на старшее и младшее слово Msg.LParam. TMessage::WParam объявляется
аналогично, как компонента объединения с собой структуры WP, но с
компонентами Hi и Lo типа BYTE, а не WORD.

    Типы сообщений Windows

    В следующем разделе приводится список типов сообщений, которые может
получать прикладная Windows программа. Полный список имеется в Главе 5
"Сообщения Windows" в книге "ObjectWindows для С++. Справочное
руководство.".

    Сообщения управления окнами
    Эти сообщения сигнализируют об изменении статуса окон. Например,
сообщение WM_CLOSE посылается при закрытии окна, а WM_PAINT при
необходимости вывода занова части или всего окна.

    Инициализационные сообщения
    Эти сообщения, включающие WM_CREATE и WM_INITDIALOG, посылаются при
создании программой окна или блока диалога.

    Сообщения ввода
    Эти сообщения принимаются в результате ввода информации посредством
"мыши", клавиатуры, полос прокрутки или системного таймера. Одним из
наиболее распространенных сообщений ввода является сообщение WM_COMMON,
которое посылается при выборе из меню или акселератора, или от
управляющего события, такого как нажатие кнопки.

    Системные сообщения
    Эти сообщения посылаются в ответ на манипуляции пользователем
стандартными управляющими средствами прикладной программы, такими как блок
меню-управления (Control-menu), линейки прокрутки, кнопки минимизации и
максимизации. В результате выбора блока меню-управления посылается
сообщение WM_SYSCOMMAND. Большинство прикладных программ не перехватывают
системные сообщения.

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

    Сообщения о системной информации
    Эти сообщения посылаются при изменении системных атрибутов Windows,
например цвет или шрифт.

    Сообщения манипулирования управляющими элементами
    Эти сообщения посылаются прикладной программой своим управляющим
элементам, таким как блоки списков или кнопки. Каждый класс управляющих
элементов понимает свой собственный набор сообщений, запрашивающих и
устанавливающих их атрибуты. Сообщения манипулирования управляющими
элементами отличаются от всех других сообщений, за исключением MDI
сообщений, тем, что они не извещают приложение о событии, а вызывают
выполнение события. Смотри "Посылка сообщений".

    Сообщения уведомления управляющих элементов
    Сюда входят сообщения WM_COMMAND, которые уведомляют родительское окно
о происшедшем событии управления, таком как выбор пункта из блока диалога
или ввод информации в редактируемый управляющий элемент. Параметры
сообщения задают определенный код уведомления, например LBN_SELCHANGE и
EN_CHANGE.

    Уведомляющие сообщения линеек прокрутки
    Эти сообщения являются уведомляющими сообщениями управляющих элементов,
обслуживающие линейки прокрутки. В эту группу входят два сообщения,
WM_HSCROLL и WM_VSCROLL.

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

    Сообщения мультидокументального интерфейса
    Эти сообщения посылаются прикладными программами, удовлетворяющими
стандарту MDI манипулирования ее дочерними окнами. MDI сообщения включают
WM_MDIACTIVATE и WM_MDIDESTROY.

    Обработка сообщений по умолчанию

    Windows обеспечивает обработку по умолчанию многих сообщений,
посылаемых прикладной программе. Если ваш управляющий элемент, блок диалога
или окно игнорирует входящее сообщение, то ObjectWindows автоматически
вызывает поддерживаемую Windows обработку по умолчанию. Соответствующая
управляющему элементу, блоку диалога или окну обработка по умолчанию
задается ее функцией-компонентой DefWndProc.
    !!! Смотри раздел "Обработка сообщений по умолчанию" в Главе 9
"Интерфейсные объекты".
    Однако, когда вы определяете способ реакции на входящее сообщение, вы
можете дополнить его стандартными действиями Windows. Для этого, вызовете
функцию-компоненту вашего управляющего элемента, блока диалога или окна
DefWndProc из функции-компоненты ответа на сообщение.

    Посылка сообщений

    Одним из источников сообщений является сама ваша прикладная программа.
Ваша программа может посылать сообщения себе самой, вызовом двух функций
API Windows, SendMessage и PostMessage. SendMessage заставляет окно, чей
дескриптор вы передаете, обрабатывать сообщение немедленно, в то время, как
PostMessage помещает сообщение в очередь сообщений окна и завершает работу
без ожидания окончания его обработки. Этот технический прием является также
полезным для моделирования событий в вашей прикладной программе. Более
подробная информация об этих API функциях имеется в системе интерактивной
помощи Help и в Главе 4 "Функции Windows" в книге "ObjectWindows для С++.
Справочное руководство.".
    SendMessage более удобна при работе с управляющими элементами, такими
как блоки списков и кнопки. Windows определяет сообщения манипулирования
управляющими элементами для осуществления таких действий, как добавление
пунктов в блок списка (LB_ADDSTRING) или изменения состояния блока проверки
(BM_SETCHECK). Управляющие объекты ObjectWindows определяют
функции-компоненты, такие как TListBox::AddString и TCheckBox::SetCheck,
которые посылают к связанным с ними интерфейсным элементам многие из этих
сообщений (наиболее часто используемых). Если вы хотите передать
управляющим элементам в ваших окнах сообщения, отличные от этих, то
используйте SendMessage, которая передает HWindow управляющего элемента как
первый параметр (Wnd).
    Если вы хотите послать сообщение управляющему элементу в блоке диалога,
то процедура будет несколько отличаться. Обычно, управляющие элементы в
ваших объектах TDialog не будут иметь связанных с ними интерфейсных
объектов ObjectWindows, но вы можете послать им сообщения с помощью
TDialog::SendDlgItemMsg. Вы можете облегчить связь с управляющими
элементами в блоках диалога, связыванием с ними интерфейсных объектов при
помощи переменного конструктора TWindow.
    !!! Смотри раздел "Работа с управляющими элементами и обработка
сообщений" ниже.

    Диапазоны сообщений

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

 Таблица 7.1: Диапазоны сообщений Windows
-------------------------------------------------------------------------
 Константа   Значение  Диапазон сообщений  Значение
-------------------------------------------------------------------------
 WM_FIRST    0х0000    0х0000-0x03FF       Сообщения Windows
 WM_USER     0х0400    0х0400-0x7F00       Оконные сообщения, определяемые
                                           программистом
 WM_INTERNAL 0х7F00    0х7F00-0x7FFF       Зарезервировано для внутреннего
                                           использования
 ID_FIRST    0x8000    0x8000-0x8EFF       Сообщения на основе дочерних
                                           идентификаторов,
                                           определяемые программистом
 ID_INTERNAL 0x8F00    0x8F00-0x8FFF       Зарезервировано для внутреннего
                                           использования
 NF_FIRST    0x9000    0x9000-0x9EFF       Уведомляющие сообщения,
                                           определяемые программистом
 NF_INTERNAL 0x9F00    0x9F00-0x9FFF       Зарезервировано для внутреннего
                                           использования
 CM_FIRST    0xA000    0xA000-0xFEFF       Командные сообщения,
                                           определяемые программистом
 CM_INTERNAL 0xFF00    0xFF00-0xFFFF       Зарезервировано для внутреннего
                                           использования
-------------------------------------------------------------------------

    Сообщения, определяемые пользователем

    Windows позволяет пользователю самому определять свои собственные
сообщения для последующего их использования в прикладных программах. Они
являются особенно полезными при определении новых событий и реакции на них.
Например, вы можете иметь одно окно, посылающее сообщение, определяемое
пользователем, всем остальным открытым окнам прикладной программы.
    Каждое Windows сообщение имеет связанный с ним идентификатор типа WORD.
Для каждого предопределенного сообщения Windows в файле WINDOWS.H
определяется соответствующая константа типа WORD, которая затем
используется в объявлении функции-компоненты ответа на это сообщение.
    Среди допустимых диапазонов идентификаторов сообщений Windows
существуют значения, зарезервированные для сообщений, определяемых
пользователем. Диапазон значений, зарезервированных для предопределенных
сообщений Windows, находится от 0 до WM_USER - 1. Значения сообщений,
определяемых пользователем, могут лежать в диапазоне от WM_USER до WM_USER
+ 31,744. Мы советуем вам определять значение ваших собственных сообщений
как константы, начинающиеся со значений WM_USER, WM_USER + 1, WM_USER + 2 и
так далее. Например,

    #define WM_MYFIRSTMESSAGE WM_USER
    #define WM_MYSECONDMESSAGE WM_USER + 1
    #define WM_MYTHIRDMESSAGE WM_USER + 2

    Для посылки ваших сообщений используйте Windows функцию SendMessage:

    SendMessage(AWindow->HWindow, WM_MYFIRSTMESSAGE, AWord, ALong);

    Для получения сообщений, определяемых пользователем, используйте
процедуру, что и для стандартных сообщений Windows. Например:

    class MyWindow : public TWindow
    {
    ...
      virtual void WMMyFirstMessage(RTMessage Msg) =
        [WM_FIRST + WM_MYFIRSTMESSAGE];
      virtual void WMMySecondMessage(RTMessage Msg) =
        [WM_FIRST + WM_MYSECONDMESSAGE];
      virtual void WMMyThirdMessage(RTMessage Msg) =
        [WM_FIRST + WM_MYTHIRDMESSAGE];
    ...
    };

    Глава 8. Объекты прикладная программа и модуль

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

         Поток прикладной программы

    Главная программа вашего приложения ObjectWindows обычно состоит только
из трех операторов.
    !!! THelloApp HelloApp("HelloApp", hInstance, hPrevInstance, lpCmdLine,
nCmdShow);
    Первый оператор конструирует объект прикладная программа посредством
вызова его конструктора. Конструктор также инициализирует компоненты данных
объекта прикладная программа. Доступ к объекту прикладная программа из
других частей программы ObjectWindows осуществляется вызовом
GetApplication.
    !!! HelloApp.Run();
    Второй оператор вызывает функцию-компоненту приложения Run, которая
затем вызывает InitApplication и InitInstance для осуществления
инициализации первого экземпляра и последующих, соответственно. Затем
вызывается InitMainWindow для создания главного окна. В большинстве
случаев, вам надо переопределить только InitMainWindow (смотри Рисунок
8.1).
    После этого Run вызовом MessageLoop устанавливает прикладную программу
в активное состояние для начала обработки входящих сообщений Windows,
инструкций управляющих работой прикладной программы. MessageCall вызывает
функции-компоненты, обрабатывающие определенные входящие сообщения.
MessageLoop в действительность организует цикл сообщений, который
продолжается до закрытия приложения.
    !!! return HelloApp.Status;
    Третий оператор возвращает финальный статус прикладной программы,
который ObjectWindows хранит в компоненте данных Status. Это необходимо,
так как WinMain обязательно должна возвращать значение int. Значение Status
может быть полезным при отладке, когда ненулевое значение свидетельствует
об ошибке.

   љЋЋЋЋ> InitApplication
   ѓ
 RunЋЋЋЋ> InitInstanceЋЋЋЋЋ> InitMainWindow
   ѓ
   ЉЋЋЋЋ> MessageLoop

    Рисунок 8.1 Функция-компонента осуществляет вызовы, организуя поток
                прикладной программы

         Инициализация приложений

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

    Инициализация главного окна

    Приведем минимальное определение класса прикладная программа:

    class THelloApp :public TApplication
    {
    public:
      THelloApp(LPSTR AName, HANDLE hInstance, HANDLE hPrevInstance,
                LPSTR lpCmdLine, int nCmdShow) : TApplication(AName,
                hInstance, hPrevInstance, lpCmdLine, NCmdShow) {};
      virtual void InitMainWindow();
    };
    !!! Это фрагмент программы из файла HELLOAPP.CPP

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

    void THelloApp::InitMainWindow()
    {
       MainWindow = new TWindow(NULL, "Hello World!");
    }

    Приводимая ниже прикладная программа ObjectWindows состоит из главной
программы, плюс определение производного от TApplication класса, в котором
определяется одна функция-компонента InitMainWindow:

    !!! Это программа HELLOAPP.CPP
    #include 

    // Определяем класс, являющийся производным от TApplication
    class THelloApp :public TApplication
    {
    public:
      THelloApp(LPSTR AName, HANDLE hInstance, HANDLE hPrevInstance,
                LPSTR lpCmdLine, int nCmdShow) : TApplication(AName,
                hInstance, hPrevInstance, lpCmdLine, NCmdShow) {};
      virtual void InitMainWindow();
    };

    // Конструктор компоненты данных MainWindow класса THelloApp
    void THelloApp::InitMainWindow()
    {
       MainWindow = new TWindow(NULL, "Hello World!");
    }
    int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
      LPSTR lpCmdLine, int nCmdShow)
    {
      THelloApp HelloApp ("HelloApp", hInstance, hPrevInstance,
                          lpCmdLine, nCmdShow);
      HelloApp.Run();
      return Hello.App.Status;
    }

    Приведенная программа является простейшим приложением ObjectWindows,
которое просто высвечивает окно с заголовком "Hello World!" ("Здравствуй
Мир!"). Результат работы показан на Рисунке 8.2. Вы можете перемещать это
окно и изменять его размер. Оно также может быть минимизировано в иконку,
нажатием иконки "стрелка вниз" в верхнем правом углу. Для восстановления
прежних размеров нажмите на иконку два раза. Нажатие иконки "стрелка вверх"
разворачивает окно на весь экран. Для закрытия окна и завершения работы
приложения дважды нажмите клавишу "мыши" на блоке управляющего меню в левом
верхнем угле окна.

    Рисунок 8.2 Главное окно

    Инициализация каждого экземпляра прикладной программы

    Функция-компонента класса прикладная программа InitInstance
осуществляет инициализацию каждого экземпляра Windows приложения.
TApplication определяет функцию-компоненту InitInstance, которая вызывает
InitMainWindow, и затем вызовом его функции-компоненты Show выводит главное
окно. Вы можете переопределить InitInstance для изменения стандартной
инициализации; например, загружать таблицу акселераторов. Если вы
переопределили InitInstance для вашего класса прикладная программа, то
гарантируйте первоначальный вызов TApplication::InitInstance.
    Ниже приводится пример функции-компоненты InitInstance, загружающей
таблицу акселераторов до установления приложения в активное состояние.
Идентификатор ресурсов таблицы акселераторов определяется в файле ресурсов
и равен 100.

    void TMyApp::InitInstance()
    {
      TApplication::InitInstance();
      HAccTable = LoadAccelerators(GetApplication()->hInstance,
                   MAKEINTRESOURCE(100));
    }

    InitInstance должна только выполнять инициализацию экземпляра
приложения. Инициализация главного окна должна осуществляться в
конструкторе и в функциикомпоненте объекта главное окно SetupWindow.

    Инициализация первого экземпляра прикладной программы

    Если пользователь будет запускать прикладную программу независимо
несколько раз (без завершения ее работы), то вы можете определить некоторые
действия, производимые только в случае первого запуска. Эти действия
называются инициализацие первого экземпляра приложения. Заметим, что если
пользователь запустил прикладную программу, затем завершил ее работу,
снова запустил и так далее, то каждый экземпляр, в этом случае, будет
рассматриваться как первый.
    Если текущий экземпляр является первым, то вызывается его
функция-компонента InitApplication. TApplication определяет "ничего не
делающую" функцию-компоненту InitApplication, которая может быть
переопределена для осуществления отдельной инициализации первого
экземпляра.
    Например, вы можете модифицировать TestApp так, что заголовок главного
окна будет информировать о первом экземпляре приложения. Для этого добавте
в класс прикладная программа компоненту данных WindowsTitle. Затем,
определите функцию-компоненту InitApplication так, что она устанавливает
WindowTitle в "First Instance" ("Первый Экземпляр"). Заставьте конструктор
прикладной программы инициализировать WindowTitle в "Additional Instance"
("Дополнительный Экземпляр"). InitApplication вызывается только для первого
экземпляра.

    Рисунок 8.3 Последовательная инициализация экземпляров приложения

    #include 
    #include 

    class TTestApp : public TApplication
    {
      TTestApp(LPSTR AName, HANDLE hInstance, HANDLE hPrevInstance, LPSTR
               lpCmdLine, int nCmdShow);

    protected:
      char WindowTitle[20];
      virtual void InitMainWindow();
      virtual void InitApplication();
    };
    TTestApp::TTestApp(LPSTR AName, HANDLE hInstance, HANDLE hPrevInstance,
                LPSTR lpCmdLine, int nCmdShow) : TApplication(AName,
                hInstance, hPrevInstance, lpCmdLine, NCmdShow)
    {
       strcpy(WindowTitle, "Additional Instance");
    }

    void TTestApp::InitApplication()
    {
       strcpy(WindowTitle, "First Instance");
    }

    void TTestApp::InitMainWindow()
    {
       MainWindow = new TWindow(NULL, WindowTitle);
    }

    int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR
                       lpCmdLine, int nCmdShow);
    {
        TTestApp TestApp("Instance Tester", hInstance, hPrevInstance,
                         lpCmdLine, nCmdShow);
        TestApp.Run();
        return TestApp.Status;
    }
    !!! Это файл INSTTEST.CPP

         Запуск прикладных программ

    Цикл сообщений вашей прикладной программы инициализируется при вызове
функции-компоненты объекта прикладная программа Run, которая, в свою
очередь, вызывает функцию-компоненту MessageLoop. Во время функционирования
вашей программы, цикл сообщений обрабатывает приходящие сообщения Windows.
Ваша прикладная программа ObjectWindows наследует функцию-компоненту
MessageLoop, которая работает автоматически.
    Для обработки Windows сообщений функция-компонента MessageLoop вызывает
три функции-компоненты перевода. ProcessDlgMsg управляет сообщениями
немодальных диалогов, ProcessAccels управляет акселераторами, и
ProcessMDIAccels управляет акселераторами для приложений MDI. Для
приложений, которые не используют акселераторы или немодальные блоки
диалога, или не являютмя приложениями MDI, вы можете захотеть направить
MessageLoop куда-либо еще. Смотри входы для функций-компонент перевода из
TApplication в Главе 1 "Классы ObjectWindows" в книге "ObjectWindows для
С++. Справочное руководство.".

         Закрытие прикладных программ

    Перед завершением работы, прикладная программа должна выйти из цикла
сообщений. Это может произойти в результате попытки пользователя закрыть
главное окно прикладной программы. Мы сказали, что может случиться,
поскольку ObjectWindows предоставляет механизм переопределения поведения
при выходе; например, проверку до закрытия на наличие несохраненных файлов.
    При попытке пользователя закрыть приложение двойным нажатием клавиши
"мыши" на блоке меню-управления (Control-menu) происходит следующее:
    1. Windows посылает главному окну приложения сообщение WM_CLOSE.
    2. Объект главное окно отвечает вызовом функции-компоненты CloseWindow,
которая закрывает главное окно (вызовом ShutDownWindow) только после
проверки объекта прикладная программа посредством вызова его
функции-компоненты CanClose (так как закрытие главного окна приложения
приводит к закрытию самого приложения).
    3. Функция-компонента объекта прикладная программа CanClose возвращает
результат вызова функции-компоненте CanClose главного окна, позволяя
главному окну самому определять возможность своего закрытия.
    4. Главное окно, по умолчанию, решает возможность своего закрытия путем
запроса своих дочерних окон о возможности их закрытия. Если все
функции-компоненты CanClose дочерних окон возвращают TRUE, то CanClose
главного окна возвращает TRUE и приложение закрывается.
    Этот механизм действует по принципу, "Если кто-то знает, почему данное
приложение не должно закрываться, говорите сейчас, или пропадайте". В итоге
, объект прикладная программа должен отвечать за одобрение закрытия
приложения. По умолчанию, до закрытия он запрашивает главное окно. Вы
можете переопределить функцию-компоненту CanClose класса главное окно, что
приведет к другому порядку закрытия. Вы также свободны в переопределении
функции-компоненты CabClose класса прикладная программа.



    Глава 9. Интерфейсные объекты

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

         TWindowsObject

    TWindowsObject является общим базовым классом для всех интерфейсных
классов Windows: TDialog, TWindow и его производных классов, TControl.
TWindowsObject определяет общие свойства оконных, диалоговых и управляющих
объектов. Функции-компоненты TWindowsObject осуществляют следующее:
    - Поддержку двойственных структур объект ObjectWindows/элемент Windows,
включая конструирование и удаление объектов, и создание и удаление
элементов.
    - Автоматически поддерживают отношения родитель-ребенок между
интерфейсными объектами (не взаимоотношения наследственности классов).
    - Регистрируют новые классы Windows; смотри Главу 10 "Оконные объекты".
    Работа TWindowsObject остается для вас невидимой. Вы будете редко, а
возможно и никогда не будете, порождать классы непосредственно из
TWindowsObject. Гораздо более удобным будет порождать их из классов ТWindow
и TDialog.

         Почему интерфейсные объекты ?

    Зачем нам нужны интерфейсные объекты, если Windows уже имеет видимые
окна, диалоги и управляющие элементы ?
    Каждый интерфейсный объект имеет связанный с ним видимый интерфейсный
элемент - не объект, а физическое окно, диалог или управляющий элемент -
для которого он выступает как объектно-ориентированная основа. Интерфейсный
объект предоставляет функции-компоненты для создания, инициализации,
управления и удаления связанного с ним интерфейсного элемента. Компоненты
данных интерфейсного элемента содержат ассоциированные данные, включающие
дескриптор его интерфейсного элемента и его родительское и дочерние окна.
Функции-компоненты интерфейсного объекта берут на себя многие детали
программирования в Windows.
    Взаимосвязь объект/элемент очень напоминает связь между потоком С++ и
файлом ДОС. Вы можете сконструировать потоковый объект для управления
деталями ввода/вывода файла ДОС. Вы конструируете оконный объект для
управления деталями ввода/вывода окна.
    Структура интерфейсного объекта, вместе с ассоциированным интерфейсным
элементом, для обеспечения правильного функционирования окон, диалогов и
управляющих элементов должна подчиняться определенным соглашениям.
Например, для создания полного интерфейсного объекта вы должны вызывать две
функции-компоненты. Вначале конструктор, для конструирования интерфейсного
объекта и установки его атрибутов, таких как стиль и меню.
    Потом вы должны вызвать функцию-компоненту создания MakeWindow, которая
связывает интерфейсных объект с новым интерфейсным элементом. Эта связь
поддерживается посредством компоненты данных интерфейсного объекта НWindow,
которая содержит дескриптор окна. MakeWindow является функцией-компонентой
объекта прикладная программа. MakeWindow вызывает функцию-компоненту
объекта Create, которая заставляет Windows создать видимый элемент.
    MakeWindow создает видимый элемент "безопасно". В отличии от Create,
MakeWindow гарантирует правильное создание интерфейсного объекта и наличие
требуемого объема памяти. Некоторая потеря производительности от
использования MakeWindow окупится гарантией невыхода за пределы доступной
памяти и зависания системы.
    Аналогично, видимый интерфейсный элемент должен обязательно удалятся
вместе с освобождением соответсвующего интерфейсного объекта. Однако в
этом случае, вам надо только вызвать ShutDownWindow, которая уничтожает
интерфейсный элемент и затем удаляет интерфейсный объект.
    Вы должны заметить, что создания интерфейсного объекта и
соответствующего видимого элемента не означает обязательного вывода на
экран. При создании видимого элемента, Windows проверяет, имеет ли окно
установку стиля WS_VISIBLE. WS_VISIBLE и другие стили устанавливаются или
сбрасываются в конструкторе, путем их установки или сбоса в компоненте
данных интерфейсного объекта Attr.Style. Создаваемый интерфейсный элемент
выводится на экран только в случае установки стиля WS_VISIBLE.
    В любое время после создания, элемент может быть показан или спрятан
вызовом функции-компоненты интерфейсного объекта Show.

         Отношение родители-дети между окнами

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

    Списки дочерних окон

    Вы задаете родителя интерфейсного элемента во время его
конструирования. Родительский оконный объект является параметром
конструктора интерфейсного объекта. Дочерний оконный объект хранит в своей
компоненте данных Parent адрес своего родительского оконного объекта как
указатель на этот объект. Он также автоматически сохраняет адреса своих
дочерних оконных объектов.
    При определении нового интерфейсного класса с дочерними окнами, вы
также должны заставить конструктор конструировать дочерние оконные объекты.
После этого, когда прикладная программа вызывает функцию-компоненту
родительского объекта Create, создается интерфейсный элемент родителя. Если
создание прошло успешно, то вызывается функция-компонента SetupWindow.
TWindowsObject::SetupWindow осуществляет итерацию по всем дочерним окнам в
собственном списке дочерних окон, и, по умолчанию, вызывает
функцию-компоненту Create для каждого окна, за исключением диалогов.
Дочерние окна с установленным статусом WS_VISIBLE выводятся сразу после
создания.
    Часто приходится переопределять SetupWindow родительского окна с тем,
чтобы она после создания дочерних окон осуществляла установки некоторых
параметров. Заполнение блока списка элементами является одним таких из
примеров. В этом случае, обеспечьте вызов функции-компоненты SetupWindow
базового класса вашего оконного объекта первым оператором переопределенной
функции-компоненты SetupWindow.
    !!! Пример переопределения SetupWindow имеется в Шаге 10 нашей
прикладной программы.
    Для явного исключения дочернего окна (например всплывающего окна) из
обработки по умолчанию, после конструирования объекта вызовите
функцию-компоненту DisableAutoCreate. Для явного запроса на обработку
дочерного окна по умолчанию (такого как блок диалога, который обычно
исключается из такой обработки) вызовите его функцию-компоненту
EnableAutoCreate.
    Как вызов функции-компоненты Create родительского окна приводит к
вызову функций-компонент Create его дочерних окон, вызов деструктора
родительского окна приводит к вызову деструкторов его дочерних окон.
Функция-компонента CanClose также осуществляет итерацию по его собственному
списку дочерних окон, вызывая функцию-компоненту CanClose каждого дочернего
оконного объекта. Она возвращает TRUE, если все дочерние окна также
возвращают TRUE.

    Итерация по дочерним окнам

    Вы можете захотеть написать функции-компоненты, осуществляющие итерации
по дочерним окнам некоторого окна с целью выполнения некоторых действий.
Например, вам нужно будет проверить все дочерние блоки проверки в окне. В
этом случае, используйте наследуемую из TWindowsObject функцию-компоненту
ForEach как показано ниже.
    Функции-компоненты ForEach и FirstThat получают в качестве первого
аргумента указатель на функцию. Смотри книгу "ObjectWindows для С++.
Справочное руководство" для более подробной информации о
TWindowsObject::ForEach и TWindowsObject::FirstThat.

    void CheckTheBox(Pvoid P, Pvoid Param)
    {
      (PTCheckBox)P->Check();
    }
    void TMyWindow::CheckAllBoxes();
    {
      ForEach(CheckTheBox, NULL);
    }

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

    BOOL IsThisBoxChecked(Pvoid P, Pvoid Param)
    {
       return ((PTCheckBox)P->GetCheck() == BF_CHECKED);
    }

    PTCheckBox TMyWindow::GetFirstChecked();
    {
      return (FirstThat(IsThisBoxChecked, NULL);
    }

         Обработка сообщений

    Программы ObjectWindows имеют два канала связи с Windows. По одному из
них, прикладная программа управляет интерфейсом пользователя путем вызова
функций Windows. По другому, Windows посылает сообщения прикладной
программе в ответ на происходящие в ней события, такие как выбор
пользователем пункта меню (WM_COMMAND) или нажатие левой клавиши "мыши"
(WM_LBUTTONDOWN). Существует более 100 стандартных сообщений Windows; вы
можете также определять ваши собственные сообщения, как объясняется в Главе
7 "Обзор ObjectWindows".
    Приложение Windows осуществляет обработку в ответ на сообщения,
полученные от Windows. Вы можете определять нужный для вас способ реакции
на принимаемые сообщения. Он может отличаться для каждого приложения. Во
время процесса обработки сообщений могут вызываться функции Windows,
которые, в свою очередь, могут сами генерировать новые сообщения Windows.
    Входящие сообщения всегда предназначаются отдельному окну - окну,
на которое текущие события влияют наибольшим образом. Например, при нажатии
левой клавиши "мыши" в определенном окне, генерируемое сообщений
WM_LBUTTONDOWN предназначается для этого окна. ObjectWindows отображает
входящие сообщения для ваших окон в их функции-компоненты ответа.
    Это взаимодействие сообщений Windows и функций-компонент ответа на них
формирует основу прикладной программы ObjectWindows. Как вы можете увидеть,
главной обязанностью приложения ObjectWindows является реагирование на
события Windows. Это отличает их от традиционных программ, работающих под
управлением ДОС. Такой подход к программированию называется базирующимся на
порождаемых событиями сообщениях. Основной девиз такого подхода: молчите
пока вас не спросят. Такой подход требует дисциплины программирования, но
затем окупается созданием удобных мультизадачных прикладных программ.

    Ответы на сообщения

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

    class TMyWindow : public TWindow {

    public:
    ...
       virtual void WMRButtonDown(RTMessage Msg) =
         [WM_FIRST + WM_RBUTTONDOWN];
    ...
    };

    В этом примере функция-компонента WMRButtonDown вызывается при
получении объектом TMyWindow сообщения WM_RBUTTONDOWN. Имена
функции-компоненты и сообщения не обязательно должны совпадать, но их
совпадение облегчает читаемость программ. ObjectWindows использует это
соглашение в своих интерфейсных классах.
    Msg требует аргумента типа RTMessage. RTMessage является определяемым в
ObjectWindows типом ссылки на структуру TMessage, содержащую поля WParam,
LParam и Result. Компоненты WParam и LParam (типа WORD и LONG) содержат
информацию о пославшем сообщение событии. Чаще всего каждый параметр
содержит два блока информации. Например, при посылке сообщения
WM_LBUTTONDOWN, младшее слово LParam содержит х-координату точки в которой
произошло нажатие клавиши "мыши", а старшее слово - у-координату.
    LParam определяется как компонента объединения с собой структуры LP,
содержащей компоненты Hi и Lp типа WORD. Поэтому, вы можете использовать
Msg.LP.Hi и Msg.LP.Lo для ссылки на старшее и младшее слово Msg.LParam.
WParam объявляется аналогично, как компонента объединения с собой структуры
WP. Наиболее часто, вы будете использовать WParam и поля LP.Lo и LP.Hi,
имеющие тип WORD. Msg передается по ссылке, так как несколько сообщений
Windows требуют ответа, сохраняемого вашими функциями-компонентами в
компоненте Result.
    В программах ObjectWindows вы можете безболезненно игнорировать
сообщения Windows на которые ваши объекты не должны отвечать. Вы просто не
определяете соответствующие этим сообщениям функции-компоненты. В этом
случае, такие сообщения будут по умолчанию обрабатываться наследуемой из
TWindowsObject функцией-компонентой вашего окна DefWndProc.
    Существуют некоторые исключения в модели ответа на сообщения
ObjectWindows для нескольких общих сообщений, связанных с меню и дочерними
окнами, такими как редактируемые управляющие элементы и линейки прокрутки.
На этих вопросах мы остановимся ниже.

    Командные сообщения и сообщения дочерних окон

    Windows генерирует сообщения на основе команд, при выборе
пользователем пункта меню или нажатии акселератора. При активации
пользователем управляющего элемента, такого как кнопка или блок списка,
Windows генерирует сообщения на основе дочерних идентификаторов.
Большинство таких действий привод к появлению сообщения WM_COMMAND.
    Для облегчения распознавания и избежания длинных операторов выбора
(case), ObjectWindows автоматически отвечает на сообщения WM_COMMAND
генерацией другого, более специализированного сообщения. Вызываемая
виртуальная фукнкция-компонента базируется на содержимом этого второго
сообщения. Вы определяете функции-компоненты ответа на сообщения на основе
команд и сообщения на основе дочерних идентификаторов с использованием
возможностей расширенного объявления функций-компонент, как описывалось
выше.

    Обработка командных сообщений

    Для связи события, генерируемого меню или акселератором, с
функцией-компонентой ответа на командное сообщение используется
идентификатор, являющийся суммой определяемой в ObjectWindows константы
CM_FIRST, и идентификатора меню или акселератора, определяемого в ресурсе
меню или акселератора. Например, возьмем окно, объект TFileWindow, имеющее
три пункта меню File|New, File|Open и File|Save. Идентификаторы меню,
определяемые в файле OWLRC.H, CM_FILENEW, CM_FILEOPEN и CM_FILESAVE,
соответственно. Вот как будет выглядеть в этом случае определение объекта
TFileWindow:

    class TFileWindow : public TWindow
    {
    ...
      virtual void CMFileNew(RTMessage Msg) =
        [CM_FIRST + CM_FILENEW];
      virtual void CMFileOpen(RTMessage Msg) =
        [CM_FIRST + CM_FILEOPEN];
      virtual void CMFileSave(RTMessage Msg) =
        [CM_FIRST + CM_FILESAVE];
    ...
    };

    Однако, окно, владеющее меню, не всегда обязано определять ответ на
сообщение на основе команд. Вместо этого, этот ответ может определять одно
из его дочерних окон, включая управляющие элементы. Дочернее окно, которое
имеет поле ввода во время осуществления выбора из меню или нажатия
акселератора, имеет преимущественное право ответить на сообщение на основе
команд, определяя для этого функцию-компоненту ответа. Если окно не
определяет ответа, то оно пересылается родительскому окну этого дочернего
окна, затем к его родительскому окну и так далее, до тех пор, пока не будет
дан ответ или достигнуто окно, владелец меню. Это означает, что
редактируемый управляющий элемент в окне может сразу отвечать на выбор из
меню редактирования. Как пример использования данной технологии смотри
класс ObjectWindows TEditWindow.

    Обработка сообщений дочерних окон

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

    class TListBoxWindow : public TWindow
    {
    ...
      virtual void HandleListBoxMsg(RTMessage Msg) =
        [ID_FIRST + ID_LISTBOX];
    }

где ID_LISTBOX является константой, равной идентификатору управляющего
элемента. Идентификатор дочернего окна должен быть положительным целым
числом, меньшим 4,096.
    Альтернативой ответу родительского окна на сообщения событий в дочерних
окнах, может быть определение функции-компоненты реакции на уведомляющее
сообщение в самом управляющем элементе. Смотри Главу 12, раздел "Реакция на
предупреждающие сообщения управляющих элементов".

    Обработка сообщений по умолчанию

    !!! Более подробная информация о уведомляющих сообщениях приведена в
Главе 12 "Управляющие объекты".
    Этот раздел относится к сообщения Windows, которые вы перехватываете
напрямую с использованием константы WM_FIRST (означает, что сообщения
Windows), но не являющиеся сообщениями на основе команд, уведомления или
идентификатора дочернего окна.
    Приложение Windows получает гораздо больше сообщений, чем ему
необходимо ответить. Windows, по умолчанию, обрабатывает многие из этих
сообщений, в особенности, посылаемые управляющим элементам. Например, при
вводе пользователем информации в редактируемый управляющий элемент, по
умолчанию, Windows отвечает выводом новых символов и, при необходимости,
прокруткой. Ответы Windows по умолчанию вызываются для оконных объектов
ObjectWindows функцией-компонентой DefWndProc (включая диалоговые и
управляющие объекты).
    Когда вы игнорируете сообщение Windows (путем незадания
функции-компоненты ответа на него), ObjectWindows автоматически вызывает
функцию-компоненту DefWndProc интерфейсного объекта. Однако, даже если вы
получите сообщение с определенной для него функцией-компонентой ответа, вы
можете обработать его и вызовом DefWndProc.
    Например, следующий фрагмент программы перехватывает сообщение WM_CHAR,
которое посылается объекту редактируемого управляющего элемента во время
ввода в него информации. Вот функция-компонента ответа на сообщение WM_CHAR
для класса, производного из TEdit:

    void TMyEdit::WMChar(RTMessage Msg)
    {
       MessageBeep(0);
    }

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

    void TMyEdit::WMChar(RTMessage Msg)
    {
       MessageBeep(0);
       DefWndProc(Msg);
    }

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

    Глава 10. Оконные объекты

    В этой главе объясняется как создавать, отображать и заполнять окна
прикладной программы. В ObjectWindows класс ТWindow определяет большую
часть основных характеристик главного окна и всех остальных окон программы.
ТWindow определяет характеристики для открытия, закрытия, рисования и
прокрутки окон, а также, для дочерних окон и обработки командных сообщений.
    ТWindow имеет четыре производных класса TMDIFrame, TMDIClient, TControl
и TEditWindow. TMDIFrame и TMDIClient используются в приложениях
ObjectWindows, соответствующих стандарту многодокументального интерфейса
(MDI) Windows. Объяснение MDI и этих типов приводится в Главе 14. TControl
определяет управляющие элементы, такие как кнопки и блоки списков, и
описывается в Главе 12. TFileWindow, описываемый ниже, является производным
от TEditWindow. Чаще всего, вы будете создавать новые классы окон из класса
ТWindow.
    Эта часть описывает TWindow, TEditWindow и TFileWindow и включает
примеры построения новых классов Windows.

         Класс ТWindows

    Как минимум, ваша прикладная программа ObjectWindows должна иметь
главное окно, которое появляется при ее старте. Главным окном программы
ObjectWindows, обычно, является экземпляр класса, производного от ТWindow.
Многие прикладные программы имеют другие окна, обычно являющиеся дочерними
окнами главного.
    Эти дополнительные окна также обычно являются экземплярами классов,
производных от ТWindow. Например, программа рисования может определять
TPaintWindow для своего главного окна, и TZoomWindow для другого окна,
отображающего увеличенное нарисованное изображение. В этом случае,
TPaintWindow и TZoomWindow происходят от ТWindow. Приложениях с хорошим
дизайном, TZoomWindow возможно будет специализированной версией
TPaintWindow и выступать как класс, производный от TPaintWindow (и ТWindow
неявно).

         Инициализация и создания оконных объектов

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

    Инициализация оконных объектов

    Обычное Windows приложение имеет много различных стилей окон:
перекрывающиеся или всплывающие, граничные, прокручиваемые и захватываемые,
только некоторые из них. Эти атрибуты стиля, а также и другие атрибуты
создания, обычно устанавливаются при конструировании оконного объекта, и
используются при создании представляемого им видимого элемента.
    Атрибуты создания оконного объекта, такие как его стиль, заголовок и
меню, хранятся в его компоненте данных Attr, имеющая тип TWindowAttr.
TWindowAttr включает следующие компонента данных:
------------------------------------------------------------------------
 Компонента  Использование
 данных
------------------------------------------------------------------------
 Style       Типа DWORD, содержит константу комбинированного стиля.
 ExStyle     Типа DWORD, содержит расширенный стиль.
 Menu        Типа LPSTR, идентифицирует ресурс меню.
 X           Типа int, задает горизонтальную координату начального
             местоположения окна. Является горизонтальной координатой
             левого верхнего угла окна на экране.
 Y           Типа int, задает вертикальную координату начального
             местоположения окна. Является вертикальной координатой
             левого верхнего угла окна на экране.
 W           Типа int, задает начальную ширину окна в экранных
             координатах.
 H           Типа int, задает начальную высоту окна в экранных
             координатах.
 Param       Типа LPSTR, будет передаваться окну при его создании.
 Id          Типа int, задает идентификатор дочернего окна, используемого
             для связи между управляющим элементом и его родительским
             окном или диалогом. Id должен быть разным для всех дочерних
             окон одного родителя. Если управляющий элемент определяется в
             ресурсе, то его Id должен совпадать с идентификатором ресурса.
             Окно никогда не имеет оба набора Menu и Id.
------------------------------------------------------------------------
    Таблица 10.1: Компоненты данных TWindowAttr.

љЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋ‰
ѓ(0,0)                                            ѓ
ѓ  (X,Y)                                          ѓ
ѓ  љЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋ‰ ЊЋ   ѓ
ѓ  ѓ                Title                  ѓ ѓ    ѓ
ѓ  ЌЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋ„ ѓ    ѓ
ѓ  ѓMenu                                   ѓ ѓ    ѓ
ѓ  ЌЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋ„ ѓ    ѓ
ѓ  ѓ                                       ѓ H    ѓ
ѓ  ѓ                                       ѓ ѓ    ѓ
ѓ  ѓ                                       ѓ ѓ    ѓ
ѓ  ѓ                                       ѓ ѓ    ѓ
ѓ  ѓ                                       ѓ ѓ    ѓ
ѓ  ЉЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋ™ ‹Ћ   ѓ
ѓ  ЌЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋ W ЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋ„      ѓ
ЉЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋ™

    Рисунок 10.1 Атрибуты окна

    Первым аргументом в вызове конструктора является объект родительского
окна. Он NULL, если окно является главным (без родителя). Конструктор
ТWindow устанавливает компоненту данных Title в задаваемую аргументом
LPSTR. По умолчанию, он также устанавливает компоненту данных Attr в
WS_OVERLAPPEDWINDOW, если окно является главным окном приложения
(WS_VISIBLE в противном случае). X и W устанавливаются в CW_USEDEFAULT, а Y
и H в 0. Тем самым задается всплывающее окно наиболее приемлемых размеров.
Здесь мы описали стандартный способ задания размеров нового главного окна.
    При создании классов окон, производных от ТWindow вы обязательно
должны, по крайней мере, вызывать конструктор его базового класса:

    class MyWindow : public TWindow
    {
    ...
      TMyWindow(PTWindowsObject AParent, LPSTR ATitle) : TWindow(AParent,
                ATitle) {}
    ...
    };

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

    class MyWindow : public TWindow
    {
    ...
      TMyWindow(PTWindowsObject AParent, LPSTR ATitle) : TWindow(AParent,
                ATitle) {}
    ...
      PTChildWindow AChildWindow;
      PTListBox ListBox;
    };
    ...
    TMyWindow::TMyWindow(PTWindowsObject AParent, LPSTR ATitle) :
                         TWindow(AParent, ATitle);
    {
       Attr.Style |= WS_MAXIMIZE;
       AChildWindow = new TChildWindow(this, "Child Title");
       ListBox new TListBox(this, ID_LISTBOX, 201, 20, 20, 180, 80);
    ...
    }

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

    TChildWindow::TChildWindow(PTWindowsObject AParent, LPSTR ATitle) :
                               TWindow(AParent, ATitle)
    {
       Attr.Style |= WS_POPUP | WS_CAPTION;
       Attr.X = 100;
       Attr.Y = 100;
       Attr.W = 300;
       Attr.H = 300;
    }

    Атрибут Style может быть комбинацией констант оконных стилей или одной
константой, такой как
    - WS_OVERLAPPEDWINDOW
    - WS_POPUPWINDOW
    - WS_CHILDWINDOW
    - WS_CAPTION
    - WS_BORDER
    - WS_VSCROLL
    Представленный список содержит только наиболее часто используемые
стилевые константы. Полный список приведен в Главе 7 "Константы Windows" в
книге "ObjectWindows для С++. Справочное руководство.".
    В качестве альтернативы, если вы не определяете производного класса для
дочернего окна, вы можете сконструировать оконный объект, а затем,
переустановить его атрибуты в конструкторе его родительского окна:

    TMyWindow::TMyWindow(PTWindowsObject AParent, LPSTR ATitle) :
                         TWindow(AParent, ATitle);
    {
       AssignMenu(100);
       AChildWindow = new TChildWindow(this, "Child Title");
       AChildWindow->Attr.Style |= WS_POPUP | WS_CAPTION;
       AChildWindow->Attr.X = 100;
       AChildWindow->Attr.Y = 100;
       AChildWindow->Attr.W = 300;
       AChildWindow->Attr.H = 300;
    ...
    }
    !!! Заметим, что вы не можете напрямую установить компоненту Attr.Menu,
вместо этого используется функция-компонента AssignMenu.

    Создание оконных элементов

    Создание оконных элементов является процессом построения видимых
элементов, связанных с оконным объектом. Вы осуществляете этот вызовом
функции-компоненты объекта прикладная программа MakeWindow, передавая
оконный объект в качестве параметра:

    if (GetApplication() -> MakeWindow(AWindow))
       // успешное создание
    else
       // создание не удалось

    !!! GetApplication() возвращает указатель на объект прикладная
программа.
    MakeWindow вызывает две важные функции-компоненты. Первая, ValidWindow,
проверяет, был-ли оконный объект сконструирован успешно. Если
конструирование по какой-либо причине не удалось, MakeWindow удаляет
оконный объект и возвращает NULL. Если конструирование прошло успешно, то
MakeWindow вызывает функцию-компоненту оконного объекта Create.
Функция-компонента Create указывает Windows на создание видимого элемента.
Если Create заканчивается ошибкой, то MakeWindow удаляет оконный объект и
возвращает NULL. В противном случае, она возвращает указатель на оконный
объект.
    Хотя оконный элемент и создается только одной функцией, мы не
рекомендуем вам создавать его явным вызовом Create. Главное окно прикладной
программы создается автоматически при ее старте, вызовом
TApplication::InitInstance. Все остальные окна приложения являются
дочерними окнами главного окна, явными или неявными, и они обычно создаются
в функции-компоненте SetupWindow их объектов родительских окон. Окна могут
быть созданы динамически, "на лету", вызовом MakeWindow.

    Обобщение информации о создании и инициализации

    При проектировании нового оконного класса вы можете определить его
конструктор для установки атрибутов окна и конструирования любых дочерних
оконных объектов. (Атрибуты окна могут также устанавливаться в конструкторе
родительского окна.)
    GetApplication()->MakeWindow создает видимый элемент оконного объекта
вызовом функции-компоненты окна Create. Если WS_VISIBLE присутствует в
наборе стилей в его компоненте данных Attr.Style, то окно после создания
отображается на экране. После этого, вызывается функция-компонента окна
SetupWindow, которая, по умолчанию, создает его дочерние окна.
    Конструктор родительского окна обычно конструирует его дочерние окна.
Атрибуты оконного объекта обычно устанавливаются его конструктором, или
конструктором его родительского окна. Поскольку главное окно прикладной
программы не имеет родителя, объект прикладная программа конструирует и
создает все стартовые установки приложения.

         Регистрация оконного класса

    Вы уже знаете, что во время инициализации оконного объекта вы можете
установить атрибуты окна, такие как стиль и местоположение, путем
заполнения компоненты данных объекта Attr. Так как эти атрибуты
используются при создании соответствующего оконного элемента, они
называются атрибутами создания.
    !!! Вы можете обновить меню окна вызовом функции-компоненты AssignMenu.
    Существуют и другие атрибуты, включающие цвет фона, представление
иконки и курсора "мыши". Для просмотра этих атрибутов, запустите Windows
сразу с несколькими различными типами прикладных программ. При перемещении
курсора "мыши" от одной прикладной программы к другой, курсор может
изменяться от стрелки (обычный) до широкого (для электронных таблиц) или
тонкого (для графических программ) перекрестия.
    Эти "другие" атрибуты окна (включая его иконку) задаются идентификацией
класса Windows окна при его создании. Атрибуты, определяемые для класса
Windows, в отличии от атрибутов создания, не могут быть изменены от окна к
окну, и сохраняются для всех окон одного класса Windows.
    Внимание !!!
    Класс Windows окна не является классом оконного объекта ObjectWindows.
Каждый экземпляр оконного класса ObjectWindows, однако, разделяет тот же
самый класс Windows.
    Класс Windows обязательно должен быть зарегистрирован в Windows до
создания окна этого класса. ObjectWindows сама заботится о требуемой
регистрации при создании окна. ObjectWindows также поддерживает класс
Windows по умолчанию с атрибутами регистрации по умолчанию. Если вы не
хотите менять какие-либо из атрибутов по умолчанию, то вам вообще не надо
заботится о регистрации.
    Если вы изменяете хотя-бы один из этих атрибутов для одного из ваших
окон, вы должны ассоциировать это окно с новым, определенным вами классом
Windows. Для этого, вы переопределяете две виртуальные функции-компоненты
вашего окна: GetWindowClass и GetClassName. В GetWindowClass вы задаете
требуемые атрибуты регистрации. Функция GetClassName просто возвращает имя
(LPSTR) оконного класса. ТWindows определяет функцию-компоненту
ПуеСдфыыТфьу, возвращающую "OWLWindow", имя оконного класса по умолчанию.
    Для определения оконного класса IBeamWindow, который использует курсор
в форме I-beam, а не стандартную стрелку, переопределим наследуемую
функцию-компоненту GetClassName:

    LPSTR IBeamWindow::GetClassName()
    {
       return "IBeamWindow";
    }

    Возвращаемое имя класса может совпадать, а может и не совпадать, с
действительным именем класса. Это не имеет значения.
    !!! Возвращаемое имя класса используется как идентификатор класса
Windows при его регистрации в Windows.
    Функция-компонента GetWindowClass использует структуру WNDCLASS как
ссылочный аргумент и заполняет ее поля новыми атрибутами регистрации. Вы
должны вызвать в начале функцию-компоненту вашего базового класса
GetWindowClass, а затем изменить установку полей в требуемые значения. В
следующем примере вызывается ТWindow::GetWindowClass и затем,
переустанавливается поле hCursor, содержащее дескриптор ресурса иконки.

    void IBeamWindow::GetWindowClass(WNDCLASS& AWndClass)
    {
      TWindow::GetWindowClass(AWndClass);
      AWndClass.hCursor = LoadCursor(NULL, IDC_IBEAM);
    }

    IDC_BEAM является константой, представляющей одну из форм курсора в
Windows. На Рисунке 10.2 показан результат работы программы, использующей
объект IBeamWindow.

    Рисунок 10.2 Программа, использующая класс IBeamWindow.

    Атрибуты регистрации

    По умолчанию, ObjectWindows определяет класс ObjectWindows, который
используется для экземпляров ТWindow. Атрибуты регистрации "OWLWindow"
включают : пустую иконку, курсор в форме стрелки и стандартный цвет окна.
Чтобы изменить этих характеристики для вашего класса, производного от
ТWindow, вы должны переопределить его функцию-компоненту GetWindowClass.
GetWindowClass передается по ссылке структура WNDCLASS, которую вы должны
заполнить атрибутами регистрации.
    Ниже приводится таблица с некоторыми важными полями структуры WNDCLASS
и их значениями по умолчанию, в которые они устанавливаются
TWindow::GetWindowClass.

---------------------------------------------------------------
 Характеристика     Поле           Значение по умолчанию
---------------------------------------------------------------
 Стиль класса       Style          CS_HREDRAW | CS_VREDRAW
 Иконка             hIcon          LoadIcon(0, IDI_APPLICATION)
 Курсор             hCursor        LoadCursor(0, IDC_ARROW)
 Цвет фона          hbrBackground  (HBRUSH)(COLOR_WINDOW+1)
 Меню по умолчанию  lpszMenuName   NULL
---------------------------------------------------------------
    Таблица 10.2 Атрибуты регистрации окна

    Компонента стиля класса

    Компонента Style структуры WNDCLASS отличается от стиля окна,
задаваемого как атрибут создания оконного объекта в Attr.Style. Attr.Style
используется для задания видимого внешнего вида создаваемого окна, и
устанавливается константами WS_("стиль окна").
    Компонента Style структуры WNDCLASS используется для задания
определенного поведения окна после его создания, и устанавливается
константами CS_("стиль класса"). Например, при установке константы
CS_HREDRAW, при изменении горизонтального размера окна осуществляется его
полная перерисовка. Установка CS_NOCLOSE, блокирует опцию Close
управляющего меню. Компонента Style может содержать одну константу CS_ или
их комбинацию. Полный список констант CS_ приводится в Главе 7 "Константы
Windows" книги " ObjectWindows для С++. Справочное руководство.".
    TWindow::GetWindowClass устанавливает эту компоненту как показано ниже:

    AWndClass.Style = CS_HREDRAW | CS_VREDRAW;

    Компонента иконки

    Компонента hIcon содержит дескриптор иконки, используемой для
представления окна в его минимизированном состоянии. Обычно, вы будете
определять ресурс иконки, представляющей главное окно вашей программы.
ТWindow::GetWindowClass устанавливает эту компоненту в дескриптор уже
созданной иконки, IDI_APPLICATIОN, имеющей вид пустого прямоугольника.

    Компонента курсора

    Компонента данных hCursor содержит дескриптор курсора, используемого
для представления указателя "мыши", позиционируемого в окно.
ТWindow::GetWindowClass устанавливает эту компоненту в дескриптор
стандартной стрелки Windows, IDC_ARROW. Некоторые другие возможные формы
курсора приведены ниже:
    - IDC_IBEAM
    - IDC_WAIT
    - IDC_CROSS
    Курсоры, как и иконки, могут определяться пользователем.

    Компонента цвета фона

    Это поле задает цвет фона окна. ТWindow::GetWindowClass устанавливает
эту компоненту в системный цвет окна по умолчанию, который может быть
установлен на Панели Управления (Control Panel). Вы можете установить эту
компоненту в дескриптор физичекой кисточки определенного цвета. И напротив,
вы можете установить ее в любой из системных цветов, такой как
COLOR_ACTIVECAPTION, установкой hbrBackground в COLOR_ACTIVECAPTION + 1.

    Компонента меню по умолчанию

    Это поле содержит указатель на имя ресурса меню, которое является общим
для всех окон этого класса Windows. Например, если вы определили тип
EditWindow, всегда имеющий стандартное меню редактирования, то вы можете
задать это меню в этом поле. Такой прием избавит вас от необходимости
задавать меню атрибутом создания в конструкторе. Если ресурс меню имеет
идентификатор ресурса 101, то вы должны установить это поле как

    AWndClass.lpszMenuName = MAKEINTRESOURCE(101);

         Прокручиваемые окна

    Вы чаще всего не сможете охватить размерами окна всю выводимую
информацию. К счастью, окна ObjectWindows могут легко программироваться для
осуществления прокрутки их содержимого. ТWindow уполномочен с помощью
объекта TScroller выполнять эту задачу.
    Пользователи обычно манипулируют линейками прокрутки на краю области
пользователя окна для прокрутки текущего изображения. (Эти линейки
прокрутки окна не являются управляющими элементами, а являются частью
самого окна, созданной для окон со стилями создания, включающим WS_VSCROLL
или WS_HSCROLL). TScroller порождает линейки прокрутки окна, обеспечивая
автоматизацию просмотра его содержимого.
    В дополнении, окно ObjectWindows может также прокручиваться при
перемещении пользователем "мыши" из области пользователя окнав его внешнюю
часть. Мы будем называть этот технический прием авто-прокруткой; он будет
работать для окон, даже не имеющих линеек прокрутки.

    Атрибуты прокрутки

    TScroller содержит значения, которые определяют размер прокручиваемого
содержимого окна. Эти значения хранятся в компонентах данных TScroller
XUnit, YUnit, XLine, YLine, XPage, YPage, XRange и YRange. Компоненты
данных, начинающиеся с Х, представляют горизонтальные значения, а
начинающиеся с Y - вертикальные значения.
    Единица прокрутки определяет крупность разбиения прокрутки, которая
является наименьшим числом единиц устройства (обычно пикселей в окне, но
зависит от режима представления отображения) прокручиваемых в
горизонтальном или вертикальном направлении. Единица прокрутки обычно
базируется на типе выводимой информации. Например, при выводе текстовой
информации со средней шириной символа 8 пикселей и высотой 15 пикселей,
удобные значения для XUnit и YUnit будут 8 и 15, соответственно.
    Другие атрибуты прокрутки - строка, страница и диапазон - задаются в
терминах единиц прокрутки. Значения строк и страниц представляют собой
число прокручиваемых единиц в ответ на запрос пользователя на прокрутку.
Запрос, инициализируемый нажатием клавиши "мыши" на одной из стрелок в
конце линейки прокрутки, прокручивает "информативные строки" окна на число
единиц, хранимых в компонентах данных строки. Нажитик клавиши на самой
линейке прокрутки (но не на кнопке прокрутки, или "бегунке") прокручивает
"информативную страницу". Атрибуты диапазонов задают общее число единиц,
которые могут прокручиваться, обычно на основе размера области окна, такой
как область редактируемого документа.
    Например, давайте взглянем на окно редактирования текста. Если вы
хотите отобразить файл, имеющий 400 строк текста, шириной 80 симовлов в
строке и 50 строк на странице, то возможные значения этих атрибутов
следующие:

---------------------------------------------------------------
 Компонента   Значение  Смысл
 данных
---------------------------------------------------------------
 XUnit           8      Ширина символа
 YUnit          15      Высота символа
 XLine,YLine     1      1 единица на строку
 XPage          40      40 символов на горизонтальную страницу
 YPage          50      50 строк на вертикальную страницу
 XRange         80      Максимальный горизонтальный диапазон
 YRange        400      Максимальный вертикальный диапазон
---------------------------------------------------------------
    Таблица 10.3 Типичные установки компонент данных окна редактирования

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

    Организации прокрутки в окне

    Для организации прокрутки в окне, сконструируйте в конструкторе вашего
окна объект TScroller, и занесите указатель на него в компоненту данных
окна Scroller. Несмотри на то, что вы сконструировали объект, имеется
возможность установить размер единиц и диапазон. Хотя линейки прокрутки не
являются обязательными для организации прокрутки, как в случае
авто-прокрутки, многие прокручиваемые окна имеют их. Для добавления их к
окну, просто добавьте в его конструкторе в компоненту данных окна
Attr.Style константу WS_VSCROLL, WS_HSCROLL, или обе. Приведем пример
конструктора для окна редактирования текста:

    TScrollWindow::TScrollWindow(LPSTR ATitle) : TWindow(NULL, ATitle)
    {
      Attr.Style |= WS_VSCROLL | WS_HSCROLL;
      Scroller = new TScroller(this, 8, 15, 80, 60);
    }
    !!! Это фрагмент программы из файла SCROLAPP.CPP.

    Конструктор TScroller берет, в качестве аргументов, прокручиваемое окно
и начальные значения для компонент данных XUnit, YUnit, XRange и YRange,
соответственно. Атрибуты строк и страниц устанавливаются в их значения по
умолчанию.
    Будучи выведенным на экран, содержимое области пользователя этого окна
может прокручиваться в вертикальном и горизонтальном положении при помощи
линеек прокрутки или авто-прокруткой. Функция-компонента этого окна Paint
просто отрисовывает графическую информацию, без знания о том, была ли
произведена прокрутка или нет. Конечно, не совсем удобно отрисовавать
большое изображение, если отображается только его часть. Функция-компонента
Paint может быть оптимизирована для рисования только части изображения,
которая будет выводится. Такой прием описывается в конце раздела.

    Пример с прокруткой

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

    #include 

    // Объявление TScrollApp, происходящего из TApplacation
    class TScrollApp : public TApplication {
    public :
      TScrollApp(LPSTR AName, HANDLE hInstance, HANDLE hPrevInstance,
                 LPSTR lpCmdLine, int nCmdShow) : TApplication(AName,
                 hInstance, hPrevInstance, lpCmdLine, nCmdShow) {};
      virtual void InitMainWindow();
    };

    // Объявление TScrollWindow, происходящего из ТWindow
    class TScrollWindow : public TWindow
    {
    public:
      TScrollWindow(LPSTR ATitle);
      virtual void Paint(HDC PaintDC, PAINTSTRUCT&, PaintInfo);
    };

    /* Конструктор для TScrollWindow, устанавливает стили прокрутки
       и конструирует объект Scroller . */
    TScrollWindow::TScrollWindow(LPSTR ATitle) : TWindow(NULL, ATitle)
    {
      Attr.Style |= WS_VSCROLL | WS_HSCROLL;
      Scroller = new TScroller(this, 8, 15, 80, 60);
    }

    /* Ответ на входящее сообщение "рисовать" перерисовкой прямоугольников.
       Заметим, что метод Scroller BeginView, который устанавливает оригинал
       демонстрационного окна относительно текущей позиции прокрутки, уже
       вызван. */
    void TScrollWindow::Paint(HDC PaintDC, PAINTSTRUCT&)
    {
      int X1, Y1, I;

      for (I = 0; I <= 49; ++I)
      {
        X1 = 10 + I*8;
        Y1 = 30 + I*5;
        Rectangle(PaintDC, X1, Y1, X1 + X1, X1 + Y1 * 2);
      }
    }

    // Конструирование MainWindow для TScrollApp типа TScrollWindow
    void TScrollApp::InitMainWindow()
    {
      MainWindow = new TScrollWindow("Boxes");
    }

    // Запуск ScrollApp
    int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR
                       lpCmdLine, int nCmdShow);
    {
      TScrollApp ScrollApp("ScrollApp", hInstance, hPrevInstance,
                       lpCmdLine, nCmdShow);

      ScrollApp.Run();
      return ScrollApp.Status;
    }
    !!! Это программа из файла SCROLAPP.CPP.

    Авто-прокрутка и отслеживание местоположения

    Объект TScroller обладает возможностью авто-прокрутки по умолчанию, но
она может быть выключена установкой компоненты данных AutoMode в FALSE.
Прокручиваемое окно делает это в своем конструкторе после конструирования
объекта TScroller:

    Scroller = new TWindows(this, 8, 15, 80, 60);
    Scroller->AutoMode = FALSE;

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

    Scroller->TrackMode = FALSE;

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

    Модификация единиц прокрутки и диапазона

    Во всех примерах, до настоящего времени, мы предполагали, что значения
единиц и диапазонов известны к моменту конструирования ТScroller. Во многих
случаях, однако, эта информация не известна или может изменяться, как
например при изменении размера выводимой информации. Поэтому, иногда
необходимо устанавливать или изменять значения диапазонов (и возможно
единиц) в более позднее время. Если вы не знаете эти значения к моменту
конструирования, вы можете передать ноль конструктору ТScroller.
    Функция-компонента SetRange имеет два аргумента типа long, число
вертикальных и горизонтальных единиц, определяющих общий диапазон
прокрутки. SetRange должна использоваться всякий раз, при изменении размера
изображения. Например, для обеспечения возможности вывода изображения 100
единиц шириной и 300 единиц высотой, эта команда должна устанавливать
следующий диапазон прокрутки:

    Scroller->SetRange(100, 300);

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

    void ScrollScrolle::SetupWindow()
    {
      TWindow::SetupWindow();
      Scroller->XUnit = 10;
      Scroller->YUnit = 20;

    Модификация положения прокрутки

    Вы можете осуществлять прокрутку вызовом двух функций-компонент
TScroller: ScrollTo и ScrollBy. Каждая из них имеет два аргумента типа
long, задаваемых в терминах горизонтальных и вертикальных единиц прокрутки.
Например, если необходимо переустановить положение прокрутки в верхний
левый угол изображения, используйте ScrollTo:

    Scroller->ScrollTo(0, 0);

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

    Scroller->ScrollTo(0, 200);

    Функция-компонента ScrollBy перемещает положение прокрутки вверх,
вниз, влево или вправо на заданное число единиц. Отрицательные значения
перемещают положение прокрутки по направлению к началу координат,
верхнему левому углу, а положительные вниз и вправо. Например, эта команда
прокручивает вправо на 10 единиц и вниз на 20:

    Scroller->ScrollTo(10, 20);

    Установка размера страницы

    По умолчанию, размер страницы (XPage и YPage) устанавливается согласно
размеру области пользователя в окне. Прокрутка извещается при любом
изменении размера окна ее владельца. Функция-компонента окна владельца
WMSize вызывает функцию-компоненту прокрутчика SetPageSize, которая
устанавливает его компоненты данных XPage и YPage в зависимости от текущего
размера области пользователя и значений XUnit и YUnit.
    Для переопределения этого механизма и установки размера страницы
напрямую, вы должны переопределить наследуемую вашим оконным объектом
функцию-компоненту WMSize:

    void TTestWindow::WMSize(RTMessage Msg)
    {
      TWindow::WMSize(msg);
    }

    Затем, вы можете устанавливать XPage и YPage непосредственно из
конструктора вашего окна (или в конструкторе класса, производного от
TScroller):

    TScrollWindow::TScrollWindow(PTWindowsObject AParent, LPSTR ATitle) :
                                 TWindow(AParent, ATitle)
    {
      Attr.Style |= WS_VSCROLL | WS_HSCROLL;
      Scroller = new TScroller(this, 8, 15, 80, 400);
      Scroller -> XPage = 40;
      Scroller -> YPage = 100;
    }

    Оптимизация функций-компонент Paint при организации прокрутки

    В предыдущем примере прикладная программа рисовала 50 прямоугольников,
но даже не делала попытки установить, являются ли все они действительно
видимыми в области пользователя. Из этого проистекают неоправданные затраты
на рисование невидимой графической информации. Функция
TScroller::IsVisibleRect может использоваться в функции-компоненте окна
Paint для оптимизации рисования.
    Функция-компонента ScrollWindow::Paint, описываемая ниже, использует
IsVisibleRect для определения необходимости вызова Windows функции
Rectangle. Rectangle использует аргументы, заданные в координатах
устройства, в то время как IsVisibleRect - в единицах прокрутки. По этой
причине, значения X1 и Y1, начало прямоугольника, и (X2-X1) и (Y2-Y1),
ширина и высота прямоугольника, должны делиться на соответствующие значения
единиц до вызова IsVisibleRect:

    void TScrollWindow::Paint(HDC PaintDC, PAINTSTRUCT& PaintInfo)
    {
      int X1, Y1, X2, Y2, I;

      for (I = 0; I <= 49; ++I)
      {
        X1 = 10 + I*8;
        Y1 = 30 + I*5;
        X2 = X1 + X1;
        Y2 = X1 + Y2 * 2;
        if (Scroller->IsVisibleRect(X1/Scroller->XUnit,
                                    Y1/Scroller->YUnit,
                                    (X2 - X1)/Scroller->XUnit,
                                    (Y2 - Y1)/Scroller->YUnit))
        Rectangle(PaintDC, X1, Y1, X2, Y2);
      }
    }

         Окна редактирования и окна работы с файлами

    ObjectWindows предоставляет два наследника ТWindow, задающие окна для
редактирования текста. Экземпляры класса TEditWindow являются окнами
редактирования текста, которые не могут читать из файла или записывать в
файл. Экземпляры класса TFileWindow, производного от TEditWindow,
определяют дополнительные возможности, необходимые для поддержки операций
файлового ввода-вывода. Вы можете использовать экземпляры этих классов в
ваших собственных прикладных программах. Или, вы можете создать специальные
текстовые редакторы, являющиеся производными от этих классов. Программы,
использующие окна редактирования или окна работы с файлами, должны включать
файлы EDITWND.H или FILEWND.H, соответственно, и обязательно включать
определенные ресурсы, поставляемые с ObjectWindows.

    Окна редактирования

    Текст в TEditWindow в действительности содержится и обрабатывается в
дочернем управляющем элементе TEdit. Это TEdit осуществляет основной объем
работы, предоставляемый ObjectWindows с расширенными возможностями
редактирования текста.
    Конструктор TEditWindow устанавливает его компоненту данных Editor в
ссылку на конструируемый им экземпляр TEdit. Его функция-компонента WMSize,
вызываемая всякий раз при изменении размеров TEditWindow, устанавливает
размеры TEdit так, что бы они охватывали область пользователя в
TEditWindow. Его компонента WMSetFocus передает фокус ввода в Editor всякий
раз, когда TEditWindow получает его.
    TEditWindow добавляет новые возможности во встроенное в TEdit основное
средство Search (Поиск). Он отвечает за большинство характеристик средств
Search и Replace (Поиски и Замена), предоставляемых пользователю
TEditWindow. В то время как TEdit знает как осуществлять поиск в хранимом в
нем тексте, TEditWindow отвечает за возможности Обновления (Replace)
текста. TEditWindow также предоставляет средство "next" ("следующи"),
посредством поддержки TSearchStruct. TEditWindow также единилично
ответственны за интерфейс Search и Replace с пользователем.
    Следующая программа, EWNDTEST.CPP, использует окно редактирования для
предоставление пользователю редактора текста для простой электронной почты.
Рисунок 10.3 показывает работу этой прикладной программы.

    Рисунок 10.3 Окно редактирования

    #include 
    #include 
    #include 
    #include "ewndtest.h"

    class TTestApp : public TApplication
    {
    public:
      TTestApp(LPSTR AName, HANDLE hInstance, HANDLE hPrevInstance,
                 LPSTR lpCmdLine, int nCmdShow) : TApplication(AName,
                 hInstance, hPrevInstance, lpCmdLine, nCmdShow) {};
      virtual void InitMainWindow();
      virtual void InitInstance();
    };

    class TTestWindow : public TEditWindow
    {
    public:
      TTestWindow(PTWindowsObject AParent, LPSTR ATitle);
      virtual void CMSendText(RTMessage Msg)
        = [CM_FIRST + CM_SENDTEXT];
    };

    TTestWindow::TTestWindow(PTWindowsObject AParent, LPSTR ATitle) :
                             TEditWindow(AParent, ATitle)
    {
      AssignMenu("COMMANDS");
    }

    void TTestWindow::CMSendText(RTMessage)
    {
      int Lines;
      char Text[20];

      Lines = Editor->GetNumLines();
      sprintf(Text, "%d lines sent", Lines);
      MessageBox(HWindow, Text, "Message Sent", MB_OK);
    }

    void TTestApp::IntMainWindow()
    {
      MainWindow = new TTestWindow(NULL, Name);
    }

    void TTestApp::InitInstance()
    {
      TApplication::InitInstance();
      HAccTable = LoadAccelerators(hInstance, "EDITCOMMANDS");
    }

    int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR
                       lpCmdLine, int nCmdShow);
    {
      TTestApp TestApp("Edit Window Tester", hInstance, hPrevInstance,
                       lpCmdLine, nCmdShow);
      TestApp.Run();
      return TestApp.Status;
    }
    !!! Это программа из файла EWNDTEST.CPP

    #include "windows.h"
    #include "editwnd.h"
    #include "owlrc.h"

    rcinclude stdwnds.dlg
    rcinclude editacc.rc

    COMMANDS MENU LOADONCALL MOVEABLE PURE DISCARDABLE
    BEGIN
      POPUP "&Edit"
      BEGIN
        MenuItem "&Undo\aAlt+BkSp", CM_EDITUNDO
        MenuItem SEPARATOR
        MenuItem "&Cut\aShift+Del", CM_EDITCUT
        MenuItem "C&opy\aIns", CM_EDITCOPY
        MenuItem "&Paste\aShift+Ins", CM_EDITPASTE
        MenuItem "&Delete\aDel", CM_EDITDELETE
        MenuItem "C&ear All\aCtrl+Del", CM_EDITCLEAR
      END
      POPUP "&Search"
      BEGIN
        MenuItem "&Find...", CM_EDITFIND
        MenuItem "&Replace...", CM_EDITREPLACE
        MenuItem "&Next\aF3", CM_EDITFINDNEXT
      END
      MenuItem "Send &Text", CM_SENDTEXT
    END
    !!! Это программа из файла EWNDTEST.RC

    Окна работы с файлами

    Конструктор TFileWindow берет имя файла в качестве аргумента.
TFileWindow::SetupWindow производит начальные установки окна работы с
файлами путем чтения содержимого этого файла в его Editor, управляющий
элемент TFile наследует его из TEditWindow. TFileWindow также
переопределяет CanClose для выдачи приглашения пользователю сохранить
текущее редактируемое содержимое, если оно есть, до закрытия окна работы с
файлами.
    TFileWindow имеет четыре функции-компоненты, которые отвечают на
запросы от пользователя на открытие и сохранение файлов: CMFileNew,
CMFileOpen, CMFileSave и CMFileSaveAs. Пользователь осуществляет эти
запросы путем выбора соответствующего пункта меню. Идентификаторы пунктов
меню приводятся в Таблице 10.4.

--------------------------------------------------
 Функция-компонента  Идентификатор меню для вызова
--------------------------------------------------
 CMFileNew           CM_FILENEW
 CMFileOpen          CM_FILEOPEN
 CMFileSave          CM_FILESAVE
 CMFileSaveAs        CM_FILESAVEAS
--------------------------------------------------
    Таблица 10.4 Функции-компоненты окна работы с файлами и идентификаторы
                 меню

    Любой из этих запросов требует задания пользователем имени файла и
используют для этого экземпляр TFileDialog, в котором пользователь может
указать диск, каталог и имя файла.
    Вы можете использовать окна работы с файлами как простые,
стандартизированные текстовые редакторы не производя в них никаких
изменений. Однако, вы заходите произвести от TFileWindow ваши собственные
новые классы, которые, например, могут изменять шрифт выводимого в Editor
текста. Обычно, такие возможности реализуются посредством спадающего меню,
которое вам надо создать. Для использования пользовательского интерфейса
для файлового ввода-вывода, предоставляемого TFileWindow, создавайте меню с
идентификаторами, приведенными в Таблице 10.4.

    Глава 11. Диалоговые объекты

    Диалогом является интерфейсный элемент, чьи атрибуты создания задаются
в файле ресурсов Windows. Атрибуты создания в определении ресурса диалога
задают внешний вид и местоположение диалога и его управляющих элементов.
    Диалоги обычно используются как дочерние окна при необходимости
получения специализированной вводной информации. Например, дочерний диалог
может использоваться для запроса у пользователя параметров печати.
ObjectWindows предоставляет три производных от TDialog класса. Эти классы
предназначены для выполнения следующих задач: TInputDialog получает от
пользователя текстовую строку, TFileDialog получает имя файла, а
TSearchDialog текст для поиска и замены в окне редактирования.

         Использование ресурсов диалога

    Диалоговый интерфейсный элемент должен обязательно иметь определение
ресурса в файле ресурсов Windows. Определение ресурса, имеющее уникальный
идентификатор, задает внешний вид и местоположение диалогового элемента и
его управляющих элементов. Определение ресурсов не задает поведение диалога.
    В приложении ObjectWindows поведение диалога определяет объект TDialog.
Его конструктор использует идентификатор определения ресурса в качестве
своего параметра. Это определение ресурса используется при создании
интерфейсного элемента, связываемого с TDialog.

         Использование дочернего диалогового объекта

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

    Конструирование и инициализация дочерних диалоговых объектов

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

    ADialog = new TSampleDialog(this, "SampleDialog");

или

    ADialog = new TSampleDialog(this, 101);

    Однако, хорошим стилем программирования считается использование
констант для задания идентификаторов:

    #define ID_SAMPLERESOURCE 101
    ...
    ADialog = new TSampleDialog(this, ID_SAMPLERESOURCE);

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

    Создание и выполнение диалогов

    Связь между дочерним диалоговым объектом и интерфейсным элементом
устанавливается при создании или выполнении TDialog.
    Дочерний диалог выполняется, когда последующая обработка зависит от
введенной пользователем информации; обработка приостанавливается до момента
получения вводимой информации или закрытия диалога. Например, закрытие окна
редактирования текста приводит к выполнению диалога, определяющего
необходимость сохранения измененного текстового файла. Дальнейшая обработка
(сохранять или не сохранять) будет зависеть от ввода пользователя.
Обработка приостанавливается (окно не закрывается) до получения ответа.
Фактически, во время выполнения диалога его родительское окно блокируется и
неспособно отвечать на ввод пользователя.
    И наоборот, дочернее окно создается, когда ввод пользователя может
повлиять на последующую обработку, но сама обработка может продолжаться и
без него. Например, окно редактирования текста может создавать диалог
поиска для получения строки поиска во время первого запроса на его
осуществление. Если он не будет явно закрыт пользователем, то тот же самый
диалог может использоваться для получения новых строк поиска во время
последующих запросов, при этом, продолжается обычная обработка
(редактирование текста). Созданный диалог не блокирует свое родительское
окно.
    Выполнение диалога отличается от создания диалога и еще одной деталью:
При выполнении диалога, он возвращает значение, указывающее на выбранную
пользователем опцию. Простые диалоги часто выводят вопросительные
приглашения, и имеют кнопки ОК и Cancel. Когда пользователь нажимает одну
из кнопок, диалог завершает работу и, как результат своей работы, возвращает
идентификатор нажатой кнопки.
    Windows, однако, позволяет возвращать выполняемым диалогам только
целочисленные значения. Для получения более сложной информации, вы должны
изменить предоставляемый ObjectWindows механизм передачи данных, или
передавать и заполнять определяемый вами в производном классе диалога
собственный буфер передачи информации.
    !!! Смотри раздел "Передача данных управляющих элементов" в Главе 12.

    Модальные и немодальные диалоги

    Диалог, который исполняется, называется модальным диалогом; диалог,
который создается, называется немодальным. TDialog может связываться с
модальным или немодальным диалогом посредством вызова его функций-компонент
Execute и Create, соответственно. Однако, эти функции-компоненты не должны
вызываться напрямую. Для безопасного выполнения модального диалога
вызывается функция-компонента объекта прикладная программа ExecDialog; для
безопасного создания немодального диалога вызывается метод MakeWindow.
ExecWindow и MakeWindow наследуются из TModule.

    // Выполнение модального диалога
    if (GetModule()->ExecDialog(new TSampleDialog(this,
                                "SampleDialog")) == IDOK)

      // сделать что-то

    // Создать немодальный диалог
    GetModule()->MakeWindow(new TSampleDialog(this, "SampleDialog"));

    Закрытие дочернего диалога

    !!! Смотри раздел "Передача данных управляющих элементов" в Главе 12.
    Закрытие модального или немодального диалога осуществляется вызовом его
функции-компоненты CloseWindow (выполняется функцией-компонентой ответа на
сообщение Ok). CloseWindow закрывает диалог, если CanClose разрешает
сделать это. CloseWindow для модального диалога, по умолчанию, передает его
данные. Если вы хотите закрыть диалог безусловно (без получения разрешения
от CanClose), вызывайте ShutDownWindow (выполняется функцией-компонентой
ответа на сообщение Cancel).
    !!! Смотри раздел "Передача данных" в Главе 12.
    В большинстве случаев вы не будете переопределять ShutDownWindow, но,
возможно, вы захотите переопределить CloseWindow, чтобы заставить
немодальный диалог вызывать TransferData. Также вы можете переопределить
CanClose для проверки данных перед закрытием. Например, если текст в
редактируемом управляющем элементе является недопустимым, то выдается блок
сообщения.
    Для модального диалога, значение, передаваемое CloseWindow или
ShutDownWindow, становится значением возврата Execute, а так же
возвращается TModule::ExecDialog. Если не задается никакого значения, то по
умолчанию возвращается значение IDCANCEL. Для немодальных диалогов вам не
надо передавать значение, поскольку немодальный диалог не имеет
возвращаемого значения.

    Использование диалога как главного окна

    Вы можете использовать немодальный диалог как главное окно вашей
прикладной программы. Это может оказаться полезным, если ваше главное окно
содержит большое число управляющих элементов. При использовании диалога как
главного окна, вы сможете использовать редактор ресурсов диалога для более
простого определения атрибутов создания вашего окна и его управляющих
элементов. Используйте класс, производный от TDialog, как главное окно тем
же способом, как вы использовали бы класс, производный от ТWindow,
конструируя его в InitMainWindow. Программа CALC.CPP в каталоге EXAMPLES
использует немодальный диалог калькулятора как свое главное окно.
    Вы также можете использовать немодальный диалог как главное окно вашей
программы, если хотите отделить задание интерфейсных элементов от самой
прикладной программы. Поступая таким образом, вы можете изменять внешний
вид главного окна без внесения изменений и перекомпиляции прикладной
программы.
    Например, программа-калькулятор может иметь немодальный диалог как
свое главное окно, где кнопки калькулятора задаются как кнопочные
управляющие элементы в ресурсе диалога. Это позволяет главному окну иметь
свои меню, иконку и курсор.
    Однако, вы столкнетесь с определенными трудностями при ответе на
акселераторы прикладной программы, если в качестве ее главного окна
используется диалог. Windows не осуществляет полной поддержки акселераторов
в безмодальных диалогах. Используя некоторые хитрости, вы сможете избежать
эти ограничения. Программа CALC.CPP использует один из таких приемов.

    Определение класса Windows для вашего немодального диалога

    В разделе "Регистрация классов Windows" в Главе 10 вы уже познакомились
с концепцией атрибутов регистрации и процессом их определения для окна. В
общем, Windows требует регистрации определенных атрибутов окна, до его
создания. Эти атрибуты регистрации включают иконку и курсор. ObjectWindows
регистрирует атрибуты для ваших окон по умолчанию; для изменения этих
действий по умолчанию, переопределите GetWindowClass и GetClassName.
    Для модификации атрибутов регистрации ваших диалогов, переопределите
GetWindowClass и GetClassName, как вы это сделали бы для окна.
Дополнительно, вам необходимо задать имя класса Windows вашего диалога в
определении его ресурсов. Это имя возвращается функцией-компонентой вашего
диалога GetClassName.
    Если вы не зададите имя класса Windows в определении ресурса диалога,
то ваш диалог будет иметь атрибуты регистрации, задаваемые Windows по
умолчанию. Если же вы задали класс Windows, то вы не сможете выполнить ваш
диалог модально.

    Работа с управляющими элементами и обработка сообщений

    Windows предоставляет слабую поддержку в создании и манипулировании
управляющими элементами со стороны их родительского окна; в приложениях
ObjectWindows для выполнения этих задач предназначаются оконные и
управляющие объекты. (Глава 12, "Управляющие объекты", дает полную
информацию об управляющих элементах окна).
    Windows руководит созданием управляющих элементов диалога, а также
предоставляет некоторую поддержку в управлении ими. Поэтому, в
ObjectWindows управляющие элементы диалога, по умолчанию, не связываются с
управляющими объектами. Однако, вы можете связать управляющие элементы
вашего диалога с управляющими объектами ObjectWindows для облегчения связи
диалог/управляющий элемент.
    Диалоговый объект и его управляющие элементы имеют двунаправленную
связь. В одном направлении диалог осуществляет управление и запросы его
элементов. Например, диалогу может понадобиться заполнить блок списка
управляющего элемента, или получить текст из редактируемого управляющего
элемента. В другом направлении управляющие элементы осуществляют
уведомление диалога о происшедших событиях, которые могут его интересовать.
Например, кнопочный управляющий элемент уведомляет родительский диалог при
ее нажатии.

    Работа с управляющими элементами диалога

    Windows предоставляет для каждого типа управляющего элемента набор
управляющих сообщений и реакции на них по умолчанию. Например, ответом по
умолчанию для блока списка на сообщение LB_GETCURSEL является возврат
индекса выбранного пункта. Диалог связывается со своими управляющими
элементами посылкой управляющего сообщения, задавая идентификатор
управляющего элемента, идентификатор сообщения и параметры сообщения.
    В ObjectWindows вы посылаете сообщение управляющему элементу диалога
путем вызова функции-компоненты диалога SendDlgItemMsg, которая имеет
четыре параметра:
    - идентификатор управляющего элемента;
    - идентификатор сообщения;
    - параметр типа WORD, содержащий характеристики сообщения (связанные
данные);
    - параметр типа LONG, содержащий более специфичную информацию.
    В следующем примере вызывается функция-компонентаSendDlgItemMsg класса
TestDialog для добавления "Item 1" ("Пункт 1") в управляющий элемент блока
списка (посылкой сообщения LB_ADDSTRING).

    void TTestDialog::FillListBox()
    {
      SendDlgItemMsg(ID_LISTBOX, LB_ADDSTRING, NULL, (DWORD) "Item 1")
    }

    Вы можете облегчить связь между диалоговым объектом и его управляющими
элементами, связывая управляющие элементы с управляющими объектами,
являющимися компонентами класса диалога. (Помним, что в ObjectWindows
управляющие элементы диалога, по умолчанию, не связываются с управляющими
объектами.) После осуществления этого приема, вы сможете манипулировать
управляющими элементами, используя компоненты данных и функции-компоненты
объекта, вместо посылки сообщений интерфейсным элементам управляющих
элементов посредством функций Windows API. Это будет соответствовать
требованиям объектно-ориентированного программирования.
    Для этого сконструируйте управляющий объект в конструкторе вашего
диалога. Используйте конструктор управляющего объекта, который имеет только
два параметра: родительский объект и идентификатор ресурса. (Нет
необходимости передавать атрибуты создания, поскольку Windows уже создала
элемент). Теперь, вы можете манипулировать и запрашивать управляющие
элементы диалога как управляющие элементы окна. Ниже приводятся
изменения, вносимые в определение, конструктор и функцию-компоненту
FillListBox класса TTestDialog.

    class TTestDialog : public TDialog {
    public:
    ...
      PTListBox ListBox;
      TTestDialog(PTWindowsObject AParent, LPSTR AName);
    ...
    };

    TTestDialog::TTestDialog(PTWindowsObject AParent, LPSTR AName) :
                             TDialog(AParent, AName)
    {
       ListBox = new TListBox(this, ID_LISTBOX);
    }

    void TTestDialog::FillListBox()
    {
      ListBox->AddString("Item 1");
    }

    Ответы на сообщения уведомления управляющих элементов

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

    class TTestDialog : public TDialog {
    ...
    void HandleButtonMsg(RTMessage Msg) =
      [ID_FIRST + ID_BUTTON];
    void HandleListBoxMsg(RTMessage Msg) =
      [ID_FIRST + ID_LISTBOX];
    };

    Каждое уведомляющее сообщение от управляющего элемента приходит с
уведомляющим кодом, целочисленной константой, которая указывает на
происшедшее событие. Например, при выборе пункта в блоке списка, посылается
уведомляющее сообщение с уведомляющим кодом LBN_SELCHANGE; при нажатии
кнопки этот код BN_CLICKED. (Уведомляющий код передается в Msg.LP.Hi,
старшем слове Msg.LParam).
    Обычно, ответные действия, задаваемые в вашей функции-компоненте ответа
на сообщения, базирующихся на идентификаторе дочернего окна, зависят от
уведомляющего кода, передаваемого в Msg.LP.Hi, как показано в следующем
примере:

    void TTestDialog::HandleListBoxMsg(RTMessage Msg) {
      switch (Msg.LP.Hi)
      {
        case LBN_SELCHANGE :
          // обработка выбранного изменения
          break;
        case LBN_DBLCLK;
          // обработка двойного нажатия
          break;
      ...
      }
    }

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

    Пример связи диалог/управляющий элемент

    В следующей программе происходит порождение класса TTestDialog из
TDialog. TTestDialog определяет HandleButtonMsg и HandleListBoxMsg,
отвечающие на уведомляющие сообщения, посылаемые диалогу его кнопочным
управляющим элементом и блоком списка. HandleButtonMsg, которая вызывается
автоматически при нажатии кнопки, реагирует заполнением блока списка
 текстовыми элементами. Для этого она вызывает функцию-компоненту
диалога SendDlgItemMag. TTestDialog выполняется как модальный диалог в
функции-компоненте ExecDialog его родительского окна.

    Рисунок 11.1 Прикладная программа с диалогом

    Текст программы содержится в файле DIALTEST.CPP, находящемся в
подкаталоге EXAMPLES.

    Усложненный пример использования диалогов

    VDLGAPP.CPP определяет сложный диалог, который просит пользователя
ввести текст, и затем проверяет правильность ввода. Программа выводит блок
диалога с полями редактирования для имени сотрудника, его номера
социального страхования и идентификатора.
    Когда пользователь нажимает кнопку ОК, функция-компонента CanClose
проверяет, что допустимое имя, номер социального страхования и
идентификатор были введены до запроса на закрытие диалога. Внешний вид
диалога показан на Рисунке 11.2.

    Рисунок 11.2 Диалог, проверяющий допустимость ввода пользователя

    Текст программы содержится в файле VDLGAPP.CPP, находящемся в
подкаталоге EXAMPLES.

         Диалоги ввода

    Диалоги ввода входят в состав набора диалогов, поддерживаемых
ObjectWindows. Они выводят приглашение и возвращают одну строку введенного
пользователем текста. Такая задача является обычной для любой прикладной
программы в Windows; поэтому, вы, вероятнее всего, будете часто их
использовать.
    Внимание !!!
    Для использования диалогов ввода вы должны включить в вашу программу
файл INPUTDIA.H, а файл .RC вашей программы INPUTDIA.DLG.

    Рисунок 11.3 Диалог ввода

    Конструктор диалога ввода имеет 5 параметров:
    - указатель на родительский оконный объект;
    - указатель на текст заголовка диалога;
    - указатель на текст приглашения;
    - указатель на заполняемый пользователем текстовый буфер;
    - размер текстового буфера ввода.
    В следующем примере конструируется диалог ввода. При нажатии кнопки ОК
диалога ввода, EditText заполняется текстовой информацией, введенной
пользователем в редактируемый управляющий элемент диалога.

    PTInputDialog AnInputDialog;
    char EditText[79];
    strcpy(EditText, "");
    AnInputDialog = new TInputDialog(this, "Caption", "Prompt", EditText,
                                     sizeof(EditText));

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

    void TSampleWindow::GetName()
    {
      char EditText[79];

      strcpy(EditText, "Frank Borland");
      if (ExecDialog(new TInputDialog(this, "Data Entry", "Enter Name",
          EditText, sizeof(EditText))) == IDOK)
        MessageBox(HWindow, EditText, "Name is:", MB_OK);
      else MessageBox(HWindow, EditText, :Name is still:", MB_OK);
    }

         Файловые диалоги

    Файловые диалоги являются другим типом диалогов, поддерживаемых
ObjectWindows в классе TFileDialog. Используйте файловый диалог всякий раз,
когда необходимо задание пользователем имени файла, как например при
реализации таких функций прикладных программ как File Open, Save и SaveAs
(Открыть Файл, Сохранить и Сохранить Как).  Смотри Рисунок 11.4.

    Рисунок 11.4 Файловый диалог

    Внимание !!!
    Для использования TFileDialog вы должны включить в вашу программу
файл FILEDIAL.H, а файл .RC вашей программы FILEDIAL.DLG.
    Конструктор блока файлового диалога имеет три параметра: указатель на
родительское окно, идентификатор ресурса и имя файла или шаблон (зависит от
того, предназначен файловый диалог для открытия или сохранения файла).
Передаваемый идентификатор ресурса является идентификатором или
стандартного блока файлового диалога Open, или стандартного блока файлового
диалога Save As (SD_FILEOPEN или SD_FILESAVEAS). Для файлового диалога Open
в параметре имени файла передается файловый шаблон по умолчанию; для
файлового диалога SaveAs он содержит имя файла по умолчанию. Параметр имени
файла также часто используется обеими типами диалогов как буфер возврата
для выбранного имени файла.
    В следующем примере выполняется блок файлового диалога Open. Поскольку
конструктору файлового диалога как файловый шаблон передается строка "*.*",
то при инициализации диалога в блоке списка имен файлов будут выведены
имена всех файлов текущего каталога:

    char TempName[MAXPATH];
    ...
    _fstrcpy(TempName, "*.*");
    if (GetApplication()->ExecDialog(new TFileDialog(this, SD_FILEOPEN,
                                     TempName)) == IDOK))
    // открытие файла с именем, содержащимся в TempName
   ...

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

    char TempName[MAXPATH];
    ...
    TempName[0] = '\0';
    if (GetApplication()->ExecDialog(new TFileDialog(this, SD_FILESAVE,
                                     TempName)) == IDOK))
    // сохранение в файл с именем, содержащимся в TempName
   ...




    Глава 12

    ОБЪЕКТЫ УПРАВЛЕНИЯ

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

Таблица 12.1: Управляющие элементы Windows, поддерживаемые ObjectWindows

---------------------------------------------------------------------------
Управляющий элемент  Класс         Использование
---------------------------------------------------------------------------
Блок списка          TListBox      Прокручивающийся список элементов,
                                   например, файлов, из которого можно
                                   осуществить выбор.
Линейка прокрутки    TScrollBar    Обычная линейка прокрутки, подобная
                                   линейкам в прокручивающихся окнах и
                                   блоках списков.
Кнопка нажатия       TButton       Кнопка нажатия с соответствующим
                                   текстом.
Блок проверки        TCheckBox     Блок, который может включен или
                                   выключен, с соответствующим текстом.
Селективная кнопка   TRadioButton  Кнопка, которая может быть выбрана или
                                   нет. Обычно используется во взаимно
                                   исключающих группах.
Блок группы          TGroupBox     Статичный прямоугольник с текстом в
                                   верхнем левом углу, использующийся для
                                   группировки других управляющих
                                   элементов.
Редактируемый        TEdit         Поле для ввода текста пользователем.
управляющий элемент
Статичный            TStatic       Текстовое поле, которое не может быть
управляющий элемент                модифицировано пользователем.
Комбинированный      TComboBox     Комбинация блока списка и редактируемого
блок                               управляющего элемента.
---------------------------------------------------------------------------

    Рисунок 12.1 показывает представление на экране некоторых типичных
управляющих элементов.

Рисунок 12.1: Некоторые простые управляющие элементы

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

         Использование объектов управления

    Срок службы управляющего элемента обычно соответствует сроку службы его
родительского окна; управляющие элементы обычно создаются и уничтожаются
вместе с их родительским окном. Этот режим работы автоматически
поддерживается в ObjectWindows. Каждое дочернее окно в списке дочерних окон
родительского окна создается в процессе создания родителя и уничтожается
при уничтожении родителя.

    Конструирование и создание объектов управления

    Вы будете почти всегда конструировать ваши объекты управления в
конструкторе их родительского окна, но не является необходимой поддержка
ссылок на дочерние окна, сконструированные вами как элементы данных
родительского окна. Ссылка на дочернее окно всегда может быть получена
вызовом метода ChildWithID его родительского окна (подразумевается, что вы
задали каждому дочернему окну уникальный идентификатор в параметре AnID для
конструктора всех классов, производных из TControl). Если вы имеете
дочернее окно, к которому программа часто осуществляет доступ, вы можете
найти более удобным поддержку указателя как компоненты данных его родителя.
    Конструкторы управляющих объектов обычно получают как минимум шесть
параметров: указатель на объект родительского окна, идентификатор
управляющего элемента, координаты x и y левого верхнего угла (относительно
области пользователя родительского окна), и его ширина и высота. Эти
параметры используются для установки атрибутов создания объекта управления,
которые поддерживаются в его структуре Attr. Стили, устанавливаемые
конструктором TControl в Attr.Style, включают в себя WS_CHILD, WS_VISIBLE,
WS_TABSTOP и WS_GROUP. Этот стандарт может быть изменен в порожденных
классах управляющих средств, как это имеет место для некоторых классов
управляющих элементов ObjectWindows. См. Таблицу 10.1 компонент данных
атрибутов окна.
    Вы не должны прямо вызывать Create (или GetApplication()->MakeWindow)
для каждого вашего управляющего элемента. Они будут создаваться
автоматически, когда функция-компонента SetupWindow класса TWindowObject
вызывается для родительского окна. Вы можете переопределить SetupWindow в
производном классе вашего родительского окна для выполнения процесса
установки его управляющих элементов. Если вы переопределяете SetupWindow,
убедитесь в вызове функции-компоненты SetupWindow базового класса для
создания управляющих элементов.
    Итак, вы при желании определяете компоненты данных для вашего
родительского окна для получения указателей его управляющих элементов.
Затем, вы конструируете объект управления в конструкторе родительского
окна. И наконец, вы переопределяете SetupWindow для выполнения необходимого
процесса установки управляющих элементов, если это нужно. ObjectWindows
будет управлять всем остальным.

    Уничтожение и удаление управляющих элементов

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

    Управляющие элементы и обработка сообщений

    Коммуникация между объектом окна и его объектами управления подобна в
некотором роде коммуникации между объектом диалога и его управляющими
элементами. В обоих случаях родительское окно манипулирует и опрашивает
свои управляющие элементы, а управляющие элементы посылают предупреждающие
сообщения о событиях управления родительскому окну. Родительское окно может
определять методы реакции, базирующиеся на идентификаторе дочернего окна,
для этих предупреждающих сообщений. Однако, управляющие элементы диалога не
являются по умолчанию связанными с объектами управления ObjectWindows;
объекты управления всегда связаны с управляющими элементами окна. Этот
параграф описывает коммуникацию окно/управляющий элемент. Описание
коммуникации диалог/управляющий элемент можно найти в Главе 11 "Объекты
диалогов".

    Манипулирование управляющими элементами окон

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

    ListBox->AddString("ScottsValley");

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

    PTListBox TheListBox;
    ...
    TheListBox = (PListBox)ChildWithID(ID_LISTBOX);
    TheListBox->AddString("Scotts Valley");

    Реакция на предупреждающие сообщения управляющих элементов

    Событие, вызванное управляющим элементом окна, имеет результатом
посылку управляющим элементом предупреждающего сообщения управляющего
элемента окну (см. "Реакция на события управления" в Главе 6). Во многих
случаях родительское окно обрабатывает предупреждающие сообщения
управляющего элемента в методах реакции, базирующихся на идентификаторе
дочернего окна, как показано в следующем примере:

    class TSampleWindow : public TWindow {
    public;
      PTListBox ListBox;
      PTButton Button1, Button2;

      virtual void HandleListBoxMsg(RTMessage MSG) =
        [ID_FIRST + ID_LISTBOX];
      virtual void HandleListButton1Msg(RTMessage MSG) =
        [ID_FIRST + ID_BUTTON1];
      virtual void HandleListButton2Msg(RTMessage MSG) =
        [ID_FIRST + ID_BUTTON2];
    }

    В ваших функциях-компонентах реакции Msg.LP.Hi содержит код
предупреждения управляющего средства, такой как LBN_SELCHANGE и BN_CLICKED.
Напишите реакцию для обработки важных кодов предупреждений:

    void TSampleWindow::HandleListBoxMsg(RTMessage Msg)
    {
      switch (Msg.LP.Hi) {
        case LBN_SELCHANGE:
          // Выбор обработки изменения
          break;
        case LBN_DBLCLK:
          // Выбор обработки двойного щелчка
          break;
      }
    }

    Однако, бывают случаи, когда вы хотите, чтобы сам объект управления
реагировал на предупреждающие сообщения управляющих элементов, и средство
реакции находилось в самом управляющем элементе. Например, вы можете
захотеть создать кнопку, которая изменяет содержащийся текст, когда
пользователь нажимает ее, или редактируемый управляющий элемент, который
позволяет вводить только цифры. В этих особых случаях вам нужно, чтобы
класс управляющих элементов поддерживал желаемое средство, а не
дублирование этого средства в каждом родительском окне. Результатом
является стандартный класс, который может использоваться несколько раз в
разных прикладных программах.
    Для программирования реакции управляющего элемента не его собственные
предупреждающие сообщения, определите функцию-компоненту реакции,
основанную на предупреждении, в классе, порожденном TControl, как показано
ниже:

    class TSpecializedListBox : public TListBox {
    public;
      virtual void LBNSelChange(RTMessage Msg) =
        [NF_FIRST + LBN_SELCHANGE];
      virtual void LBNDblClick(RTMessage Msg) =
        [NF_FIRST + LBN_DBLCLICK];
    }

    Обязательно используйте сумму NF_FIRST и кода предупреждения
управляющего элемента в расширение описания функции для ваших
функций-компонент реакции, базирующихся на предупреждениях.
    Описание TSpecializedListBox::LBNSelChange следующее:

    void TSpecializedListBox::LBNSelChange(RTMessage Msg)
    {
      // Выбор обработки изменения
    }

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

         Фокус управления и клавиатура

    Использовали ли вы когда-нибудь клавиатуру для переключения фокуса
ввода от управляющего элемента к управляющему элементу в блоке диалога?
Клавиша Tab может использоваться для циклического перехода от управляющего
элемента к управляющему элементу (в порядке создания). Кроме того, для
переключения фокуса между селективными кнопками внутри блока группа. В
ObjectWindows этот клавиатурный интерфейс может эмулироваться для
управляющих элементов ваших окон. Для разрешения функционирования этого
"клавиатурного управления" вызывайте функцию-компоненту EnableKBHandler
вашего окна в его конструкторе.

         Управляющие элементы блоков списков

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

    Конструирование и создание блоков списков

    Один из конструкторов TListBox принимает в качестве параметров
родительское окно, идентификатор и размеры X, Y, W и H управляющего
элемента. Например, в конструкторе родительского окна может возникнуть
следующее утверждение:

    ListBox = new TListBox(this, ID_LISTBOX, 20, 20, 340, 100);

    Конструктор TListBox вызывает конструктор TControl, затем добавляет
LBS_STANDARD к стандартным стилям, заданным для блока списка в Attr.Style.
LBS_STANDARA является комбинацией стилей, указывающей, что блок списка
будет

* располагать элементы в алфавитном порядке (LBS_SORT)
* предупреждать родительское окно о возникновении события блока списка
(LBS_NOTIFY)
* иметь рамку (WS_BORDER)
* иметь вертикальную линейку прокрутки (WS_VSCROLL)

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

    ListBox = new TListBox(this, ID_LISTBOX, 20,20, 340, 100);
    ListBox->Attr.Style &= LBS_SORT;

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

    Модификация блоков списков

    После того, как вы создадите блок списка, его нужно наполнить списком
элементов, которые должны быть строками или owner-drawn. Позже, вы можете
захотеть добавить или удалить элементы, или полностью очистить список. Для
заполнения элементами или добавления элементов вызывайте функцию-компоненту
AddString блока списка:

    ListBox->AddString("Item 1");
    ListBox->AddString("Item 2");
    ListBox->AddString("Item 3");
    ListBox->AddString("Item 4");
    ListBox->AddString("Item 5");
    ListBox->AddString("Item 6");

    Если AddString завершилась неуспешно, она возвращает отрицательное
значение.
    Блок списка сохраняет список строк подобно массиву строк; они оба
сохраняют их элементы по индексу. Если ListBox был массивом, "Item1"
сохранялся бы в ListBox[0], "Item 2" в ListBox[1], "Item 3" в ListBox[2] и
т. д. Каждый раз, когда вы вызываете AddString, заданная строка добавляется
или в алфавитном порядке по умолчанию, или в конец списка, если стиль
исключает LBS_SORT.
    Независимо от сортировки вашего блока списка, вы можете также вставлять
новый элемент по заданному индексу с помощью InsertString:

    ListBox->InsertString("Item 1.5", 1);

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

Индекс  Элемент
------------------
 0      "Item 1"
 1      "Item 1.5"
 2      "Item 2"
 3      "Item 3"
 4      "Item 4"
 5      "Item 5"
 6      "Item 6"

    См. Рис. 12.2.

Рис. 12.2 Заполненный блок списка

    Вставка новой строки по индексу -1 добавляет строку в конец списка.
    Для удаления элемента используйте DeleteString. Следующий вызов
функции-компоненты удаляет строку по индексу 1 ('Item 1.5') и перемещает
строки по индексам, большим 1, на одну позицию (в меньшую сторону):

    ListBox->DeleteString(1);

    Наконец, ClearList удаляет каждую строку в блоке списка:

    ListBox->ClearList(1);

    Запросы к блокам списков

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

* GetCount возвращает число элементов списка.
* FindString и FindExactString исследуют блок списка на содержание заданной
строки, начиная с заданного индекса, переходя к началу списка при
необходимости. FindString ищет строку в блоке списка, которая содержит
заданную строку, а FindExactString ищет строку, начинающуюся с заданной
строки.
* GetString получает строку, находящуюся в списке по индексу, заданному
целочисленным аргументом. Аргумент типа LPSTR указывает на буфер для
получения строки. GetString также возвращает целое значение, представляющее
длину строки. * GetStringLen просто возвращает длину строки по заданному
индексу, но не возвращает саму строку. * GetSelIndex возвращает индекс
текущей выбранной строки только для блоков списков с единственным выбором.

    Получение выборов из блока списка

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

Таблица 12.2 Предупреждающие сообщения блоков списков

WParam         Операция
---------------------------------------------------------------------------
LBN_SELCHANGE  Выбор был сделан одинарным щелчком мыши.
LBN_DBLCLK     Выбор был сделан двойным щелчком мыши.
LBN_SETFOCUS   Пользователь передал фокус блоку списка щелчком или двойным
               щелчком на элементе или с помощью клавиши Tab. Предшествует
               LBN_SELCHANGE и LBN_DBLCLK.

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

Фрагмент программы LBOXTEST.CPP

    void TSampleWindow::HandleListBoxMsg(RTMessage Msg)
    {
      int Idx;
      char Str[10];

      if (Msg.LP.Hi == LBN_SELCHANGE)
      {
        Idx = ListBox->GetSelIndex();
        if (ListBox->GetStringLen(Idx) <= sizeof(Str))
        {
          ListBox->GetSelString(Str, sizeof(Str));
          MessageBox(HWindow, Str, "You select:", MB_OK);
        }
      }
    }

    Пользователь сделал выбор, если старшее слово LParam содержит
предупреждающий код LBN_SELCHANGE. Если это так, определяется длина
выбранной строки, проверяется, поместится ли она в буфер Str, возвращается
строка, и эта строка будет показана в блоке сообщений (см. Рис. 12.3).

Рис. 12.3  Реакция на выбор пользователем элемента блока списка

    Выбранные элементы обрабатываются четырьмя функциями-компонентами
TListBox: GetSelString, GetSelIndex, SetSelString и SetSelIndex.
Функции-компоненты Get... получают строку и индекс выбранной строки.
Функции-компоненты Set... обходят пользователя и устанавливают выбор
некоторого элемента заданием строки или индекса. Это заставляет элемент
появится в области видимости, если его там нет.
    Программа LBoxTest создает окно с блоком списка. Когда запускается
прикладная программа, блок списка появляется в главном окне. Когда
пользователь выбирает элемент блока списка, появляется диалог с
представленным элементом списка. Главное окно поддерживает ссылки на
управляющий элемент блока списка в компоненте данных ListBox для облегчения
манипулирования управляющего элемента.
    Полный файл LBOXTEST.CPP можно найти в поддиректории EXAMPLES.

         Комбинированные блоки

    Управляющий элемент комбинированного блока является комбинацией двух
других управляющих элементов: блока списка и редактируемого управляющего
элемента. Это используется для получения пользовательского ввода в одной из
двух форм: выбор в блоке списка или текст в редактируемом управляющем
элементе.
    TComboBox порожден из TListBox и наследует его функции-компоненты для
модификации, запросов и выбора элементов списка. Кроме того, TComboBox
предоставляет функции-компоненты для манипуляций с частью списка
комбинированного блока, которая в некоторых случаях может "выпадать" по
требованию.

    Три разновидности комбинированных блоков

    Существуют три типа комбинированных блоков: простые, выпадающие и
выпадающие списковые. Рис. 12.4 показывает, как выглядят три типа
комбинированных блоков, и как выглядит блок списка.

Рис. 12.4  Три типа комбинированных блоков и блок списка

    Простые комбинированные блоки

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

    Выпадающие комбинированные блоки

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

    Выпадающие списковые комбинированные блоки

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

    Выбор типов комбинированных блоков

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

Рис. 12.5  Выпадающий списковый комбинированный блок

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

    Конструирование комбинированных блоков

    Конструктор TComboBox использует обычные параметры конструктора
управляющего элемента: указатель на родительское окно, идентификатор
управляющего элемента и данные местоположения и размера. Кроме того,
конструктор TComboBox использует параметры AStyle и ATextLen.
    В параметре AStyle в конструктор комбинированного блока передается один
из стандартных стилей комбинированных блоков Windows (CBS_SIMPLE,
CBS_DROPDOWN или CBS_DROPDOWNLIST). Компонента данных Attr.Style в
комбинированном блоке устанавливается с использованием параметра AStyle и
дополнительных стандартных стилей, как показано ниже:

    Attr.Style = WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP |
                 CBS_SORT | CBS_AUTOHSCROLL | WS_VSCROLL | AStyle;

    В следующем примере программы компонента данных Attr.Style в
комбинированном блоке модифицируется так, чтобы комбинированный блок не
имел сортировки:

    Combo3 = new TComboBox(this, ID_COMBO3, 190, 160, 150, 100,
                           CBS_DROPDOWNLIST, 40);
    Combo3->Attr.Style &= ~CBS_SORT;

    Параметр ATextLen является максимальной длиной области редактируемого
управляющего элемента в комбинированном блоке.

    Модификация комбинированных блоков

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

    Пример приложения: CBoxTest

    Программа CBoxTest производит приложение, показанное на Рис. 12.4.
Используются все три типа комбинированных блоков. Combo1 является простым
комбинированным блоком, Combo2 - выпадающим комбинированным блоком, а
Combo3 - выпадающим списковым комбинированным блоком.
    Щелчок на кнопках Show и Hide заставляет представляться и скрываться
список правого нижнего комбинированного блока, Combo3, с помощью вызова
функций-компонент ShowList и HideList.
    Полный файл CBOXTEST.CPP можно найти в поддиректории EXAMPLES.

         Статичные управляющие элементы

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

Рис. 12.6  Статичные управляющие элементы

    Конструирование статичных управляющих элементов

    Так как статичные управляющие элементы не посылают предупреждающих
сообщений и не обрабатываются программно обычным образом с помощью
идентификаторов, они не нуждаются в уникальных идентификаторах. По
общему соглашению, "безразличным" идентификатором управляющих элементов,
часто используемым для статичных управляющих элементов, является -1.
    Кроме обычных для управляющих элементов параметров конструктора,
конструктор TStatic использует один дополнительный параметр, который
содержит длину текста, передаваемого объектами TStatic. Описание передачи
данных и сведения о роли компоненты данных TextLen в TStatic можно найти в
параграфе "Передача данных управляющих элементов" (в конце этой главы).
    Конструкторы TStatic вызывают конструктор TControl, а затем добавляют в
компоненту данных Attr.Style стиль SS_LEFT (для выравнивания текста слева)
и удаляют стиль WS_TABSTOP. Для изменения стилей, установленных по
умолчанию для вашего статичного управляющего элемента, модифицируйте
Attr.Style:

    AStatic = new TStatic(this, -1, "&Text", 20, 50, 200, 24, 6);
    AStatic->Attr.Style = AStatic->Attr.Style & ~SS_LEFT | SS_CENTER;

    Если вам не нужно изменять стили вашего статичного управляющего
элемента после конструирования, вам не нужно поддерживать ссылку на него
как локальную переменную. Если, как это обычно бывает, вам не нужно
манипулировать статичным управляющим элементом после его создания, вам не
нужно поддерживать ссылку на него как компоненту данных в его родительском
окне.
    Единственная возможная опция статичного управляющего элемента это
подчеркивание одного или нескольких символов в текстовой строке. Реализация
и эффект этого подобен подчеркиванию первого символа пункта меню: Вы
вставляете символ & в строку непосредственно перед символом, который нужно
подчеркнуть. Например, для подчеркивания буквы T в слове Text, пошлите в
вызове конструктора строку "&Text". Если вы хотите использовать в строке
символ &, используйте статичный стиль Windows SS_NOPREFIX (см. Рис. 12.6).

    Запросы к статичным управляющим элементам

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

    Модификация статичных управляющих элементов

    TStatic имеет две функции-компоненты для изменения текста статичного
управляющего элемента. Функция SetText устанавливает статичный текст,
передаваемый аргументом типа LPSTR. Clear удаляет статичный текст. Однако,
вы не можете изменить текст статичного управляющего элемента, созданного со
стилем SS_SIMPLE.

    Пример: Приложение StatTest

    Приложение STATTEST.CPP создает статичное тестовое приложение,
представленное на Рис. 12.6. Заметим, что такие обозначения, как "Default
Static" и "SS_SIMPLE", являются статичными управляющими элементами, также
как и "Sample Text" и черный и серый прямоугольники.
    Полный файл STATTEST.CPP можно найти в поддиректории EXAMPLES.

         Редактируемые управляющие элементы

    Редактируемые управляющие элементы это прямоугольные управляющие
элементы (имеющие или неимеющие видимую рамку), которые используются для
получения текста, вводимого пользователем. Текст в редактируемых
управляющих элементах может набираться пользователем, а также
модифицироваться программой. Некоторые операции над редактируемым
управляющим элементом (такие как Вырезание (Cut), Копирование (Copy) и
Вклейка (Paste)) посылают и получают текст из Буфера информационного обмена
(Clipboard). Поддерживаются следующие операции:

* Ввод текста пользователем
* Динамическое представление текста приложением
* Вырезание, копирование и вклейка текста в Буфер информационного обмена
* Редактирование нескольких строк (полезно в редакторах текста)

    Windows предоставляет редактируемые управляющие элементы с возможностью
реагировать на различные события, вызываемые пользователем. Например, когда
вы осуществляете двойной щелчок мыши на слове в редактируемом управляющем
элементе, слово выбирается; когда вы нажимаете клавишу Del, символ в
позиции курсора удаляется. ObjectWindows расширяет возможности
редактируемых управляющих элементов Windows и предоставляет различные
режимы редактирования текста.
    Рис. 12.7 представляет окно, содержащее два редактируемых управляющих
средства.

Рис. 12.7  Окно с редактируемыми управляющими элементами

    Конструирование редактируемых управляющих элементов

    Конструктор TEdit использует обычные параметры конструктора
управляющего элемента: указатель на родительское окно, идентификатор
управляющего элемента и данные местоположения и размера. Кроме того,
конструктор TEdit использует параметр Multiline типа BOOL и параметр AText
типа LPSTR. Multiline указывает, создавать или не создавать управляющий
элемент с редактированием нескольких строк. Другой параметр, ATextLen,
содержит максимальное число символов, которые могут быть введены в
редактируемый управляющий элемент. Так как текст должен включать
завершающее пустое значение, число символов, которые можно ввести в
редактируемый управляющий элемент, в действительности на единицу меньше
переданного параметра ATextLen.
    Управляющий элемент, редактирующий одну строку, и управляющий элемент,
редактирующий несколько строк, конструируются в следующем примере
программы. Пользователь может ввести максимум 39 символов в оба этих
управляющих элемента:

    SingleLineEdit = new TEdit(this, ID_SEDIT, "Default Text",
                               20, 50, 150, 30, 40, FALSE);
    MultiLineEdit = new TEdit(this, ID_MEDIT, "Default Text",
                              20, 50, 150, 30, 40, TRUE);

    Следующие стандартные стили устанавливаются для всех редактируемых
управляющих элементов: WS_CHILD, WS_VISIBLE, WS_TABSTOP, ES_LEFT,
ES_AUTOHSCROLL и WS_BORDER. Если параметр Multiline имеет значение TRUE,
управляющий элемент может редактировать несколько строк и имеет
дополнительные стандартные стили: ES_MULTILINE, ES_AUTOVSCROLL, WS_HSCROLL
и WS_VSCROLL.
    После того, как редактируемый управляющий элемент был сконструирован,
его стиль может быть изменен:

    BorderlessEdit = new TEdit(this, ID_SEDIT, "Default Text",
                               20, 50, 150, 30, 40, FALSE);
    BorderlessEdit->Attr.Style &= ~WS_BORDER;

    Эти операторы будут иметь результатом редактируемый управляющий
элемент, представленный на Рис. 12.8, где вызвана функция-компонента Create
для редактируемого управляющего элемента.

Рис. 12.8 Редактируемый управляющий элемент создается без рамки

    Следующая таблица приводит некоторые общие стили редактируемых
управляющих элементов:

Таблица 12.3  Общие стили редактируемых управляющих элементов

Стиль           Результат
---------------------------------------------------------------------------
ES_LEFT         Выравнивание слева
ES_CENTER       Выравнивание по центру
ES_RIGHT        Выравнивание справа
ES_MULTILINE    Управляющий элемент, редактирующий несколько строк
ES_UPPERCASE    Преобразование текста в заглавные символы
ES_LOWERCASE    Преобразование текста в прописные символы
ES_AUTOVSCROLL  Автоматическая вертикальная прокрутка текста в управляющем
                элементе, редактирующем несколько строк
ES_AUTOHSCROLL  Автоматическая горизонтальная прокрутка текста в
                управляющем элементе, редактирующем несколько строк
WS_BORDER       Добавить прямоугольник вокруг редактируемого управляющего
                элемента
WS_VSCROLL      Добавить вертикальную линейку прокрутки
WS_HSCROLL      Добавить горизонтальную линейку прокрутки

    Буфер информационного обмена и операции редактирования

    Вы можете прямо переносить текст между редактируемым управляющим
элементом и Буфером информационного обмена Windows, используя следующие
функции-компоненты TEdit:

* Cut
* Copy
* Paste

    Также возможны другие функции-компоненты редактирования:

* Clear (порождена из TStatic)
* DeleteSection
* Undo

    Единственной возможной дополнительной функцией-компонентой
редактирования является булевская функция-компонента CanUndo, которая
определяет, может ли быть отменено последнее изменение.
    Возможно, вы будете часто хотеть дать пользователю доступ к этим
функциям-компонентам через меню редактирования. Редактируемый управляющий
элемент автоматически реагирует на такие пункты меню, как Edit|Copy и
Edit|Undo. TEdit определяет такие функции-компоненты, базирующиеся на
командах, как CMEditCopy и CMEditUndo, которые вызываются в ответ на
некоторый пункт меню (команду) в родительском окне управляющего элемента.
CMEditCopy вызывает Copy, а CMEditUndo вызывает Undo.
    Следующая таблица перечисляет функции-компоненты, вызываемые в ответ на
выбор меню с идентификаторами меню (определенными в RCOLWLH):

Таблица 12.4  Идентификаторы меню и вызываемые ими функции-компоненты

Идентификатор меню  Вызываемая функция-компонента
---------------------------------------------------
CM_EDITCUT          CMEditCut
CM_EDITCOPY         CMEditCopy
CM_EDITPASTE        CMEditPaste
CM_EDITCLEAR        CMEditClear
CM_EDITDELETE       CMEditDelete
CM_EDITUNDO         CMEditUndo

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

    Запросы к редактируемым управляющим элементам

    Возможно, часто вы будете хотеть послать запрос редактируемому
управляющему элементу для проверки его текстового содержимого, сохранения
содержимого для дальнейшего использования или копирования содержимого в
другой управляющий элемент. TEdit поддерживает много функций-компонент для
запросов. Некоторые запросы редактируемого управляющего средства и
функции-компоненты модификаций возвращают или требуют задания вами номера
строки или позиции символа в строке. Все эти индексы начинаются с нуля.
Другими словами, первая строка это строка с индексом ноль, а первый символ
в любой строке это символ с индексом ноль. Наиболее важные
функции-компоненты это GetText, GetLine, GetNumLines и GetLineLength.
    Вы можете заметить, что функции-компоненты запросов TEdit, возвращающие
текст из редактируемого управляющего элемента, сохраняют формат текста. Это
важно только для управляющих элементов, редактирующих несколько строк,
которые позволяют тексту представляться в нескольких строках. В этом случае
возвращаемый текст, занимающий несколько строк в редактируемом управляющем
элементе, содержит два дополнительных символа для каждого конца строки:
возврат каретки (0x0D) и перевод строки (0x0A). Когда этот текст снова
вставляется в управляющий элемент, вклеивается из Буфера информационного
обмена, записывается в файл или распечатывается на принтере, концы строк
представляются также, как это было сделано в управляющем элементе. Таким
образом, когда вы используете функции-компоненты запросов, которые получают
заданное число символов, не забывайте учитывать два дополнительных символа,
используемых для конца строки.
    GetText получает текст из редактируемого управляющего элемента. Он
заполняет строку, указываемую аргументом типа LPSTR, содержанием
редактируемого управляющего элемента, включая концы строк, до числа
символов, заданного во втором параметре. Она возвращает в качестве значения
вызова число скопированных символов. Если редактируемый управляющий элемент
не содержал текста, возвращается ноль.
    Следующая процедура получает текст из редактируемого управляющего
элемента и возвращает его в аргумент TextBuffer:

    void TTestWindow::ReturnText(LPSTR TextBuffer, int BufferSize)
    {
      char TheText[20];

      if (BufferSize >= sizeof(TheText) &&
          (Edit->GetText(TheText, sizeof(TheText) != 0))
        strcpy(TextBuffer, TheText);
      else strcpy(TextBuffer, "")
    }

    GetLine более специализирована, чем GetText. В управляющих элементах,
редактирующих несколько строк, она возвращает текст в строке, заданной
целым аргументом. Строка 0 это первая строка.
    GetNumLines возвращает число строк, введенных в управляющем элементе,
редактирующем несколько строк. Вы должны использовать GetNumLines для
проверки того, сколько строк было введено в редактируемый управляющий
элемент, перед получением текста из строки с помощью GetLine.
    GetLine Length возвращает число символов в заданной строке в
управляющем элементе, редактирующем несколько строк. Если строка
существует, но не содержит символов, GetLineLength возвращает ноль. Вы
должны использовать GetLineLength, GetNumLines и GetLine вместе для
безопасного получения текста из управляющего элемента, редактирующего
несколько строк.

    void TTestWindow::ReturnLine(int LineNum, LPSTR TextBuffer,
                                 int BufferSize) {
      char TheText[20];

      if ((Edit->GetNumLines >= LinenNum) &&
          (Edit->GetLineLength(LineNum) >= sizeof(TheText)) &&
          (Edit->GetLine(TheText, sizeof(TheText)) != 0))
        strcpy(TextBuffer, TheText);
      else
        strcpy(TextBuffer, "")
    }

    GetSubText это другая функция-компонента, более специализированная, чем
GetText. Она использует аргумент типа LPSTR и два целых аргумента,
указывающих на начальный и конечный индексы - размер - текста
редактируемого управляющего элемента. Первый символ имеет индекс ноль. В
управляющем элементе, редактирующем несколько строк, индекс вычисляется
последовательно, начиная с первой строки, и далее в оставшихся стоках.
Конец строки считается за два символа. Это позволяет вашей программе
сохранять формат текста редактируемого управляющего элемента при
представлении его, печати, передаче другим редактируемым управляющим
элементам или помещении в Буфер информационного обмена.
    Вы можете спросить, где вам получить индексы для передачи в качестве
аргументов в вызове функции-компоненты GetSubText. Можно использовать
GetSelection, функцию-компоненту, возвращающую начальный и конечный индекс
текущего выбранного или выделенного текста. Обычно текст выбирается
пользователем, но он также может быть выбран программой с помощью
функции-компоненты SetSelection (см. параграф "Модификация редактируемых
управляющих элементов"). Конечный индекс, возвращаемый GetSelection, это
индекс последнего выбранного символа плюс один.
    Вы можете сохранить некоторое время обработки в прикладной программе,
которая производит обработку текста редактируемого управляющего элемента.
Функция-компонента IsModified типа BOOL возвращает TRUE, если пользователь
модифицировал текст редактируемого управляющего элемента, и FALSE в
противном случае. Возвращение FALSE может предотвратить вызов вами GetText
или GetLine. ClearModify сбрасывает флаг изменения в FALSE.
    GetLineIndex и GetLineFromPos являюся двумя функциями-компонентами,
которые вы можете использовать только для вычисления местоположения текста
в управляющем элементе, редактирующем несколько строк. GetLineIndex
возвращает число символов (включая два символа на каждый конец строки) во
всех строках перед строкой, заданной целочисленным аргументом.
Функция-компонента возвращает все редактируемые символы, если заданная
строка не существует. GetLineFromPos получает как аргумент индекс позиции
символа и возвращает соответствующий номер строки. Обе эти
функции-компоненты полезны для выяснения структуры строк и содержания
управляющего элемента, редактирующего несколько строк.

    Модификация редактируемых управляющих элементов

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

    Удаление текста

    Clear просто удаляет все содержание текста в редактируемом управляющем
элементе. Вы можете использовать ее как часть сбрасывающей операции,
удаляющей несколько или все редактируемые управляющие элементы в окне.
DeleteSection удаляет текущий выбранный текст редактируемого управляющего
элемента. Редактор полного текста может предоставлять доступ к Clear и
DeleteSection из пунктов меню (см. параграф Буфер информационного обмена и
операции редактирования"). DeleteSection это функция типа BOOL, которая
возвращает FALSE, если не было текущего выбранного текста.
    DeleteSubText подобна DeleteSection за исключением того, что она
удаляет текст между передаваемыми начальной и конечной позициями. Удаляемый
текст включает в себя символ в начальной позиции, но не включает символ в
конечной позиции.
    DeleteLine удаляет весь текст, находящийся в заданной строке. Он не
удаляет, однако, два символа, составляющие конец строки. В связи с этим он
не влияет на другие строки.

    Вставка текста

    Insert подобна Paste за исключением того, что она получает текст для
вставки из предоставляемого аргумента, а не из Буфера информационного
обмена. Она удаляет весь существующий выбранный текст, но не делает
вставленный текст выделенным. Используя Insert, вы можете реализовать
текстовый Буфер информационного обмена, управляемый вашей программой.
    SetText комбинирует действия Clear и Insert. Она удаляет все содержание
редактируемого управляющего элемента и вставляет текст предоставляемого
аргумента. Например, для переустановки экрана компонентов заказа для
системы компонентов заказа,которая получает большинство заказов из
Калифорнии, вы можете использовать

    StateField->SetText("CA");

для переустановки поля "штат".

    Принудительный выбор и прокрутка текста

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

    Пример программы: EditTest

    EditTest является программой, которая представляет главное окно,
которое служит родительским окном для двух редактируемых управляющих
элементов, двух статичных управляющих элементов и кнопки. Это окно
представлено на Рис. 12.7.
    Когда пользователь осуществляет щелчок мыши на кнопке, текст из левого
редактируемого управляющего элемента (Edit1) копируется в правый
редактируемый управляющий элемент (Edit2). В Edit2 текст преобразуется в
заглавные буквы, так как в компоненту данных Attr.Style к стандартным
стилям создания был добавлен стиль ES_UPPERCASE. Если в Edit1 не было
выбранного текста, в Edit2 копируется весь текст. Если же в Edit1 был
какой-либо выбранный текст, в Edit2 копируется только выбранный текст.
    Меню редактирования поддерживает функции редактирования несмотря на то,
в каком редактируемом управляющем элементе находится фокус ввода.
    Полный файл EDITTEST.CPP можно найти в поддиректории EXAMPLES.

         Управляющие элементы кнопок нажатия

    Кнопки нажатия (иногда называемые "командными кнопками") используются
для выполнения некоторых задач каждый раз при нажатии кнопки. Существуют
два стиля кнопок нажатия, порожденные из TButton: BS_PUSHBUTTON и
BS_DEFPUSHBUTTON. Каждый стиль может использоваться для экземпляров
TButton. Стандартные кнопки нажатия подобны кнопкам нажатия, но имеют
выделенную рамку и используются обычно для указания стандартной реакции
пользователя.
    Конструктор TButton использует обычные параметры конструктора
управляющего элемента: указатель на родительское окно, идентификатор
управляющего элемента и данные местоположения и размера. Кроме того,
конструктор TButton использует параметр IsDefaultButton типа BOOL и
параметр Text типа LPSTR. IsDefaultButton указывает, будет ли кнопка
нажатия стандартной кнопкой нажатия. Text содержит текст, представляемый в
кнопке. Например, следующие предписания конструктора родителя кнопки
конструируют кнопку с заголовком "Test Button".

    Push1 = new TButton(this, ID_BUTTON, "Test Button",
                        38, 48, 316, 24, FALSE);

    Реакция на сообщения кнопок

    Когда пользователь осуществляет щелчок мыши на кнопке нажатия,
родительское окно кнопки получает предупреждающее сообщение от кнопки.
Родительское окно может реагировать на эти сообщения с помощью определения
функций-компонент реакции, базирующихся на идентификаторе потомка.
    В следующем примере TTestWindow::HandleButtonMsg реагирует на
предупреждающие сообщения от кнопки с идентификатором ID_BUTTON
представлением блока сообщений:

    class TTestWindow : public TWindow {
    public;
      PTRadioButton Button;
      virtual void HandleButtonMsg(RTMessage Msg) =
        [ID_FIRST + ID_BUTTON];
    }
    void TTestWindow::HandleButtonMsg(RTMessage Msg)
    {
      MessageBox(HWindow, "clicked", "The button was:", MB_OK);
    }

    Единственный предупреждающий код, определенный Windows для кнопки
нажатия, это BN_CLICKED, так что код не нуждается в проверке.

         Блоки проверки и селективные кнопки

    Блоки проверки и селективные кнопки иногда обозначаются как селективные
блоки, так как они используются для получения ввода пользователя в форме
выбора. Блоки проверки и селективные кнопки обычно имеют два состояния,
выбранное и невыбранное. Специальные блоки проверки с тремя состояниями
имеют дополнительное "бледное" состояние. Когда блок проверки является
бледным, его выбор заблокирован, и блок не может быть проверен.
    Обычно, блоки проверки используются для получения вариантов выбора
(возможно, многочисленных) от пользователя, а селективные кнопки
используются для получения одного из взаимоисключающих вариантов выбора.
Например, блоки проверки могут использоваться для того, чтобы дать
возможность выбрать число загружаемых шрифтов. Селективные кнопки могут
использоваться для возвращения типа шрифта для некоторого символа.
    Блоки проверки и селективные кнопки представляются экземплярами классов
TCheckBox и TRadioButton ObjectWindows. TCheckBox порожден из TButton, а
TRadioButton порожден из TCheckBox.

    Конструирование блоков проверки и селективных кнопок

    Конструкторы TCheckBox и TRadioButton используют обычные параметры
конструктора управляющего элемента: указатель на родительское окно,
идентификатор управляющего элемента и данные местоположения и размера.
Кроме того, оба конструктора использует параметр Text типа LPSTR и параметр
AGroup типа PTGroupBox. Text содержит текст, который по умолчанию будет
показан справа селективного блока. AGroup является указателем на объект
TGroupBox, который используется для облегчения логической группировки
селективных блоков. Если AGroup имеет значение NULL, селективный блок не
является частью никакой из логических групп (см. параграф "Блоки группы"):

    GroupBox = new TGroupBox(this, ID_GROUPBOX, "A Group Box",
                             38, 102, 176, 108);
    CheckBox = new TCheckBox(this, ID_CHECKBOX, "Check Box Text",
                             235, 12, 150, 26, GroupBox);

    По умолчанию, для конструкторов TChekBox и TRadioButton устанавливаются
стили BS_AUTOCHECKBOX и BS_AUTORADIOBUTTON. В результате, Windows
автоматически переключает проверяемое состояние каждого типа блока
проверки, когда на нем осуществляется щелчок мыши. Когда на селективной
кнопке со стилем "автоматически" осуществляется щелчок, все остальные
селективные кнопки той же группы получают состояние "не выбрана". Если вы
переустановили Attr.Style, задав "неавтоматические" стили BS_CHECKBOX или
BS_RADIOBUTTON, вы становитесь ответственным за состояния ваших селективных
блоков.
    Если вы хотите, чтобы текстовые строки ваших блоков проверки и
селективных кнопок появлялись слева от блоков, перед их созданием добавьте
стиль BS_LEFTTEXT в Attr.Style:

    CheckBox->Attr.Style |= BS_LEFTTEXT;

    Запрос состояния селективного блока

    Запрос к селективному блоку это единственный путь определить его
состояние и отреагировать на это. Селективные кнопки и блоки проверки имеют
два состояния: выбрано и не выбрано. Для запроса состояния селективного
блока используйте компоненту функцию GetCheck из TCheckBox:

    MyState = CheckBox->GetCheck();

    Возвращаемое значение функции GetCheck может сравниваться с константами
BS_UNCHECKED, BS_CHECKED и BS_GRAYED для определения состояния блока.

    Модификация состояния селективного блока

    Модификация состояния селективного блока обычно осуществляется
пользователями вашей программы, а не вами. Однако, в некоторых случаях ваша
программа будет нуждаться в прямом управлении состоянием селективного
блока.
    Например, один из случаев, когда вы желаете управлять состоянием
селективного блока, это представление выборов, которые были сделаны ранее и
сохранены. Класс TCheckBox определяет четыре функции-компоненты для
модификации состояния блока проверки: Check, Uncheck, Toggle и (наиболее
важная) SetCheck.

* Check устанавливает состояние блока проверки в выбранное:

    CheckBox->Check();

* Uncheck устанавливает состояние блока проверки в невыбранное:

    CheckBox->Uncheck();

* Toggle изменяет состояние блока проверки из выбранного в невыбранное и
наоборот. В случае блока с тремя состояниями, Toggle переключает
невыбранное состояние в выбранное, выбранное в "бледное", а "бледное" в
невыбранное.

    CheckBox->Toggle();

* SetCheck дает вам полноценное управление состоянием блока проверки:

    CheckBox->SetCheck(BF_UNCHECKED);  // Делает блок проверки невыбранным
    CheckBox->SetCheck(BF_CHECKED);    // Делает блок проверки выбранным
    CheckBox->SetCheck(BF_GRAYED);     // Делает блок проверки с тремя
                                          состояниями "бледным"

    Когда селективная кнопка со стилем BS_AUTORADIOBUTTON выбирается
вызовом одной из этих трех функций-компонент, все выбранные селективные
кнопки в той же группе делаются невыбранными (см. параграф "Блоки группы").
По договоренности, одна и только одна селективная кнопка в группе должна
быть всегда выбрана.

    Реакция на сообщения блоков проверки и селективных кнопок

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

         Блоки группы

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

    Конструирование блока группы

    Конструктор TGroupBox использует обычные параметры конструктора
управляющего элемента: указатель на родительское окно, идентификатор
управляющего элемента и данные местоположения и размера. Кроме того, он
использует параметр Text типа LPSTR, который указывает заголовок блока
группы:

    GroupBox = new TGroupBox(this, ID_GROUPBOX, "A Group Box",
                             38, 102, 176, 108);

    Реакция на сообщения блока группы

    Когда происходят изменения в состоянии селективного блока, являющегося
частью групп, предупреждается его TGroupBox. Затем, TGroupBox посылает
"групповое" предупреждающее сообщение родителю. Родитель может
реагировать на сообщения определением функций-компонент реакции,
базирующихся на идентификаторе потомка, для обработки предупреждающих
сообщений блока группы.
    В примере программы GBOXTEST.CPP компонента-функция родительского окна
определяется для реакции на изменения выбора в каждой из групп селективных
кнопок.
    Файл GBOXTEST.CPP можно найти в поддиректории EXAMPLES.

    Пример программы: BtnTest

    BtnTest это программа, которая создает окно с управляющими элементами
кнопок нажатия, блоков проверки, селективных кнопок и блоков группы. Когда
прикладная программа запускается, управляющие элементы представляются в
главном окне. Когда пользователь осуществляет щелчок мыши на управляющих
элементах, прикладная программа реагирует различными способами. См. Рис.
12.9.

Рис. 12.9  Окно с различными кнопками

    Полный файл BTNTEST.CPP можно найти в поддиректории EXAMPLES.

         Линейки прокрутки

    Управляющий элемент линейки прокрутки содержит перемещающийся
прямоугольник (бегунок), который может перемещаться по всей длине линейки
прокрутки в различные позиции. Каждая позиция связана со значением в
заданном диапазоне. Управляющие элементы линеек прокрутки используются для
получения значения, которое имеет диапазон установки, от пользователя.
Например, управляющий элемент линейки прокрутки может использоваться для
получения от пользователя установки температуры или значения цвета.
    Управляющие элементы линеек прокрутки очень похожи на линейки прокрутки
окон и почти всегда неотличимы от них. Линейки прокрутки окон, управляющие
прокруткой содержимого окна, являются частью самого окна; они не являются
дочерними окнами. Windows создает линейки прокрутки окон для окон, которые
имеют стили создания WS_HSCROLL или WS_VSCROLL. В ObjectWindows управляющие
элементы линеек прокрутки создаются тем же путем, что и остальные
управляющие элементы. Вам нужно только сконструировать объект линейки
прокрутки в конструкторе его родителя.

Рис. 12.10  Управляющий элемент линейки прокрутки

    Конструирование объектов линеек прокрутки

    Конструктор TScrollBar использует обычные параметры конструктора
управляющего элемента: указатель на родительское окно, идентификатор
управляющего элемента и данные местоположения и размера. Кроме того, он
использует параметр IsHorizontal типа BOOL, который указывает ориентацию
(горизонтальную или вертикальную) линейки прокрутки в родительском окне.
Если для вертикальной линейки прокрутки задается ширина ноль, она
конструируется со стандартной шириной, подобно блоку списка. Тоже самое
истинно, если для горизонтальной линейки прокрутки задается высота ноль.
Следующий пример создает горизонтальную линейку прокрутки стандартной
высоты, показанную на Рис. 12.10:

    Thermometer = new TScrollBar(this, ID_THERMOMETER,
                                 20, 170, 340, 0, TRUE);

    Конструктор TheScrollBar вызывает конструктор TControl, а затем
добавляет стили SBS_HORZ или SBS_VERT к стандартному набору стилей для
линейки прокрутки. Вы можете задать дополнительные стили, как, например,
SBS_TOPALIGN, изменением компоненты данных Attr.Style линейки прокрутки.
    Рис. 12.11 показывает различные управляющие элементы линеек прокрутки.

Рис. 12.11  Окно с различными линейками прокрутки

    Диапазон линейки прокрутки устанавливается по умолчанию от 1 до 100 при
создании линейки прокрутки. Для задания другого диапазона используйте
функцию-компоненту SetRange как описано в параграфе "Модификация линеек
прокрутки".
    Двумя другими атрибутами объекта линейки прокрутки являются величина
строки и величина страницы. LineMagnitude это число позиций, на которое
будет перемещаться бегунок, когда пользователь осуществит щелчок на одной
из стрелок в концах линейки прокрутки. PageMagnitude это число позиций, на
которое будет перемещаться бегунок, когда пользователь осуществит щелчок на
одной из областей прокрутки - пространстве между бегунком и концами линейки
прокрутки. LineMagnitude и PageMagnitude по умолчанию устанавливаются в 1 и
10, соответственно. Вы можете переустановить эти значения прямой
модификацией компонент данных LineMagnitude и PageMagnitude в TScrollBar.

    Запросы линейкам прокрутки

    TScrollBar определяет две функции компоненты для запроса линейке
прокрутки: GetRange и GetPosition. Функция-компонента GetRange является
процедурой, которая использует две целочисленные ссылки. Процедура помещает
в них наибольшую и наименьшую позиции бегунка в диапазоне линейки
прокрутки.
    GetPosition это функция, которая возвращает целочисленную позицию
бегунка. Часто программы получают диапазон и позицию, а затем сравнивает
их.

    Модификация линеек прокрутки

    Модификация линеек прокрутки часто выполняется самим пользователем
программы. Однако, ваши программы также могут модифицировать линейку
прокрутки.
    SetRange это функция-компонента, использующая два целочисленных
аргумента для наименьшей и наибольшей позиции диапазона. По умолчанию,
новая линейка прокрутки имеет диапазон от 1 до 100. Вы можете захотеть
изменить этот диапазон для лучшего отражения сущности линейки прокрутки.
Для того, чтобы сделать это, надо вызвать функцию-компоненту SetRange
линейки прокрутки после того, как линейка прокрутки будет создана. Самое
лучшее место для того этого - функция-компонента SetupWindow ее
родительского окна. Убедитесь в том, что перед тем, как вызвать SetRange,
вы вызываете функцию-компоненту SetupWindow родительского базового класса
для создания управляющего элемента линейки прокрутки.
    В следующем примере родительской функцией-компонентой SetupWindow
модифицируется диапазон линейки прокрутки, используемой как термостат:

    void TParentWindowType::SetupWindow()
    {
      TWindow::SetupWindow();
      Thermometer->SetRange(32, 120);
    }

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

    Thermometer->SetPosition(78);

    Третья функция-компонента, DeltaPos, перемещает бегунок линейки
прокрутки на заданное целочисленным аргументом число позиций вверх (влево)
или вниз (вправо). Положительное целое число перемещает бегунок вниз
(вправо), а отрицательное - вверх (влево). Например, для уменьшения
значения температуры термостата на 5 градусов используйте:

    Thermometer->DeltaPos(-5);

    Реакция на события линейки прокрутки

    Когда линейка прокрутки прокручивается, ее родительское окно получает
предупреждающее сообщение линейки прокрутки. Если вы хотите, чтобы ваше
окно реагировало на события прокрутки, обрабатывайте предупреждающие
сообщения обычным способом, определением функций-компонент реакции,
базирующихся на идентификаторе потомка. Однако, предупреждающие сообщения
линеек прокрутки несколько отличаются от предупреждающих сообщений других
управляющих элементов. Они базируются на сообщениях Windows WM_HSCROLL и
WM_VSCROLL, а не на WM_COMMAND. Вы должны помнить о том, что
предупреждающие кода линеек прокрутки сохраняются в Msg.WParam, а не в
Msg.LP.Hi. Вот некоторые общие коды: SB_LINEUP, SB_LINEDOWN, SB_PAGEUP,
SB_PAGEDOWN, SB_THUMBPOSITION и SB_THUMBTRACK.
    Чаще всего, вы будете реагировать на все события линейки прокрутки
одним и тем же способом: получением текущей позиции и выполнением
соответствующего действия. Вы обычно игнорируете предупреждающий код, как в
следующем примере:

    void TTestWindow::HandleThermMsg(RTMessage)
    {
      int NewPos;

      NewPos = Thermometer->GetPosition();
     // Выполнить действия в зависимости от NewPos
    }

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

    void TTestWindow::HandleThermMsg(RTMessage)
    {
      int NewPos;

      if (Msg.WParam != SB_THUMBTRACK)
      {
        NewPos = Thermometer->GetPosition();
       // Выполнить действия в зависимости от NewPos
      }
    }

    Иногда вы можете хотеть, чтобы объект линейки прокрутки сам реагировал
на предупреждающие сообщения линейки прокрутки; для этого создайте
стандартное средство реакции в объекте линейки прокрутки. Для
программирования прямой реакции объекта линейки прокрутки на его
предупреждающие сообщения, определите для его класса функцию-компоненту
реакции, базирующуюся на предупреждении. Используйте сумму NF_FIRST и кода
предупреждения линейки прокрутки как виртуальное объявление метода. Для
того, чтобы сделать это, породите новый класс из TScrollBar:

    class TSpecializedBar : public TScrollBar
    {
    public:
      virtual void SBTop(RTMessage Msg) =
        [NF_FIRST + SB_TOP];
    };

    TSpecializedSBar::SBtop(RTMessage Msg)
    {
      TScrollBar::SBTop(Msg);
    // Заданная обработка, когда линейка прокрутки позиционирована вверху
    }

    Заметим, что функция-компонента SBtop сначала вызывает
TScrollBar::SBtop. TScrollBar::SBTop перемещает бегунок линейки прокрутки
при получении предупреждающего сообщения SB_TOP. TScrollBar определяет
функции-компоненты для реакции на соответствующие предупреждающие
сообщения:

Таблица 12.5  Функции-компоненты, определяемые TScrollBar

Функция-компонента TScrollBar   Предупреждающее сообщение
-----------------------------------------------------------
SBLineUp                        SB_LINEUP
SBLineDown                      SB_LINEDOWN
SBPageUp                        SB_PAGEUP
SBPageDown                      SB_PAGEDOWN
SBThumbPosition                 SB_THUMBPOSITION
SBThumbTrack                    SB_THUMBTRACK
SBTop                           SB_TOP
SBBottom                        SB_BOTTOM

    Если вы переопределяете какую-либо из этих функций-компонент, вызывайте
функцию-компоненту TScrollBar, которую вы переопределили для перемещения
бегунка линейки прокрутки.

    Пример: SBarTest

    Программа SBarTest создает прикладную программу термостата,
представленную на Рис. 12.10.
    Полный файл SBARTEST.CPP можно найти в поддиректории EXAMPLES.

         Передача данных управляющих элементов

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

    Определение буфера передачи

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

    struct SampleTransferStruct {
      char StaticText[STATICTEXTLEN];  // статичный текст
      char EditText[EDITTEXTLEN];      // редактируемый текст
      TListBoxData ListBoxData;        // строки блока списка и выбор
      TComboBoxData ComboBoxData;      // строки комбинированного блока и
                                          выбранная строка
      WORD CheckState;                 // состояние блока проверки
      WORD RadioState;                 // состояние селективной кнопки
      TScrollBarData ScrollBarStruct;  // диапазон линейки прокрутки и т.д.
    }

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

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

    struct MyTrasferBuffer
    {
    ...
      PTListBoxData pMyListBoxData;
    ...
    };

    Установите pMyListBoxData следующим образом:

    MyTransferBuffer.pMyListBoxData = new TListBoxData();

    Определение класса TListBoxData включает три компоненты данных:

Таблица 12.6  Компоненты данных TListBoxData

Компонента данных   Описание
---------------------------------------------------------------------------
PArray Strings      Массив строк (из библиотеки контейнерных классов),
                    содержащий элементы блока списка.
PArray SelStrings   Массив строк, содержащий выбранные элементы блока
                    списка. В блоке списка с одним выбором там будет только
                    одна строка.
int SelCount        Число выбранных элементов. Для блока списка с одним
                    выбором SelCount равен единице.

* Комбинированные блоки передают строки блока списка комбинированного блока
и указатель на выбранную строку в объекте типа TComboBoxData. Вы можете
включить указатель на объект TComboBoxData в вашем буфере передачи также,
как это вы делали в блоках списка, заменив TListBoxData на TComboBoxData:

    struct MyTrasferBuffer
    {
    ...
      PTComboBoxData pMyComboBoxData;
    ...
    } ;

    Установите pMyComboBoxData следующим образом:

    MyTransferBuffer.pMyComboBoxData = new TComboBoxData();

    Определение класса TComboBoxData включает две компоненты данных:

Таблица 12.7  Компоненты данных TComboBoxData

Компонента данных   Описание
---------------------------------------------------------------------------
PArray Strings Массив строк (из библиотеки контейнерных классов),
                    содержащий элементы блока списка комбинированного
                    блока.
Pchar Selection     Указатель на символьную строку, которая является
                    выбранным элементом блока списка комбинированного
                    блока.

* Блоки проверки и селективные кнопки передают их состояния как значения
типа WORD (BF_UNCHECKED, BF_CHECKED или BF_GRAYED).

* Линейки прокрутки используют другую запись, TScrollBarData, для
сохранения диапазона и позиции управляющего элемента линейки прокрутки.
Далее приводится определение TScrollBarData:

    struct TScrollBarData {
      int LowValue;
      int HighValue;
      int Position;
    }

    Конструирование управляющих элементов и разрешение передачи

    Окно или диалог, которые используют механизм передачи, должны
конструировать их объекты управления, участвующие в передаче, в том
порядке, в котором определяются соответствующие компоненты данных буфера
передачи. Для разрешения механизма передачи для объекта окна или диалога
просто установите его компоненту данных TransferBuffer для указания на
определенный вами буфер передачи.
    Механизм передачи требует использование объектов ObjectWindows для
представления управляющих элементов, для которых вы хотите передавать
данные. Для связи объектов с управляющими элементами в блоках диалога
используйте конструктор, который использует в качестве аргументов просто
родительское окно и идентификатор ресурсов.
    Связанные объекты управления с управляющими элементами описаны в Главе
11 "Объекты диалога".

    TParentWindow::TParentWindow(PTWindowsObject AParent, LPSTR ATitle) :
                                 TWindow(AParent, ATitle)
    {
      TheDialog = new TDialog(this, ID_DIALOG);
      new TStatic(TheDialog, ID_STATIC, 20);
      new TEdit(TheDialog, ID_EDIT);
      new TListBox(TheDialog, ID_LISTBOX);
      new TComboBox(TheDialog, ID_COMBOBOX, 20);
      new TCheckBox(TheDialog, ID_CHECKBOX, 0);
      new TRadioButton(TheDialog, ID_RADIOBUTTON, 0);
      new TScrollBar(TheDialog, ID_SCROLLBAR);
      TheDialog->TransferBuffer = &TransferStruct; }

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

    ...
    Edit = new TEdit(this, ID_EDIT, "", 10, 10, 100, 30, 40, FALSE);
    EDIT->EnableTransfer();
    ...

    Для прямого исключения управляющего элемента из механизма передачи
надо вызвать его функцию-компоненту DisableTransfer.

    Передача данных

    При создании окна или диалога или выполнении диалога данные
автоматически передаются из буфера передачи в набор участвующих управляющих
элементов.
    Только для модального диалога данные автоматически передаются из
управляющих элементов в буфер передачи, когда диалог получает командное
сообщение с идентификатором управляющего элемента из IDOK. Данные
автоматически передаются из управляющих элементов модального диалога в
буфер передачи, когда вызов CanClose в CloseWindow возвращает значение
TRUE. Тогда, если диалог выполняется снова, данные из буфера опять
передаются управляющим элементам. По этой схеме диалог и буфер
синхронизируются.
    Однако, вы можете прямо передавать данные в обоих направлениях в любой
момент. Например, вы можете захотеть передать данные из управляющих
элементов окна или немодального диалога. Или вы можете захотеть
переустановить состояния управляющих элементов, используя данные в буфере
передачи в качестве реакции на щелчок пользователя на кнопке Reset.
Используйте в каждом случае функцию-компоненту TransferData, предоставляя
константу TF_SETDATA для передачи данных из буфера в управляющие элементы и
константу TF_GETDATA для передачи данных в обратном направлении. Например,
вы можете захотеть вызвать TransferData в компоненте-функции CloseWindow
объекта окна:

    void TSampleWindow::CloseWindow()
    {
      TransferData(TF_GETDATA);
      TWindow::CloseWindow();
    }

    Модификация передачи для управляющих элементов

    Вы можете захотеть модифицировать способ обычной передачи данных
управляющих элементов или включить новый управляющий элемент, определенный
вами, в механизм передачи. В обоих случаях вам нужно просто написать
функцию компоненту Transfer для вашего объекта управления так, чтобы при
передаче TF_GETDATA данные копировались из управляющего элемента в
местоположение, указанное передаваемым адресом. Если передается TF_SETDATA,
копируйте данные из местоположения, указанного передаваемым адресом, в
управляющий элемент. Если передается TF_SIZEDATA, просто возвращайте размер
передаваемых данных без выполнения каких-либо других действий. Размер
передаваемых данных доступен в компоненте данных TextLen TStatic. Например,
здесь приводится TStatic::Transfer:

    WORD TStatic::Transfer(Pvoid DataPtr, WORD TransferFlag)
    {
      if (TransferFlag == TF_GETDATA)
        GetText((LPSTR)DataPtr, TextLen);
      else
        if (TransferFlag == TF_SETDATA)
          SetText((LPSTR)DataPtr, TextLen);
      return TextLen;
    }

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

    Примеры передачи

    Главное окно программы TranTest производит модальный диалог с полями
для ввода пользователем информации имени и адреса. Оно использует буфер
передачи для сохранения этой информации и представляет ее в управляющих
элементах диалога, когда диалог снова выполняется. Заметим, что новый класс
диалога не нуждается в определении для установки и получения данных
диалога.
    Файл TRANTEST.CPP можно найти в поддиректории EXAMPLES. Программы
LBXTTEST.CPP и CBXTTEST.CPP демонстрируют передачу данных блока списка и
комбинированного блока. Эти программы являются модифицированными версиями
программ LBOXTEST.CPP и CBOXTEST.CPP, представленных ранее в этой главе.




    ГЛАВА 13

         АДАПТИРОВАННЫЕ ОБЪЕКТЫ УПРАВЛЕНИЯ

    Стандартные характеристики, представление и режим работы классов
элементов управления ObjectWindows были описаны в предыдущих главах.
Некоторые способы, которыми вы можете изменить эти стандартные установки,
были упомянуты. Эта глава детально описывает технику, которую вы можете
использовать для модификации характеристик предопределенных управляющих
элементов ObjectWindows. Также представлена процедура разработки ваших
собственных адаптированных управляющих элементов.

         Модификация предопределенных управляющих элементов

    Существуют два основных способа модификации характеристик
предопределенных управляющих элементов ObjectWindows:

* модификация их стилей создания
* определение методов реакции на сообщения

    Модификация стилей создания

    Вы можете модифицировать некоторые стандартные режимы работы
предопределенных управляющих элементов с помощью изменения атрибутов
создания стилей (которые содержит Attr.Style). Например, для предотвращения
взаимоисключающих выборов в группе селективных кнопок удалите из их стилей
создания BS_AUTORADIOBUTTON и добавьте BS_RADIOBUTTON. (Заметим, что вам не
нужно самому проверять кнопки.) Другой пример это ситуация, когда вы можете
позволить многочисленный выбор элементов блока списка добавлением
LBS_MULTIPLESEL к стилям создания блока списка.
    Для ограничения размера вы можете модифицировать представление
предопределенного управляющего элемента с помощью изменения его стилей
создания. Например, можете задать появление текста управляющего элемента
блока проверки слева от блока:

    MyBox = new TCheckBox(TheParent, ID_CHECKBOX, "Text",
                          100, 100, 80, 20, NULL);
    MyBox->Attr.Style |= BS_LEFTTEXT;

    Вы можете добавить рамку управляющему элементу с помощью добавления
WS_BORDER его стилю создания.
    Вы можете задать стиль создания owner-draw для некоторых
предопределенных управляющих элементов для повышения возможностей
управления представлением управляющего элемента. Рисуемые управляющие
элементы обсуждаются в следующем параграфе.
    Возможные модификации характеристик предопределенных управляющих
элементов с помощью изменения стилей создания ограничиваются вариантами
стилей, возможных для типа управляющего элемента. Полный список стилей
создания для каждого типа управляющих элементов можно найти в "Справочном
руководстве" (Reference Guide) ObjectWindows for C++.

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

    Если вы хотите определить внешний вид вашего управляющего элемента
(внутри его предопределенной формы), вы можете задать стиль owner-draw для
некоторых типов предопределенных управляющих элементов. Для того, чтобы
сделать, например, кнопку рисуемой, задайте в качестве одного из стилей
создания BS_OWNERDRAW:

    Attr->Style |= BS_OWNERDRAW;

    Кроме того, вам нужно определить способ, которым она будет рисоваться.
Для того, чтобы это сделать, определите функцию-компоненту ODADRAWENTIRE в
порожденном классе. Вы можете при желании переопределить функции-компоненты
ODAFocus и ODASelect для модификации представления вашего рисуемого
управляющего элемента, когда он получает фокус или выбирается.
    Функции-компоненты ODADrawEntire, ODAFocus и ODASelect передают
DRAWITEMSTRUCT, которая содержит информацию, используемую для рисования
управляющего элемента. Ее компонента данных hDC содержит дескриптор для
представления используемого контекста; этот дескриптор должен передаваться
функциям GDI Windows, вызываемых при рисовании управляющего элемента. Ее
компонента данных rcItem содержит область пользователя управляющего
элемента. DRAWITEMSTRUCT детально описана в Главе 6 "Справочного
руководства" (Reference Guide) Object Windows for C++.
    Рисуемый управляющий элемент также может рисоваться родительским окном,
которое переопределяет WMDrawItem. Указатель на DRAWITEMSTRUCT передается
как LParam заданного RTMessage; идентификатор рисуемого управляющего
элемента передается в компоненте CtlId структуры.
    Прикладная программа DCTLTEST.CPP в директории EXAMPLES представляет
диалог с рисуемыми управляющими элементами кнопок, когда выбирается пункт
меню "Test". Возникает диалог, который показывает, как рисуемые кнопки
реагируют на изменения фокуса и выбора. Используйте клавиши Tab и стрелок
для переключения фокуса и щелчок мыши на кнопке для выбора кнопки. В ваших
собственных приложениях вы можете использовать побитовые отображения для
ваших рисуемых кнопок.
    Кнопки нажатия, блоки проверки, селективные кнопки и блоки групп могут
быть сделаны рисуемыми с помощью задания BS_OWNERDRAW в качестве стиля
создания. Блоки списков и комбинированные блоки могут быть сделаны
рисуемыми с помощью задания LBS_OWNERDRAW и CBS_OWNERDRAW, соответственно,
в качестве стилей создания. Windows не предоставляет эту возможность для
линеек прокрутки, редактируемых управляющих средств и статических
управляющих средств.

    Модификация предопределенных реакций на сообщения

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

    Задание дополнительной обработки для предопределенного управляющего
элемента

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

    void TBeepButton::WMLButtonDown(RTMessage Msg)
    {
      MessageBeep(0);
      DefWndProc(Msg);
    }

    Так как стандартной обработкой WM_LBUTTONDOWN для кнопки является
посылка предупреждающего сообщения BN_CLICKED, альтернативное выполнение
возможно с помощью переопределения BNClicked.
    В качестве другого примера вы можете определить функции-компоненты
WMSetFocus и WMKillFocus для модификации рамки редактируемого управляющего
элемента, когда он получает фокус. Так как стандартной обработкой является
посылка этими сообщениями предупреждающих сообщений ENSetFocus и
ENKillFocus, альтернативное выполнение возможно с помощью переопределения
функций-компонент ENSetFocus и ENKillFocus.

    Обход реакции предопределенного управляющего элемента

    В некоторых пределах вы можете захотеть обойти стандартную обработку
предопределенного управляющего элемента. Это может повлечь большие затраты,
чем вы предполагаете, так как стандартная обработка управляющих элементов
может быть комплексной. Обязательно полностью протестируйте результаты.
Иногда вы будете симулировать некоторый предопределенный режим работы.
    Например, для выполнения редактируемого управляющего элемента с
атрибутом только для чтения вы должны сделать "ничего не делающую" реакцию
WMChar в классе, порожденном из TEdit. Ваш редактируемый управляющий
элемент может использоваться для представления текста, который может
копироваться в Буфер информационного обмена, но не может быть
модифицирован.
    Другим примером обхода предопределенного режима работы является обход
предопределенной обработки WMLButtonDown для кнопки для того, чтобы создать
"плоскую" кнопку, не модифицирующую ее представление при щелчке мыши на
ней. Вы будете тогда посылать предупреждающее сообщение с кодом BN_CLICKED
родителю кнопки сами, так как обычно оно посылается в предопределенной
обработке WMLButtonDown.

    void TFlatButton::WMLButtonDown(RTMesage Msg)
    {
      SendMessage(Parent->HWindow, WM_COMMAND, Attr.Id,
                  MAKELONG(HWindow, BN_CLICKED));
    }

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

         Использование адаптированного управляющего элемента

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

    Разработка адаптированного управляющего элемента

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

* определение типов пользовательского ввода
* описание визуального представления вашего управляющего элемента
* определение реакции на ввод

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

    Описание адаптированного управляющего элемента

    Основные шаги реализации адаптированного управляющего элемента:

* объявление класса, порожденного из TControl
* переопределение Paint для рисования вашего управляющего элемента
* определение методов реакции на сообщения, которые перехватывают и
реагируют на пользовательский ввод

    Адаптированный управляющий элемент используется в примере прикладной
программы CCTLTEST.CPP, находящейся в директории EXAMPLES. Этот управляющий
элемент цвета предоставляет варианты цвета, которые могут быть выбраны
пользователем. Объект TColorControl реагирует на щелчок левой кнопки мыши
посылкой предупреждающего сообщения родителю:

    void TColorControl::WMLButtonDown(RTMessage)
    {
      SendMessage(Parent->HWindow, WM_COMMAND, Attr.Id,
                  MAKELONG(HWindow, CN_CLICKED));
    }

    void TColorControl::WMLButtonDblClk(RTMessage)
    {
      SendMessage(Parent->HWindow, WM_COMMAND, Attr.Id,
                  MAKELONG(HWindow, CN_DBLCLICKED));
    }

    Константы CN_CLICKED и CN_DBLCLICKED, передающиеся родителю
управляющего элемента цвета, являются предупреждающими кодами, описывающими
тип происшедшего события (одинарный или двойной щелчок). Адаптированные
управляющие элементы посылают предупреждающие сообщения с адаптированными
предупреждающими кодами; управляющий элемент может послать любой код,
понятный его родителю.
    Так как эти предупреждения управляющего элемента нестандартны, вы
должны определять их сами:

    const unsigned int CN_CLICKED = 0;
    const unsigned int CN_DBLCLICKED = 1;

    TColorControl в прикладной программе CCTLTEST переопределяет Paint для
рисования области пользователя, используя представляемый цвет. Вам также
нужно переопределить Paint для вашего адаптированного управляющего
элемента; прежде всего, невидимый управляющий элемент тяжело использовать!
Вы можете также захотеть предоставить функции-компоненты реакции на
сообщения WMSetFocus и WMKillFocus для изменения представления вашего
управляющего элемента, когда он получает и теряет фокус.
    TControl наследует функцию-компоненту GetWindowClass TWindow, которая
задает атрибуты регистрации, которые по умолчанию подходят адаптированному
управляющему элементу. Если вы хотите модифицировать эти стандартные
установки для вашего управляющего элемента, вам нужно переопределить
GetWindowClass и GetClassName.
    Более подробную информацию о этих стандартных атрибутах и о
переопределении GetWindowClass и GetClassName можно найти в параграфе
"Регистрация класса окон" в Главе 10 "Объекты окон".
    TColorControl, например, модифицирует регистрационный стиль класса для
указания того, что управляющий элемент принимает сообщения о двойном щелчке
мыши:

    void TColorControl::GetWindowClass(WNDCLASS& AWndClass)
    {
      TControl::GetWindowClass(AWndClass);
      AWndClass.style |= CS_DBLCLKS;
    }

    LPSTR TColorControl::GetClassName()
    {
      return "ColorControl";
    }

    Вам нужно задать имя класса Windows адаптированного управляющего
элемента диалога в определении ресурсов диалога. Имя класса Windows
управляющего элемента является именем, возвращаемым его
функцией-компонентой GetClassName. Если вы не переопределяли GetClassName,
она возвратит "OWLWindow".


    ГЛАВА  14

         ОБЪЕКТЫ MDI

    Мультидокументальный интерфейс (MDI) это стандарт интерфейса для
приложений Windows, который позволяет пользователю одновременно работать с
несколькими открытыми документами. Документ, в этом смысле, это обычно
файлоориентированная задача, такая как редактирование текстового файла или
работа с файлом таблицы. В приложениях с MDI пользователь может, например,
иметь в одном приложении несколько открытых файлов. Примеры приложений MDI
вы могли использовать, как, например, Менеджер Программ Windows или
Менеджер Файлов Windows. Стандарт MDI является частью спецификаций Общего
Доступа Пользователей (Common User Access) IBM.

         Компоненты приложений MDI

    Существуют определенные компоненты, присутствующие в каждом приложении
MDI. Во-первых, там есть главное окно, называемое окном рамки. Внутри
области пользователя окна рамки находится невидимое окно, окно пользователя
MDI, которое предоставляет закулисное управление динамически создаваемыми
дочерними окнами, называемыми дочерними окнами MDI.

Рис. 14.1  Компоненты примера приложения MDI

    Окно рамки также имеет меню, часто озаглавленное Window, в котором
пользователь может выбрать операции, управляющие дочерними окнами MDI,
например, Каскадное покрытие, "Кафельное" покрытие, Скомпоновать и Закрыть
Все. Это меню называется меню дочерних окон. Элемент для каждого открытого
дочернего окна MDI автоматически добавляется в конец этого меню. Текущее
выбранное окно помечается меткой.
    Каждое дочернее окно MDI имеет некоторые характеристики перекрывающего
окна. Оно может быть максимизировано до полного размера окна рамки или
минимизировано в пиктограмму внутри окна рамки. Дочерние окна MDI никогда
не появляются вне границ их окна рамки. Дочернее окно MDI не может иметь
меню, так что все его функции управляются меню окна рамки. Заголовком
каждого дочернего окна MDI часто является имя открытого файла, связанного с
этим окном, хотя этот режим работы необязателен и находится под управлением
программы. Вы можете рассматривать приложение MDI как минисеанс Windows с
несколькими приложениями, представленными окнами или пиктограммами.

    Каждое окно MDI является объектом

    ObjectWindows определяет классы, экземпляры которых представляют окна
рамки и дочерние окна MDI. Они являются TMDIFrame и TMDIClient,
соответственно. Дочерние окна MDI являются экземплярами класса, который вы
порождаете из TWindow.
    В приложении MDI ObjectWindows окно рамки владеет его окном
пользователя MDI и сохраняет ссылки на него в его компоненте данных
ClientWnd. Окно рамки также содержит каждое из его дочерних окон MDI в его
списке дочерних окон.
    Функция-компонента TMDIFrame занимается в основном построением и
управлением дочерних окон MDI и окна пользователя MDI и обработкой выборами
в меню. Основная роль TMDIClient состоит в закулисном управлении дочерними
окнами MDI. При разработке приложений MDI вы будете главным образом
порождать новые классы из TMDIFrame и TWindow для вашего окна рамки и
дочерних окон, соответственно. Для окна пользователя MDI обычно используют
экземпляры TMDIClient.

         Конструирование окон MDI

    Далее приводятся некоторые соображения о конструировании окон для
приложений MDI. Полное описание конструирования окон приводится в Главе 10
"Объекты окон".

    Конструирование окон рамки MDI

    Окно рамки приложения MDI также является главным окном, так что оно
конструируется с помощью функции-компоненты InitMainWindow объекта
приложения. Однако, в отличие от TWindow, конструктор TMDIFrame не
использует родительского окна (окна рамки, будучи главными окнами, не имеют
родителей), но использует другой параметр, идентификатор ресурса меню. Окна
рамки MDI требуют наличия меню, так что конструктор TMDIFrame требует
аргумент меню и устанавливает для вас Attr.Menu.
    Меню окна рамки должно включать меню дочерних окон стиля MDI. Это меню,
в которое добавляются элементы меню дочерних окон MDI. Компонента данных
ChildMenuPos TMDIFrame содержит позицию компоненты дочернего окна.
Конструктор TMDIFrame первоначально устанавливает ChildMenuPos в ноль,
указывая самый левый пункт меню верхнего уровня. Однако, вы можете
переустановить ChildMenuPos в вашем конструкторе окна MDI:

    TMyMDIWindow::TMyMDIWindow(LPSTR ATitle, HMENU AMenu) :
                               TMDIFrame(ATitle, AMenu)
    {
      ChildMenuPos = 1;
    }

    TMDIFrame::SetupWindow вызывает InitClientWindow для конструирования
объекта TMDIClient являющегося окном пользователя MDI.
    TMDIFrame::SetupWindow создает окно пользователя MDI.

    Конструирование дочерних окон MDI

    TMDIFrame определяет функцию-компоненту автоматической реакции,
называющуюся CMCreateChild, которая вызывается, когда выбирается пункт меню
с идентификатором CM_CREATECHILD. Обычно этот пункт меню называется Новый
или Создать. Как это определяется TMDIFrame, CMCreateChild конструирует
объект дочернего окна MDI с помощью вызова TMDIFrame::CreateChild. Затем,
CreateChild вызывает InitChild и функцию-компоненту MakeWindow объекта
приложения для создания окна.
    TMDIFrame::InitChild просто создает окно типа TWindow без заголовка,
так что вы можете захотеть переопределить InitChild для вашего класса окон
MDI для конструирования экземпляра вашего класса дочерних окон MDI:

    PTWindowsObject TMyMDIWindow::InitChild()
    {
      return(new TMyMDIChild(this, "New Child Window"));
    }

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

    void TMyMDIWindow::SetupWindow()
    {
      TMDIFrame::SetupWindow();
      CreateChild();
    }

    Если вы хотите, чтобы первое дочернее окно MDI было максимизировано,
конструируйте объект дочернего окна MDI или прямо, или с помощью вызова
InitChild, и добавьте WS_MAXIMIZED к стилям, ранее установленным для
объекта. Затем, надо вызвать метод MakeWindow объекта приложения для
создания дочернего окна MDI:

    void TMyMDIWindow::SetupWindow()
    {
      PMyMDIChild NewChild;

      TMDIFrame::SetupWindow();
      NewChild = new TMyMDIChild(this, "New Child Window");
      NewChild->Attr.Style |= WS_MAXIMIZE;
      GetApplication()->MakeWindow(NewChild);
    }

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

         Обработка сообщений в приложениях MDI

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

         Управление дочерними окнами MDI

    Классы окон MDI Windows предоставляют функции-компоненты для
манипуляции дочерними окнами MDI приложений MDI. Хотя основная скрытая
работа выполняется TMDIClient, все функциональные возможности и данные
доступны через функции-компоненты TMDIFrame.

    Активизация дочерних окон

    Пользователи приложений MDI могут свободно активизировать любые
открытые или минимизированные дочерние окна MDI. Однако, вы можете захотеть
предпринять некоторые действия, когда пользователь деактивизирует
какое-либо дочернее окно активизацией другого. Например, меню окна рамки
может отражать текущее состояние активного дочернего окна с помощью
изменения цвета или пометки. Всякий раз, когда дочернее окно становится
активным или неактивным, оно получает сообщение Windows WM_MDIACTIVATE. Вы
можете переопределить TMDIClient::WMMDIActivate (которое обычно просто
вызывает DefWndProc) для объекта дочернего окна для реакции соответствующим
образом.

    Меню дочерних окон

    TMDIFrame определяет функцию-компоненту реакции сообщений Windows,
которая автоматически реагирует на стандартные выборы меню MDI: "Кафельное"
расположение, Каскадное расположение, Скомпоновать пиктограммы и Закрыть
все. Эти функции-компоненты ожидают сообщения, базирующиеся на командах, с
предопределенными константами идентификатора меню. Используйте эти
идентификаторы, когда вы создаете ресурс меню дочерних окон:

Таблица 14.1  Стандартные действия, команды и функции-компоненты MDI

Действие      Константа идентификатора меню  Функция-компонента TMDIFrame
---------------------------------------------------------------------------
"Кафельное"
расположение  CM_TILECHILDREN                CMTileChildren
Каскадное
расположение  CM_CASCADECHILDREN             CMCascadeChildren
Скомпоновать
пиктограммы   CM_ARRANGEICONS                CMArrangeIcons
Закрыть все   CM_CLOSECHILDREN               CMCloseChildren

    Функции-компоненты реакции TMDIFrame, такие как CMTileChildren,
вызывают другие функции-компоненты TMDIFrame, такие как TileChildren. Эти
функции-компоненты вызывают функции-компоненты TMDIClient с теми же
именами, такие как TMDIClient::TileChildren. Обычно, вы не будете желать
переопределять эти режимы работы, но если вы все же захотите это сделать,
то можете переопределить TMDIFrame::TileChildren или другие
функции-компоненты TMDIFrame (но не автоматические методы реакции).

         Пример приложения MDI

    Программа MDITest производит приложение, использующее MDI и показанное
на Рис. 14.1.
    Полный файл MDITEST.CPP находится в поддиректории EXAMPLES.


    ГЛАВА 15

    ОБЪЕКТЫ, КОТОРЫЕ ВОЗМОЖНО ИСПОЛЬЗОВАТЬ В ПОТОКАХ

    Эта глава описывает, как менеджер потоков ObjectWindows предоставляет
"постоянство" для объектов ObjectWindows. Различные объекты, которые вы
создаете в приложениях ObjectWindows, - окна, блоки диалогов, наборы и
т.д. - существуют недолго. Они конструируются, представляются, обеспечивают
доступ и уничтожаются в процессе работы приложения. Объекты могут
появляться и скрываться, а затем исчезать полностью, когда прекращается
программа. Менеджер потоков позволяет вам сохранять эти объекты или в
памяти, или в файловых потоках, так что они существуют после их обычного
промежутка жизни.
    Существует множество приложений для постоянных объектов. При сохранении
в разделенной памяти, например, они могут предоставлять коммуникацию между
процессами. Они могут быть переданы через модем в другую систему. И самое
главное, объекты могут постоянно сохраняться на диске, используя файловые
потоки. Они могут быть потом считаны и восстановлены тем же приложением или
другими экземплярами того же приложения, или другими приложениями.
Эффективная и безопасная возможность участвовать в потоках предоставляется
для всех объектов ObjectWindows. Все классы ObjectWindows, включая TButton,
TDialog и TWindow, разработаны, как классы, имеющие возможность участвовать
в потоках, так что использовать в потоке их легко как обычный файл
ввода/вывода.
    Создавать ваши собственные классы, имеющие возможность участвовать в
потоках, также довольно просто. Придание классу возможности участвовать в
потоках влечет за собой очень маленькую доработку. Такой класс нуждается в
трех виртуальных функциях (read, write и streamableName), построителе и
конструкторе построителя. Функции read, write и streamableName объявляются
как виртуальные функции в TStreamable. Все классы, имеющие возможность
участвовать в потоке, должны быть порождены прямо или косвенно из
TStreamable. TWindowsObject уже имеет TStreamable в качестве одного из
своих базисов, так что каждый класс, порожденный из TWindowsObject, имеет
возможность участвовать в потоке.
    Объекты потока создаются просто с помощью pstream и порожденных ею
классов. Эти классы, предоставляемые специально для сохраняемого потокового
ввода/вывода, подобны стандартным классам iostream C++, так что если вы
знакомы с ними, вы уже имеете некоторые знания.

    Библиотека iostream

    Ни C, ни C++ не имеют ключевых слов или предопределенных операторов для
управлениями операциями ввода/вывода. Оба используют функции, имеющиеся в
стандартных библиотеках: stdio в C и iostream в C++. Библиотека iostream
C++ более гибкая, расширяемая и надежная из-за предоставления перегруженных
операторов и ввода/вывода с контролем типов для стандартных и определенных
пользователем типов данных. Хотя функции stdio, как, например, printf,
возможны в C++, большинство программистов на C++ предпочитают преимущества
потокового подхода. Но что же такое, собственно, поток?
    Поток это абстрактный тип данных, представляющий неограниченную
(конечно, только теоретически) последовательность элементов с различными
свойствами доступа. Потоки имеют длину (число элементов), текущую позицию
(уникальную точку доступа в любой заданный момент) и режим доступа ("только
для чтения" для ввода, "только для записи" для вывода или "чтение/запись"
для комбинированного ввода/вывода). Чтение (или извлечение) начинается с
текущей позиции (которая затем обычно указывает на следующий элемент), но
запись (или вставка) выполняется с помощью добавления элементов в конец
потока.
    Традиционный дисковый файл это одна из реализаций потока, но концепция
потока расширяется на потоки в память, потоки символов с клавиатуры (как,
например, стандартный ввод, cin) и на экран (как, например, стандартный
вывод, cout и cerr) и потоки в и из серийных портов и других устройств.
Многое из этой концепции подчерпнуто из UNIX, где "все является файлом".
Фактически, с помощью технологии OOP любой источник (или поставщик) данных
может предоставляться с помощью методов извлечения и рассматриваться как
поток ввода. Подобно этому, приемник (или потребитель) данных может
предоставляться методами вставки и рассматриваться как потоки вывода.
Потоки, связанные с дисковыми файлами, предоставляют и ввод, и вывод.
Описание стандартной библиотеки iostream C++ приводится в "Руководстве
пользователя" (User's Guide) Borland C++. Там представлена богатая иерархия
классов потоков для выполнения различных возможностей. Классы потоков могут
представлять различные комбинации следующего: буферизованные и
небуферизованные, форматированные и неформатированные, "в память" или "в
файл", для ввода, вывода и комбинированного ввода/вывода. ObjectWindows
for C++ работает с этими конструкциями для предоставления потокового
ввода/вывода для разнообразных сложных объектов, используемых в
ObjectWindows.

         Перегруженные операторы << и >>

    Одним из преимуществ потокового ввода/вывода C++ является удобство
перегруженных операторов: << для вывода (вставки) и >> для ввода
(извлечения). Сложный синтаксис функции printf и других функций библиотеки
stdio заменен простыми, элегантными операторами, как, например:

    cout << "Hello, World" << endl;

    Предоставляется возможность того, что << и >> перегружаются
соответствующим образом для данного типа данных и потокового класса,
объекты этого типа могут записываться и считываться из соответствующих
потоковых объектов. Такая перегрузка "встроена" в C++ для стандартных
типов, таких как char, short, int, long, char *, float, double и void *.
Перегрузка может быть легко расширена для определенных пользователем,
неклассовых типов данных. Достижение перегрузки для классовых объектов
несколько более сложно, однако, это было сделано в ObjectWindows. Поэтому,
вы можете записывать и считывать объекты с помощью таких операторов, как:

    os << AWindow << ADialog;
    is >> AButton;

    без особых беспокойств о внутренних деталях. Существует набор простых
предварительных действий, когда вы создаете и регистрируете ваши
собственные классы, имеющие возможность участвовать в потоках; они будут
коротко описаны. В фрагменте программы, приведенном выше, os является
объектом opstream (или порожденным), а is является объектом ipstream (или
порожденным). Эти два класса, порожденные pstream, представляют базовые
потоковые классы для постоянных объектов (отсюда "p" в их именах. Они
являются аналогами классов istream и ostream, порожденных из ios, в
стандартной иерархии классов C++. Существует также класс iopstream,
комбинирующий ipstream и opstream. Вы также познакомитесь с вариантами
файловых потоков, ifpstream, ofpstream и iofpstream. Они соответствуют
стандартным классам C++ ifstream, ofstream и fstream и имеют похожие поля и
методы. Ниже приводится иерархия класса psteam.

Рис. 15.1 Иерархия классов, имеющих возможность участвовать в потоках и
использующихся в ObjectWindows

    Перегруженные операторы <<, использующиеся, например, для записи
объектов TWindow и указателей объектов в opstream, определяются в WINDOW.H
следующим образом:

    inline Ropstream operator << (Ropstream os, RTWindow cl);
       { return os << (RTStreamable) cl; }
    inline Ropstream operator << (Ropstream os, RTWindow cl);
       { return os << (RTStreamable) cl; }

    В первом варианте объект TWindow cl будет записан в объект opstream os.
Поток вывода возвращается оператором, позволяя легко соединять операции <<,
использующиеся в стандартном классе C++ ostream. Второй вариант производит
то же для указателя на объект TWindow. Заметим, что операторы реализуются
простым указанием объекта назначения и вызовом оператора <<, определенного
в базовом классе. В нашем случае, TWindow имеет базовый класс TStreamable.
    Подобные перегруженные операторы << и >> предоставляются для всех
стандартных классов, имеющих возможность участвовать в потоках, в
ObjectWindows. Они обычно реализуются как встраиваемые и следуют
вышеописанному шаблону. Обычно вам нужно будет написать подобные операторы
для перегрузки << и >> для ваших собственных классов, имеющих возможность
участвовать в потоках, которые обсуждаются в следующем параграфе.

         Классы, имеющие возможность участвовать в потоках, и TStreamable

    Класс, имеющий возможность участвовать в потоках, это один из тех
объектов, которые могут быть записаны и считаны из постоянных потоков,
используя средства, предоставляемые менеджером потоков ObjectWindows. В
дополнение к имеющимся подходящим операторам ввода/вывода (обычно
перегруженным << и >>, хотя вы можете определить свои собственные) класс,
имеющий возможность участвовать в потоках должен иметь TStreamable в
качестве базового класса в каком-либо месте своей иерархии. Рассмотрение
дерева классов ObjectWindows показывает, что TWindowsObject порожден из
Object и TStreamable. Все классы порождены из TWindowsObject, поэтому
наследуют свойства TStreamable. TScroller также порожден из TStreamable, и,
поэтому, имеет возможность участвовать в потоках.

         Менеджер потоков

    Некоторый набор фоновых действий необходим, когда сложные объекты
сохраняются и запрашиваются из потоков. Эта задача лежит на менеджере
потоков. Когда объекты данных записываются в поток, они могут быть и
получены в качестве бинарных образов без особых осложнений. Объекты
классов, однако, могут содержать любое количество различных типов данных,
включая указатели на другие сложные объекты и, возможно, указатели на
vtable (виртуальные таблицы).
    Для того, чтобы справится с этими затруднениями, менеджер потоков
работает с базой данных классов, имеющих возможность участвовать в потоках,
используя классы TStreamableTypes, TStreamable и TStreamableClass.
Заголовок занимает до 12 байтов на класс, имеющий возможность участвовать в
потоках. Эти классы комбинируются для предоставления базовой регистрации,
чтения, записи и функций создания для индивидуальных объектов. Поводом для
регистрации класса с помощью менеджера потоков служит, фактически, то, что
он должен быть известен менеджеру потоков как класс, имеющий возможность
участвовать в потоках, с соответствующим элементом в этой базе данных.
Операции построения рассматриваются в следующем параграфе.
    Кроме того, во время потокового чтения и записи, менеджер потоков
работает с базой данных для всех объектов, записанных или считанных из
потока. Без излишнего углубления в механизм этих действий (так как операции
выполняются в фоновом режиме), рассмотрим проблему записи объектов в поток
через указатели. Представим, что ptr1 и ptr2 указывают на один и тот же
объекты, и вы записываете *ptr1 и *ptr2 в поток. Когда эти объекты
последовательно считываются из потока, вы можете получить две идентичные
копии одного объекта с различными указателями, то есть потенциальный хаос.
База данных записанных объектов менеджера потоков решает эту проблему: в
поток записывается только одна копия *ptr1. Подобным образом, при чтении
объекта из потока в *ptr1 и *ptr2 будет создан только один объект, а ptr1 и
ptr2 оба будут указывать на него.
    Менеджер потоков работает с объектами потока с помощью предоставления
корректных функций read и write. TStreamable имеет слабые виртуальные
функции read и write (называемые читателями и писателями), которые должны
быть переопределены в каждом классе, имеющем возможность участвовать в
потоках, порожденном из него:

    class TStreamable {
    ...
    protected;
      virtual Pvoid read(Ripstream) = 0;
      virtual void write(Ropstream) = 0;
    };

    Задача писателя состоит в том, чтобы записывать все необходимые поля
объекта в поток. Каждый класс, имеющий возможность участвовать в потоках,
должен иметь писателя, но его реализация может быть сильно упрощена с
помощью вызова писателя базового класса. Далее приводится определение
TWindow::write:

    void TWindow::write(Ropstream os)
    {
      long SaveStyle;
      BOOL NameIsNumeric;
      TWindowsObject::write(os);
      if ( !IsFlagSet(WB_FROMRESOURCE) )
      {
        SaveStyle = Attr.Style & ~(WS_MINIMIZE | WS_MAXIMIZE);
        if ( HWindow )
          if (IsIconic(HWindow) )
            SaveStyle |= WS_MINIMIZE;
          else
            if ( IsZoomed(HWindow) )
              SaveStyle |= WS_MAXIMIZE;
        os << SaveStyle << Attr.ExStyle << Attr.X
           << Attr.Y << Attr.W  << Attr.H
           << (long)(Attr.Param);
      }
      os << Attr.Id;

      NameIsNumeric = HIWORD(Attr.Menu) == NULL;
      os << NameIsNumeric;
      if ( NameIsNumeric )
        os << (long)(Attr.Menu);
      else
        os.fwriteString(Attr.Menu);

      os << Scroller;
    }

    К функции чтения, называемой читателем, применимы все соображения,
описанные выше. Каждый класс, имеющий возможность участвовать в потоках,
должен переопределить слабую виртуальную функцию чтения в TStreamable. Это
делается часто расширением читателя базового класса для охвата всех
дополнительных полей.
    В определенных ситуациях чтения возникает особый случай. Если вы
считываете объект из потока в существующий объект соответствующего типа,
чтение просто переносит соответствующие данные и указатели на виртуальные
таблицы. Однако, если в чтении нет такого объекта, он должен быть сначала
сконструирован, и вид чтения шаблонного объекта должен быть инициализирован
в потоке. Эта задача лежит на построителе. Построитель вызывает специальный
конструктор StreamableInit или построительный конструктор, который
размещает необработанную память достаточного объема для содержания объекта
и конструирует виртуальную таблицу. Каждый класс, имеющий возможность
участвовать в потоках, который может попасть в такие ситуации, должен иметь
построитель и построительный конструктор.
    Читатели, писатели, построители и построительные конструкторы
предопределены для стандартных имеющих возможность участвовать в потоках
классов ObjectWindows. Если вы хотите создать свой собственный класс,
имеющий возможность участвовать в потоках, вам нужно предоставить ему это.

         Конструкторы классов, имеющих возможность участвовать в потоках


    Как описывалось раньше, класс, имеющий возможность участвовать в
потоках, объекты которого могут быть местом назначения потоковой операции
чтения, должен иметь специальный конструктор. Этот конструктор должен иметь
единственный аргумент streamableInit, константу типа enum, определенную в
OBJSTRM.H. Вы можете обнаружить, что все стандартные имеющие возможность
участвовать в потоках классы имеют такой конструктор, который должен быть
типа public. Шаблон имеет следующий вид:

    class TMyStreamable : public TBase, public TStreamable {
    ...
    public:
      TMyStreamable(StreamableInit s);
    ...
    };
    StreamableInit тип данных это тип enum с одной компонентой
streamableInit. Когда вы создаете объект с использованием этого
конструктора:

    TMyStreamable str(streamableInit);

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

    TMyStreamable str(streamableInit);
    // создание "пустого" str
    ...
    ifpstream ifps("str.sav");   // открыть файловый поток для вв/выв
    ifpts >> str;                // читать объект в str
    ...

    Маленькое замечание: объекты, созданные с помощью конструктора
streamableInit, такие как str, не могут благополучно использоваться, пока
они не будут "инициализированы" операцией потокового чтения. Между прочим,
имена перечислений и компонент не имеют особой значимости: они просто
предоставляют уникальный аргумент типа данных для отличия этого
конструктора от других.
    Функция-компонента build для класса, имеющего возможность участвовать в
потоках, определяется следующим образом:

    PTStreamable TMyStreamableClass::build()
    {
      return new TMyStreamableClass(streamableInit);
    }

    TMyStreamableClass::TMyStreamableClass(StreamableInit s) :
      TBaseClass(streamableInit)
    {
    }

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

         Имена классов, имеющих возможность участвовать в потоках

    Каждый класс, имеющий возможность участвовать в потоках, нуждается в
переопределении приватной виртуальной функции streamableName, порожденной
из TStreamable. Эта функция должна возвращать уникальное имя класса в виде
строки, завершающейся пустым символом:

    class TMyStreamable : public TBase, publc TStreamable {
    ...
    private:
      virtual const Pchar streamableName() const
      { return "TMyStreamable"; }
    };

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

         Использование менеджера потоков

    Имеются три шага использования менеджера потоков:

* Установление связей в программе менеджера потоков
* Создание подходящего потокового объекта
* Использование потокового объекта

    Теперь рассмотрим каждый шаг детально

    Установление связей в программе менеджера потоков

    Для управления объектами каждый класс, имеющий возможность участвовать
в потоках, должен определить следующие три функции: read, write и build.
Эти функции должны быть известны менеджеру потоков и связаны со всеми
приложениями, которые использует менеджер потоков. Эта установка связей,
которая называется регистрацией имени класса с помощью менеджера потоков,
достигается с помощью использования макроса __link, назначение которого
состоит в предоставлении ссылки на объект типа TStreamableClass. По
соглашению, именем этого объекта является Regимя-класса, исключая начальную
букву T имени класса, если она имеется. Например, если у вас есть класс
TBase, определенный в BASE.CPP следующим образом

    class TBase : public TStreamable
    {
      int x;
      int y;
    };

    и класс TDerived, определенный в MYAPP.CPP, который содержит компоненту
данных, являющуюся указателем на объект TBase, вам нужно использовать
макрос __link для указания менеджеру потоков установить связи в потоке,
поддерживаемом для TBase. Регистрация потокового класса и определение
TDerived имеют следующий вид:

    __link(RegBase)
    class TDerived : public TStreamable
    {
      int z;
      PTBase zz;
    };

    Помните, что P-типы это указатели!!!
    Используйте макрос __link везде, где вы имеете компоненту данных,
являющуюся указателем на класс, определенный вне исходного файла.

    Создание потокового объекта

    Создание потокового объекта ipstream или opstream требует объявления с
соответствующими аргументами, также как и для классов iostream. Для
сохранения диалога в файловом потоке, называющемся DLG.SAV, вы можете
объявить объект ofpstream следующим образом:

    TChDirDialog cdlg;
    ...
    // создание диалога

    ofpstream of("dlg.sav");
    // открытие файлового потока вывода

    of <> cdlg;
    // чтение объекта диалога из файлового потока в cdlg

         Совокупности в потоках

    Контейнерные классы C++ по умолчанию не имеют возможности участвовать в
потоках. Следующий параграф предоставляет пример того, как породить новый
контейнерный класс из существующего, сделав его имеющим возможность
участвовать в потоках. Обсуждение также предоставляет пошаговые инструкции
того, как сделать класс имеющим возможность участвовать в потоках и по ходу
раскрывается, как каждый компонент реализации используется менеджером
потоков. Пример программы STEP9.CPP находится в директории EXAMPLES\STEPS.

    Придание массивам возможности участвовать в потоках

    Программа использует класс Array (из библиотеки контейнерных классов)
для сохранения совокупности, состоящей из TPoint (объект, определенный в
этом примере программы). Сам класс Array не имеет возможности участвовать в
потоках, так что будет определяться новый класс TPointArray. TPointArray
порождается из Array и TStreamable. Все классы, имеющие возможность
участвовать в потоках, должны порождаться из TStreamable.

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

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

    class TPointArray : public Array, public TStreamable {

    public:
      TPointArray(int upper, int lower = 0, sizeType aDelta = 0) :
        Array(upper, lower, aDelta) {};
    ...
    public:
      static PTStreamable build();
    protected:
      TPointArray(StreamableInit) : Array(50, 0, 50) {};
      virtual void write(Ropstream);
      virtual Pvoid read(Ripstream);

    private:
      const Pchar streamableName() const {
        return "TPointArray";}
    };

    Следующий пример программы записывает TPointArray в поток:

    ofpstream os("save.dat");
    PTPointArray APointArray;
    ...
    os << APointArray;

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

    inline Ripstream operator >> (Ripstream is, RTPointArray cl)
      { return is >> (RTStreamable)cl; }

    inline Ripstream operator >> (Ripstream is, RTPointArray cl)
      { return is >> (RTvoid)cl; }

    inline Ropstream operator << (Ropstream is, RTPointArray cl)
      { return is << (RTStreamable)cl; }

    inline Ropstream operator << (Ropstream is, RTPointArray cl)
      { return is << (RTStreamable)cl; }

    В этом примере фрагмента программы указатель на TPointArray
записывается в поток os. Также, вызывается четвертое встроенное
определение, что влечет вызов вставщика (оператор <<) с аргументом
PTStreamable. Этот вставщик определяется определяется реализацией потока и
имеет результатом вызов виртуальной функции-компоненты write объекта,
предоставленного в качестве параметра. Например, вызывается
TPointArray::write.

    void TPointArray::write(Ropstream os) {
      RContainerIterator PointIterator = initIterator();
      os << getItemsInContainer();

      while (int(PointIterator) != 0) {
        RObject PointObject = PointIterator++;
        if (PointObject != NOOBJECT)
        {
          os << ((PTPoint)(&PointObject))->X;
          os << ((PTPoint)(&PointObject))->Y;
        }
      }
      delete &PointIterator;
    }

    TPointArray::write размещает итератор для контейнера Array, записывает
в поток число элементов массива, а затем проходит весь массив, записывая
каждый элемент массива, и, наконец, уничтожает объект итератора. Несколько
более модульный подход будет осуществлен, если сделать класс TPoint имеющим
возможность участвовать в потоках; в этом случае, цикл просто запишет
объект TPoint в поток, а не будет записывать каждую компоненту данных
TPoint (X и Y).

    Функция-компонента streamableName

    Когда объект записывается в поток, менеджер потоков вызывает
виртуальную функцию-компоненту streamableName объекта для определения его
имени. Это имя записывается в поток перед записью объекта. Описание
встроенной функции-компоненты streamableName следует из определения класса.

    class TPointArray : public Array, public TStreamable {
    ...
    private:
      const Pchar streamableName() const
        { return "TPointArray"; }
    ...
    };

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

    TStreamableClass RegPointArray("TPointArray", TPointArray::build,
                                   __DELTA(TPointArray));

    Этот конструктор задает имя класса ("TPointArray"), адрес
построительной функции (TPointArray:: build) и смещение от уровня отсчета
объекта до компонента TStreamable объекта. Макрос __DELTA автоматически
вычисляет это смещение.
    Менеджер потоков использует базу данных всех классов, имеющих
возможность участвовать в потоках, с помощью их статических объектов. Когда
объект типа TPointArray считывается из потока, он отыскивается в этой базе
данных для того, чтобы найти адрес построительной статической
функции-компоненты TPointArray::build.

    PTStreamable TPointArray::build()
    {
      return new TPointArray(streamableInit);
    }

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

    Функция-читатель, имеющая возможность работы с потоками

    Для достижимого теперь допустимого объекта менеджер потоков может
вызвать виртуальную функцию-компоненту read. Ее определение приводится
ниже:

    Pvoid TPointArray::read(Ripstream is) {
      sizeType NumPoints;
      PTPoint APoint;

      is >> NumPoint;

      for (int i = 0; i < NumPoints; ++i)
      {
        APoint = new TPoint(0, 0);
        is >> APoint->X;
        is >> APoint->Y;
        add(* (APoint));
      {;

      return this;
    }

    В этом примере функция-читатель TPointArray считывает число элементов
массива, а затем считывает значения пар X, Y, связанных с каждым TPoint.
Затем она увеличивает новый TPoint, указывающий на Array.
    Как указывалось ранее, лучший подход заключается в том, чтобы сделать
сам TPoint имеющим возможность участвовать в потоках. Если это было
сделано, а вы реализовали возможность участия в потоках TPoint в другом
файле, вам нужно указать компоновщику согласовать поддержку возможности
участия в потоках для объекта TPoint. Подразумевая, что объект регистрации
TStreamableClass был назван RegPoint, вы можете установить согласование
поддержки возможности участия в потоках с помощью макроса __link, как
показано ниже:

    __link(RegPoint)

    Это вызовет внешнюю ссылку на объект RegPoint типа TStreamableClass.
Так как этот объект регистрации содержит указатель на статическую
построительную функцию-компоненту, это вызовет согласование этой функции.
Это, в свою очередь, вызовет согласование специального конструктора и
виртуальных функций читателя и писателя.



    ЧАСТЬ 3

         ПРОГРАММИРОВАНИЕ ДЛЯ WINDOWS
---------------------------------------------------------------------------

    ГЛАВА 16

         УПРАВЛЕНИЕ ПАМЯТЬЮ

    Обычно, вы не хотите непосредственно использовать средства управления
памятью Windows. Borland C++ берет на себя большую часть работы по
управлению памятью. В общем, вы просто используете нормальные процедуры new
и delete (или malloc и free), которые вы всегда используете для размещения
динамических переменных, а компилятор размещает соответствующее
пространство динамически распределяемой области памяти Windows.
    Однако, иногда вам нужно будет непосредственно использовать менеджер
памяти Windows. Такие ситуации рассматриваются в следующих параграфах
вместе с обсуждением того, как управлять размещением и использовать память
Windows.

         Использование менеджера памяти

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

         Как Windows управляет памятью

    В большинстве систем управления памятью, базирующихся на DOS, память,
которую вы размещаете, остается фиксированной в заданном месте во время ее
использования. В Windows, однако, память может быть перемещаемой и
отбрасываемой. Блок памяти, размещенный как перемещаемый, не имеет
постоянного адреса; Windows может перемещать его при необходимости для
улучшения использования доступной памяти. Например, два блока размещенной
памяти, не являющихся последовательными, могут быть перемещены так, чтобы
они были последовательными и, поэтому, предоставлять больший блок свободной
памяти.
    Память Windows также может размещаться как отбрасываемая. Отбрасываемая
память подобна перемещаемой памяти, в которой Windows может перемещать
блок, но она также может отбрасывать ее, переразмещением блока в нулевую
длину. Это переразмещение имеет эффект уничтожения любых данных,
содержащихся в блоке. Приложение должно переразместить блок памяти и
перезагрузить его корректными данными. Этот процесс перемещения блоков
памяти для создания большого последовательного пространства свободной
памяти называется уплотнением.
    Три прямоугольника на Рис. 16.1 представляют пример процесса
уплотнения. В первом (левом прямоугольнике) имеются три непоследовательных
блока свободной памяти, разделенные блоками 1 и 2. Предположим, что сделан
запрос памяти большего размера, чем любой из трех блоков свободной памяти.
Подразумевается, что блоки 1 и 2 перемещаемые. Уплотнение переместит эти
блоки так, чтобы создать большую последовательную область свободной памяти
(центральный прямоугольник). Теперь представим, что свободной памяти все
еще недостаточно для размещения. Если блок 2 еще и отбрасываемый, он
удалится из памяти (переразместится в нулевую длину), предоставляя еще
большую последовательную свободную память (правый прямоугольник).

Рис. 16.1  Пример уплотнения

    љЋЋЋЋЋЋЋЋЋЋЋЋЋЋ‰    љЋЋЋЋЋЋЋЋЋЋЋЋЋЋ‰    љЋЋЋЋЋЋЋЋЋЋЋЋЋЋ‰
    ѓ   свободно   ѓ    ѓ              ѓ    ѓ              ѓ
    ЌЋЋЋЋЋЋЋЋЋЋЋЋЋЋ„    ѓ              ѓ    ѓ              ѓ
    ѓ              ѓ    ѓ   свободно   ѓ    ѓ              ѓ
    ѓ    Блок 2    ѓ    ѓ              ѓ    ѓ              ѓ
    ѓ              ѓ    ѓ              ѓ    ѓ   свободно   ѓ
    ЌЋЋЋЋЋЋЋЋЋЋЋЋЋЋ„    ЌЋЋЋЋЋЋЋЋЋЋЋЋЋЋ„    ѓ              ѓ
    ѓ   свободно   ѓ    ѓ              ѓ    ѓ              ѓ
    ЌЋЋЋЋЋЋЋЋЋЋЋЋЋЋ„    ѓ    Блок 2    ѓ    ѓ              ѓ
    ѓ              ѓ    ѓ              ѓ    ѓ              ѓ
    ѓ              ѓ    ЌЋЋЋЋЋЋЋЋЋЋЋЋЋЋ„    ЌЋЋЋЋЋЋЋЋЋЋЋЋЋЋ„
    ѓ    Блок 1    ѓ    ѓ              ѓ    ѓ              ѓ
    ѓ              ѓ    ѓ              ѓ    ѓ              ѓ
    ѓ              ѓ    ѓ    Блок 1    ѓ    ѓ    Блок 1    ѓ
    ЌЋЋЋЋЋЋЋЋЋЋЋЋЋЋ„    ѓ              ѓ    ѓ              ѓ
    ѓ   свободно   ѓ    ѓ              ѓ    ѓ              ѓ
    ЉЋЋЋЋЋЋЋЋЋЋЋЋЋЋ™    ЉЋЋЋЋЋЋЋЋЋЋЋЋЋЋ™    ЉЋЋЋЋЋЋЋЋЋЋЋЋЋЋ™

    Перед уплотнением   После перемещения   После отбрасывания

    Дескрипторы и адреса

    Из предыдущего обсуждения может показаться, что управление памятью
Windows затруднительно с точки зрения прикладной программы. К счастью, это
не так. Когда ваша прикладная программа размещает память Windows, она
получает дескриптор (не указатель!) блока памяти. Этот указатель не
является адресом физического местоположения в памяти; он является
идентификатором, который прикладная программа использует для ссылки на
блок, когда делает последовательные вызовы функций управления памятью. Если
дескриптор равен 0, он некорректен.
    Без адреса, однако, невозможно осуществить доступ к данным
перемещаемого блока памяти. Для получения адреса блока вы должны
заблокировать его. Блокировка блока возвращает указатель на начало блока.
Пока блок памяти заблокирован, указатель, полученный в процедуре
блокировки, остается корректным до тех пор, пока блок не будет
разблокирован, даже если блок является перемещаемым или отбрасываемым.

    Особенности реального режима

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

    Особенности защищенного режима

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

         Локальная и глобальная память

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

    Локальная память

* Более быстрый доступ
* Ограниченный размер (менее 64 Кбайтов)

    Глобальная память

* 32-х байтовое выравнивание блоков
* Двенадцать байтов на блок резервируются Windows для заголовка
* Системное ограничение ширины для дескрипторов
* Более медленный доступ
* Больший объем доступной памяти

    Решающим фактором обычно является необходимый для выполнения вашей
задачи объем памяти. Если вам нужно более 1К памяти, возможно, вам лучше
использовать глобальную память. С другой стороны, если вам нужны небольшие
блоки памяти, которые будут использоваться в течение коротких промежутков
времени, лучше выбрать локальную память.

         Использование локальной области динамически распределяемой памяти

    Локальная область динамически распределяемой памяти содержит память,
которая может быть доступна только особым экземпляром вашей прикладной
программы. В Windows локальная область динамически распределяемой памяти
обычно устанавливается в сегменте данных прикладной программы. Этот сегмент
данных также содержит стек и статические переменные вашей прикладной
программы. Все оставшееся пространство сегмента данных (до 64К) доступно
для использования в качестве локальной динамически распределяемой области
памяти.
    Windows не предоставляет вашей прикладной программе локальную
динамически распределяемую область памяти автоматически. Вы можете задавать
размер локальной динамически распределяемой области памяти в момент
компоновки, расположив утверждение HEAPSIZE в вашем файле определений
модулей (.DEF). Также вы можете просто принять стандартный размер
динамически распределяемой области памяти TLINK, равный 4096 байтам.
    Максимальный размер локальной динамически распределяемой области памяти
от того, является ли сегмент данных, содержащий локальную динамически
распределяемую область памяти, фиксированным или перемещаемым. Если он
фиксирован, вы можете разместить локальный блок только с максимальным
размером, равным размеру динамически распределяемой области памяти,
заданному при компиляции и компоновке. Если сегмент данных перемещаем, и
был сделан запрос на память, большую чем заданный размер локальной области
динамически распределяемой памяти, Windows разместит дополнительную память
(до 64К) для попытки удовлетворения запроса. При необходимости, Windows
может переместить сегмент данных для получения дополнительной памяти, в
результате чего становятся некорректными все дальние указатели на локальные
данные.

    Размещение и доступ к локальной памяти

    Функция LocalAlloc размещает блок памяти размера и типа, заданных в
качестве параметров при вызове функции. Вы должны решить, будет ли блок
фиксирован, перемещаем или перемещаем и отбрасываем (для того, чтобы блок
был отбрасываем, он должен быть перемещаемым). Эти опции, заданные в
параметре Flags в вызове функции, включают

* LMEM_FIXED для фиксированного блока памяти
* LMEM_MOVEABLE для перемещаемого блока памяти
* LMEM_MOVEABLE | LMEM_DISCARDABLE для отбрасываемого блока памяти

    Когда размещается новый блок в локальной области динамически
распределяемой памяти, другие блоки в локальной области динамически
распределяемой памяти могут перемещаться или отбрасываться. Для
предотвращения того, чтобы другие блоки были отбрасываемыми, добавьте
LMEM_NODISCARD в параметр Flags. Для предотвращения перемещения и
отбрасывания других блоков добавьте LMEM_NOCOMPACT.
    Функция LocalAlloc возвращает дескриптор блока памяти, если размещение
прошло успешно, и возвращает ноль, если был неудачный результат. Если
размещение прошло успешно, блок памяти будет иметь по крайней мере
запрошенный (в байтах) размер. Используйте функцию LocalSize для
определения действительного размера. Максимальный возможный размер
локального блока определяется размером локальной области динамически
распределяемой памяти.
    Даже если вы имеете дескриптор блока, вы еще не можете осуществить
доступ к данным. Для того, чтобы сделать это, вам нужно заблокировать блок
для получения его адреса в памяти. Это делается с помощью вызова функции
LocalLock. LocalLock временно фиксирует блок в постоянном месте локальной
области динамически распределяемой памяти и возвращает указатель на блок.
Адрес, возвращаемый этой функцией остается корректным, пока блок не будет
разблокирован с помощью функции LocalUnlock. LocalLock увеличивает счетчик
блокировки блоков. Счетчик блокировки, также называемый счетчиком ссылок,
показывает число фиксаций блока в памяти.
    Вы должны всегда проверять значение, возвращаемое функцией LocalLock;
нет гарантии, что оно является корректным указателем. Возвращаемое
значением будет NULL, если переданный дескриптор был некорректным или блок
был отброшен (блок должен был быть LMEM_DISCARDABLE). Следующая программа
размещает 256-байтовый блок локальной памяти.

    HANDLE TheLocalHandle;
    PSTR TheLocalData;
    ...
    TheLocalHandle = LocalAlloc(LMEM_MOVEABLE | LMEM_DISCARDABLE, 256);
    if (TheLocalHandle != 0) {
      TheLocalData = LocalLock(TheLocalHandle);
      if (TheLocalData != NULL)
      {
        // обработка данных, с использованием TheLocalHandle в качестве
           указателя
        LocalUnlock(TheLocalHandle);
      }

      else // блокировка неуспешна;
    }

    else // размещение неуспешно
    }

    Если блок локальной памяти был размещен с атрибутом LMEM_FIXED, он не
будет перемещаться в памяти. Поэтому, вы не вызываете LocalLock для
блокировки объекта в фиксированном адресе. В этом и только в этом случае
16-ти битовый дескриптор, возвращенный LocalAlloc, также является 16-ти
битовым адресом блока памяти.

    Освобождение и отбрасывание блоков локальной памяти

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

    HANDLE LocalHandle;
    ...
    // TheLocalHandle был ранее размещен
    LocalDiscard(TheLocalHandle);

    // теперь снова используем дескриптор для создания нового блока памяти
    LocalHandle = LocalReAlloc(TheLocalHandle, 256, LMEM_MOVEABLE);

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

    Переразмещение и модификация блоков локальной памяти

    Вы можете изменить размер блока, сохраняя его содержание, с помощью
функции LocalReAlloc. Windows будет усекать блок, если вы задали новый
размер меньше, чем текущий размер. Если вы задали новый размер больше
текущего, новая область будет содержать нули, если вы задали LMEM_ZEROINIT;
в другом случае он будет содержать неопределенные данные. Как и с
LocalAlloc, существующие локальные блоки могут перемещаться или
отбрасываться Windows во время переразмещения блока с помощью LocalReAlloc.
Вы можете задать LMEM_NODISCARD или LMEM_NOCOMPACT для предотвращения
отбрасывания или перемещения других блоков во время переразмещения.
    LocalReAlloc также может использоваться для изменения атрибутов блока
между LMEM_MOVEABLE и LMEM_DISCARDABLE. Вы должны задать LMEM_MODIFY в
дополнение к новому атрибуту.
    Вы не можете использовать LocalReAlloc с атрибутом LMEM_MODIFY для
изменения блока с атрибутом LMEM_FIXED. Следующая программа изменяет тип
блока на отбрасываемый:

    HANDLE TheLocalHandle;
    ...
    TheLocalHandle = LocalAlloc(LMEM_MOVEABLE, 256);
    TheLocalHandle = LocalReAlloc(TheLocalHandle, 256, LMEMMODIFY |
                                  LMEM_DISCARDABLE);

    Запросы к блокам локальной памяти

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

    HANDLE TheLocalHandle;
    unsigned int BlockSize;

    TheLocalHandle = LocalAlloc(LMEM_MOVEABLE, 256);
    if (TheLocalHandle != 0)
      BlockSize = LocalSize(TheLocalHandle);

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

    HANDLE TheLocalHandle;
    unsigned int Flags;
    unsigned int LockCount;

    // подразумевается, что блок был размещен
    Flag = LocalFlags(TheLocalHandle);
    LockCount = Flags & LMEM_LOCKCOUNT;

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

    Замечания по программированию

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

         Использование глобальной области динамически распределяемой памяти

    Глобальная область динамически распределяемой памяти это память,
разделяемая Windows и прикладными программами. Размер глобальной области
динамически распределяемой памяти, доступный вашей прикладной программе,
зависит от режима выполнения Windows. Так как глобальная память
разделяется, ваша прикладная программа должна использовать ее эффективно,
иначе пострадает вся работа системы.

    Размещение и доступ к глобальной памяти

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

* GMEM_FIXED для фиксированного блока памяти
* GMEM_MOVEABLE для перемещаемого блока памяти
* GMEM_MOVEABLE | GMEM_DISCARDABLE для отбрасываемого блока памяти

    Когда размещается новый блок в глобальной области динамически
распределяемой памяти, другие блоки в глобальной области динамически
распределяемой памяти могут перемещаться или отбрасываться. Для
предотвращения того, чтобы другие блоки были отбрасываемыми, добавьте
GMEM_NODISCARD в параметр Flags. Для предотвращения перемещения и
отбрасывания других блоков добавьте GMEM_NOCOMPACT.
    Функция GlobalAlloc возвращает дескриптор блока памяти, если размещение
прошло успешно, и возвращает ноль, если был неудачный результат. Вы всегда
должны проверять значение, возвращаемое GlobalAlloc для того, чтобы
определить, было ли размещение успешным. Если размещение прошло успешно,
блок памяти будет иметь по крайней мере запрошенный размер. Используйте
функцию GlobalSize для определения действительного размера. Максимальный
возможный размер блока глобальной памяти, который может быть размещен, в
стандартном режиме Windows равен 1МБ, в 386 усиленном режиме равен 64МБ.
    Для получения адреса блока глобальной памяти вы должны заблокировать
его с помощью функции GlobalLock. В реальном режиме GlobalLock временно
фиксирует блок в постоянном местоположении в физической памяти; в
защищенном режиме блок не фиксируется, хотя его адрес (селектор) всегда
остается корректным, даже если Windows перемещает блоки в памяти. Во всех
режимах, однако, GlobalLock возвращает дальний указатель, который остается
корректным, пока блок не будет разблокирован с помощью GlobalUnlock.
    Вы должны всегда проверять значение, возвращаемое функцией GlobalLock;
нет гарантии, что оно является корректным указателем. Возвращаемое
значением будет NULL, если переданный дескриптор был некорректным или блок
был отброшен. Программа ниже размещает блок глобальной памяти:

    unsigned short BigArray[20000];
    unsigned short far * TheGlobalData;
    HANDLE TheGlobalHandle;

    TheGlobalHandle = GlobalAlloc(GMEM_MOVEABLE, sizeof(BigArray));
    if (TheGlobalHandle != 0)
    {
      TheGlobalData = GlobalLock(TheGlobalHandle);
      if (TheGlobalData != NULL)
      {
        // обработка данных с использованием TheGlobalData в качестве
           указателя
        GlobalUnlock(TheGlobalHandle);
      }

      else
      // блокировка неуспешна;
    }

    else
    // размещение неуспешно;

    В отличие от блоков локальной памяти, если глобальный блок был размещен
с атрибутом GMEM_FIXED, вы должны блокировать блок перед доступом к его
данным. Дескриптор, который возвращает GlobalAlloc для фиксированного блока
в этом случае не является указателем на блок.

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

    Имеется несколько дополнительных функций, которые могут использоваться
для блокировки и разблокировки глобальной памяти. GlobalFix блокирует блок
в памяти и предохраняет его от перемещения вне зависимости от режима
выполнения Windows. Так как блок в действительности фиксируется, его
счетчик блокировок увеличивается на единицу. Не используйте GlobalFix, если
это не является абсолютно необходимым, так как это может взаимодействовать
с уплотнением памяти Windows. Немного прикладных программ требуют фиксации
памяти эти способом.
    GlobalUnfix разблокирует блок и уменьшает счетчик блокировок на
единицу.
    GlobalWire фиксирует блок в наименьшем возможном физическом адресе, а
затем блокирует его, увеличивая счетчик блокировок на единицу. GlobalWire
полезна для блоков памяти, которые должны быть заблокированными в течение
длительного периода времени. Так как блок памяти располагается в памяти с
низким адресом, он не будет взаимодействовать с уплотнением памяти Windows,
когда он фиксирован. GlobalUnWire разблокирует блок и уменьшает счетчик
блокировок.

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

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

    Переразмещение и модификация блоков глобальной памяти

    Вы можете изменить размер блока, сохраняя его содержание, с помощью
функции GlobalReAlloc. Windows будет усекать блок, если вы задали новый
размер меньше, чем текущий размер. Если вы задали новый размер больше
текущего, новая область будет содержать нули, если вы задали LMEM_ZEROINIT;
в другом случае он будет содержать неопределенные данные. Как и с
GlobalAlloc, существующие глобальные блоки могут перемещаться или
отбрасываться Windows во время переразмещения блока с помощью
GlobalReAlloc. Вы можете задать LMEM_NODISCARD или LMEM_NOCOMPACT для
предотвращения отбрасывания или перемещения других блоков во время
переразмещения.
    GlobalReAlloc также может использоваться для того, чтобы делать блоки
перемещаемыми или отбрасываемыми.
    Если вы переразместили блок памяти с новым размером, не кратным 64К,
Windows может возвратить новый дескриптор для этого блока. (Если ваша
прикладная программа выполняется в стандартном режиме, это относится к
кратности 65 519 байтам.) Вследствие этого, вы должны сохранять исходный
дескриптор и проверять соответствие новому дескриптору, возвращаемому
GlobalReAlloc. Если GlobalReAlloc выполнилась успешно, вы должны заменить
старый дескриптор на новый; в противном случае вы должны сохранить исходный
дескриптор:

    HANDLE TheGlobalHandle;
    HANDLE OriginalHandle;

    // первоначально блок размещен с размером 32К
    // переразместить с увеличением на 64К
    OriginalHandle = TheGlobalHandle;
    TheGlobalHandle = GlobalReAlloc(TheGlobalHandle, 102400,
                                    GMEM_MOVEABLE);
    if (TheGlobalHandle == 0)
      TheGlobalHandle = OriginalHandle;

    Запросы к блокам глобальной памяти

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

    HANDLE TheGlobalHandle;
    unsigned int Flags;
    ...
    Flags = GlobalFlags(TheGlobalHandle);
    // проверка, является ли блок отбрасываемым
    if (Flags & GMEM_DISCARDABLE)
      // блок отбрасываем
    else
      // блок не является отбрасываемым

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

    HANDLE TheGlobalHandle;
    unsigned long BlockSize;
    ...
    // получить значение размера наибольшего свободного блока
    BlockSize = GlobalCompact(0);
    // отбросить все отбрасываемые незаблокированные блоки
    BlockSize = GlobalCompact((unsigned long)-1);

    Изменение глобального отбрасывание

    Windows использует алгоритм "с наиболее давним использованием" (LRU)
для определения того, какой отбрасываемый блок должен быть при
необходимости отброшен. Вы можете задать, где должен располагаться
определенный блок памяти в списке отбрасываемых блоков, с помощью функций
GlobalLRUOldest и GlobalLRUNewest. Блок, заданный при вызове
GlobalLRUOldest будет следующим отброшенным блоком. GlobalLRUNewest
перемещает заданный блок в конец списка отбрасывания; он будет отброшен
последним.

    Получение предупреждений о недостаточной памяти

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

    Замечания по программированию

    Всегда разблокируйте ваши перемещаемые и отбрасываемые блоки, как
только вы заканчиваете доступ к ним. Если вы не разблокируете эти блоки,
Windows не сможет перемещать их для создания достаточного пространства
свободной памяти для дальнейших размещений. Вы также должны освобождать все
блоки памяти перед выходом из вашей программы.
    Избегайте размещения маленьких блоков памяти (меньше, чем 128 байтов) в
глобальной области динамически распределяемой памяти, учитывая заголовок и
байтовое выравнивание глобальной памяти. Вы также должны воздерживаться от
размещения большого количества блоков глобальной памяти. Лучше
комбинируйте их в небольшое количество больших блоков.
    Никогда не передавайте просто дескрипторы глобальной памяти между
приложениями для создания разделенной области памяти. Только глобальная
память, размещенная с GMEM_DDESHARE, может разделяться прикладными
программами. Кроме того, вы можете использовать DDE и Буфер информационного
обмена для предоставления разделяемой памяти. См. Главу 17 " Коммуникации
между процессами".



    ГЛАВА 17

         ВЗАИМОДЕЙСТВИЕ ПРОЦЕССОВ

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

         Буфер информационного обмена Windows

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

    Форматы Буфера информационного обмена

    Форматы Буфера информационного обмена, предопределенные Windows,
задаются в вызовах функций Буфера информационного обмена Windows
константами CF_. Они включают форматы, представленные в таблице 17.1.

Таблица 17.1  Форматы буфера информационного обмена

Формат           Значение
----------------------------------------------------------------
CF_TEXT          Массив символов, завершающийся пустым символом
CF_BITMAP        Формат побитовых отображений Windows
CF_SYLK          Формат символических связей Microsoft
CF_TIFF          Формат файла образа признака
CF_METAFILEPICT  Формат метафайлового рисунка Microsoft

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

    Помещение данных в Буфер информационного обмена

    Помещение данных в Буфер информационного обмена состоит из четырех
шагов:

1. Открытие Буфера информационного обмена (OpenClipboard).
2. Опустошение Буфера информационного обмена (EmptyClipboard).
3. Помещение новых данных в Буфер информационного обмена
(SetClipboardData).
4. Закрытие Буфера информационного обмена (CloseClipboard).

    Вызов OpenClipboard использует в качестве аргумента дескриптор окна.
Это гарантирует, что только одно окно обрабатывает Буфер информационного
обмена в данный момент времени.
    При помещении данных в Буфер информационного обмена приложение должно
копировать данные в глобальную память (см. Главу 16) и передает дескриптор
данных в вызове SetClipboardData. Когда дескриптор передан, Буфер
информационного обмена получает данные в свою собственность, и приложение
не может больше использовать их. По этому, передавайте дескриптор копии
ваших данных. Функция SetClipboardData также использует в качестве
аргумента идентификатор формата Буфера информационного обмена, как,
например, CF_TEXT. Ваше приложение может несколько раз вызывать
SetClipboardData и добавлять данные в различных форматах.
    Следующий фрагмент программы иллюстрирует процедуру размещения данных в
Буфере информационного обмена в формате CF_TEXT.

    BOOL TMyWindow::CopyText(LPSTR TextString); {
      HANDLE StringGlobalHandle;
      LPSTR StringGlobalPtr;

      StringGlobalHandle = GlobalAlloc(GMEM_MOVEABLE,
                                       (LONG)lstrlen(TextString) + 1);
      if (StringGlobalHandle)
      {
        StringGlobalPtr = GlobalLock(StringGlobalHandle);
        if (StringGlobalPtr)
        {
          lstrcpy(StringGlobalPtr, TextString);
          GlobalUnlock(StringGlobalHandle);
          if (OpenClipboard(HWindow))
          {
            EmptyClipboard();
            SetClipboardData(CF_TEXT, StringGlobalHandle);
            CloseClipboard();
            return TRUE;
          }

          else GlobalFree(StringGlobalHandle);
        }

        else GlobalFree(StringGlobalHandle);
      }

      return FALSE;
    }

    Заметим, что текстовые данные приложения (передаваемые в CopyText как
аргумент TextString) копируются с помощью lstrcpy в блок глобальной памяти,
а затем дескриптор этого блока может по прежнему использоваться. После
вызова блок памяти не может более использоваться приложением.
    Глобальная память, размещенная с помощью GlobalAlloc, не должна быть
размещена с флагом GMEM_DISCARDABLE, так как Буфер информационного обмена
не будет знать, как заново создавать данные.

    Получение данных из Буфера информационного обмена

    Получение данных из Буфера информационного обмена состоит из четырех
шагов, выполняемых приложением, желающим получить данные:

1. Открытие Буфера информационного обмена (OpenClipboard).
2. Запрос о текущих форматах Буфера информационного обмена
(IsClipboardFormatAvailable).
3. Получение данных из Буфера информационного обмена (GetClipboardData).
4. Закрытие Буфера информационного обмена (CloseClipboard).

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

    int TMyWindow::PasteText(LPSTR TextString, int TextSize) {
      HANDLE StringGlobalHandle;
      DWORD StringGlobalSize;
      LPSTR StringGlobalPtr;
      int CharsPasted;

      CharsPasted = -1;
      if (OpenClipboard(HWindow))
      {
        if (IsClipboardFormatAvailable(CF_TEXT))
        {
          StringGlobalHandle = GetClipboardData(CF_TEXT);

          if (StringGlobalHandle)
          {
            StringGlobalSize = GlobalSize(StringGlobalHandle);
            StringGlobalPtr = GlobalLock(StringGlobalHandle);

            if (StringGlobalPtr)
            {
              if (TextSize < StringGlobalSize)
                StringGlobalSize = TextSize ;

              _fstrncpy(TextString, StringGlobalPtr, StringGlobalSize);
              TextString(StringGlobalSize) = '\0';
              GlobalUnlock(StringGlobalHandle);
              CharsPasted = lstrlen(TextString);
            }
          }
        }
        CloseClipboard();
      }
      return CharsPasted;
    }

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

    Задержка преобразования

    Приложения, поддерживающие несколько форматов данных, значительно
замедляются, если они преобразуют данные в каждый формат всякий раз при
помещении данных в Буфер информационного обмена. В качестве альтернативы вы
можете задержать преобразование ваших данных, пока другое приложение не
запросит этого.
    Для задержки преобразования передайте нулевой дескриптор данных в
вызове SetClipboardData для всех форматов, для которых вы хотите задержать
преобразование. Однако, приложение, задерживающее преобразование, должно
сохранить последние данные, помещенные в Буфер информационного обмена, так,
чтобы они могли быть преобразованы позже. Когда приложение запрашивает
данные в одном из форматов, для которых задержано преобразование, Windows
посылает сообщение WM_RENDERFORMAT приложению, которое послало данные.
Аргумент (слово), переданный с сообщением WM_RENDERFORMAT, содержит
требуемый формат данных. Приложение должно затем поместить данные в Буфер
информационного обмена в запрашиваемом формате, как описано ранее (однако,
без предварительного очищения).
    Преобразующее приложение получает сообщение WM_DESTROYCLIPBOARD, когда
содержание Буфера информационного обмена очищается другим приложением.
Тогда приложение может освободиться от данных, сохраненных для задержанного
преобразования. Когда приложение, задержавшее преобразование данных,
прекращается, Windows посылает сообщение WM_RENDERALLFORMATS для того,
чтобы позволить располагать данные во всех форматах, для которых было
задержано преобразование.

         Обмен сообщениями между процессами

    Большинство сообщений, получаемых окнами или приложениями, посылаются
Windows или другим окном того же приложения (см. "Сообщения, определенные
пользователями" в Главе 7). Однако, имеется возможность послать сообщение
от одного приложения другому. В то время, как этот процесс формализован в
протоколе динамического обмена данными (DDE), Windows предлагает механизм
для обобщенного обмена сообщениями между процессами. Этот механизм наиболее
подходит для посылки предупреждающих сообщений или передачи простых
значений данных. DDE более подходит для передачи сложных данных.
    Например, пускай вы написали серию приложений, включая электронную
почту, план и календарь. Приложение электронной почты включает специальные
процедуры для перехвата предупреждающих сообщений электронной почты из
сетевого сервера. Программа электронной почты должна затем предупредить
программы плана и календаря о том, что получено сообщение электронной
почты. В этом случае вы можете определить новое сообщение, например,
WM_EMAILARRIVED. Каждое приложение, посылающее или получающее это новое
сообщение, должно зарегистрировать его с помощью функции Windows
RegisterWindowMessage. Для посылки сообщения от одного приложения к другому
используйте SendMessage или PostMessage. Для посылки сообщения каждому
открытому окну передайте в качестве параметра дескриптора окна -1. Для
проверки получения сообщения приложение, получившее сообщение, должно
послать назад другое самостоятельно разработанное сообщение.
    Однако, не следует передавать в параметре типа DWORD (LParam) указатель
на данные, так как Windows может переместить ваши данные и сделать
указатель некорректным. Также, не передавайте дескриптор глобальной памяти,
так как блок памяти может быть освобожден, перемещен или отброшен. Могут
также возникнуть проблемы, если посылающее или получающее приложение не
освобождает дескриптор памяти. В общем, вы должны передавать информацию
прямо в параметры типа WORD или DWORD.

         Динамический обмен данными

    Динамический обмен данными (DDE) это протокол, с помощью которого два
связанных приложения согласуются для разделения данных. Понятие "связанные"
указывает на то, что протокол DDE требует, чтобы два приложения знали, как
запрашивать данные по одним и тем же темам, определенным приложениями. DDE
реализуется как группа сообщений Windows (начинающихся с WM_DDE_), которые
при посылке от одного приложения другому могут передавать данные или делать
запросы другому приложению. Ваша программа должна включать файл DDE.H при
использовании DDE.
    Протокол DDE также освобождает оба приложения от проблем, заключенных в
передаче глобальных данных между приложениями. Вместо этого он использует
глобальные атомы для передачи текстовых строк и использует глобальную
память, размещенную с флагом GMEM_DDESHARE, для передачи данных. Атомы это
глобальные строковые константы, доступные для всех текущих выполняющихся
приложений Windows.

    Термины

    Когда два приложения согласовываются для обмена данными, они участвуют
в диалоге. Окно приложения, начинающее диалог, называется пользовательским
окном, а окно приложения, отвечающее на это начало диалога, называется
сервером.
    Окно одновременно может участвовать только в одном диалоге. Приложение,
которое хочет участвовать одновременно в нескольких диалогах, должно
создать несколько окон, каждое из которых будет участвовать в одном
диалоге. При необходимости эти дополнительные окна могут быть скрытыми.
    Протокол DDE имеет древовидную систему для определения данных для
обмена. Самый высокий уровень это имя приложения, после которого идет тема
диалога, а затем имя элемента. Имя приложения является важным, так как
каждый экземпляр приложения обычно "понимает" особый набор тем приложения.
Например, текстовый процессор Windows может использовать имена файлов
документов как темы диалога. Элемент это определенный запрашиваемый участок
данных. В текстовом процессоре элементом может быть текущий параграф или
шрифт.
    Приложение и тема используются для установки диалога, а элемент это
компонента передачи данных. Форматом данных, участвующих в обмене, может
быть любой формат Буфера информационного обмена.

    Установка диалога

    Пользовательское окно начинает диалог с сервером; диалог продолжается,
пока пользовательское окно или сервер не прекратит его. Сообщениями,
которыми начинается и прекращается диалог, являются WM_DDE_INITIATE и
WM_DDE_TERMINATE, соответственно.
    Пользовательское окно начинает диалог посылкой сообщения
WM_DDE_INITIATE с помощью функции SendMessage, задавая -1 в качестве
параметра дескриптора окна. Так как протокол DDE асинхронный, с помощью
SendMessage нужно послать только WM_DDE_INITIATE. После того, как с
некоторым окном будет установлен диалог, функция PostMessage используется
для передачи последовательных сообщений DDE. PostMessage отличается от
SendMessage тем, что позволяет пославшему приложению выполняться без
ожидания ответа. Неудобство асинхронности DDE состоит в том, что
пользовательское приложение ответственно за опрос всех ожидаемых ответов.
    На WM_DDE_INITIATE могут ответить несколько окон приложений серверов.
Пользовательское окно выбирает один сервер и посылает другим сообщения
WM_DDE_TERMINATE, так что отвечать будет только один сервер.
    При начале диалога сообщением WM_INITIATE приложение задает желаемые
имя приложения и тему диалога. Если в качестве имени приложения задается
пустое значение, диалог может начаться с любым выполняющимся приложением.
Если предоставляемая тема является пустой, может иметь место диалог по
любой теме.
    Следующая функция-компонента может использоваться окном для начала
диалога. Она использует в качестве аргументов имя приложения и тему
диалога. В качестве этих аргументов вы можете указывать NULL.

    void TClientWindow::InitiateConversation(LPSTR AppName,
                                             LPSTR TopicName)
    {
      WORD AppGlobalAtom, TopicGlobalAtom;

      if (AppName)
        AppGlobalAtom = GlobalAddAtom(AppName);
      else
        AppGlobalAtom = NULL;
      if (TopicName)
        TopicGlobalAtom = GlobalAddAtom(TopicName);
      else
        TopicGlobalAtom = NULL;
      SendMessage(-1, WM_DDE_INITIATE, HWindow,
                  MAKELONG(AppGlobalAtom, TopicGlobalAtom));
      if (AppGlobalAtom)
        GlobalDeleteAtom(AppGlobalAtom);
      if (TopicGlobalAtom)
        GlobalDeleteAtom(TopicGlobalAtom);
    }

    Заметим, что в функции SendMessage передаются глобальные атомы, а не
указатели DWORD. Атомы создаются, только если строка приложения или темы не
равна NULL. Приложение всегда должно освобождать все глобальные атомы,
созданные после вызова SendMessage. Это действие безопасно, так как вызов
SendMessage не производит возврат, пока все окна приложений в системе не
получат возможность обработать сообщение WM_DDE_INITIATE. Правила создания
и уничтожения глобальных атомов обсуждаются ниже при описании
соответствующих сообщений DDE. Все окна приложений, которые могут
обработать переданное приложение и тему, ответственны за посылку
подтверждения в форме сообщения WM_DDE_ACK.
    Объект окна может определять автоматическую функцию-компоненту реакции
на сообщения, вызываемую в качестве реакции на WM_DDE_INITIATE. В этой
функции-компоненте объект окна будет проверять заданную тему и, если будет
заинтересована в диалоге, пошлет подтверждение пользовательскому окну. Если
тема имеет пустое значение, окно-сервер может выбрать тему, по которой оно
может вести диалог. В каждом случае окно-сервер обычно создает новое окно
для управления диалогом. (Помните, что на один диалог DDE используется одно
окно.) Дескриптор на это новое окно передается после подтверждения.
    Далее показано, как окно-сервер, называющееся TServerWindow, будет
посылать подтверждение на сообщение WM_DDE_INITIATE:

    void TServerWindow::ACKTopic(HWND ClientHWnd, HWND ConversationHWnd,
                                 LPSTR TopicName)
    {
      WORD AppGlobalAtom, TopicGlobalAtom;

      AppGlobalAtom = GlobalAddAtom(GetApplication()->Name);
      TopicGlobalAtom = GlobalAddAtom(TopicName);
      if (!SendMessage(ClientHWnd, WM_DDE_ACK, ConversationHWnd,
          MAKELONG(AppGlobalAtom, TopicGlobalAtom)))
      {
        GlobalDeleteAtom(AppGlobalAtom);
        GlobalDeleteAtom(TopicGlobalAtom);
      }
    }

    Окно управляет диалогом DDE, пока сервер передается в качестве
параметра (ConversationHWnd), а не используется HWindow TServerWindow. Это
делается для реализации идеи того, что для каждой темы должен создаваться
новый объект окна. Тогда пользовательское окно имеет выбор из тем для
продолжения диалога. Также заметим, что глобальные атомы создаются снова и
удаляются, только если вызов SendMessage был неуспешным. Это имеет место,
так как пользовательское окно, получающее сообщение WM_DDE_ACK,
ответственно за удаление глобальных атомов.
    С этого момента диалог установлен. Сервер знает пользователя, так как
он получил дескриптор пользовательского окна в сообщении WM_DDE_INITIATE, а
пользовательское окно знает сервера, так как оно получило дескриптор
окна-сервера в сообщении WM_DDE_ACK. Затем дескрипторы окон используются в
последовательных сообщениях DDE между двумя приложениями.

    Прекращение диалога

    Пользовательское окно или сервер могут прервать текущий диалог с
помощью посылки сообщения WM_DDE_TERMINATE. Обычно это сообщение посылает
пользовательское окно, однако сервер также может его послать, если его
приложение закрывается. При получении сообщения WM_DDE_TERMINATE окно
должно послать сообщение WM_DDE_TERMINATE назад. Все сообщения DDE,
полученные после обработки сообщения WM_DDE_TERMINATE, должны быть
проигнорированы, а все глобальные атомы или данные уничтожены. После
получения ответного сообщения WM_DDE_TERMINATE, окно может быть закрыто.
    Так как сообщения WM_DDE_TERMINATE могут быть посланы при экстремальных
условиях, как, например, фатальная ошибка, закрывающая приложение, ответное
сообщение WM_DDE_TERMINATE может быть не обработано. Для защиты от этого
посылающее приложение должно ожидать, пока не придет сообщение
WM_DDE_TERMINATE или пока не пройдет определенный промежуток времени.
Рекомендуется для всех сообщений DDE, использующих функцию PostMessage,
делать такой контроль по времени. Также перед вызовом PostMessage может
быть вызвана функция /sWindow для проверки, корректен ли параметр
дескриптора окна ответчика. Эти предосторожности не являются частью
протокола DDE, но предохраняют вас от катастрофических сбоев пользователя
или сервера.

    Функции-компоненты обмена данными

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

1. Один элемент данных может быть изменен с помощью посылки пользователем
сообщения WM_DDE_REQUEST и ответа сервера сообщением WM_DDE_DATA.
2. Пользователь может быть информирован о том, что элемент данных изменен,
с помощью сообщений WM_DDE_ADVISE и WM_DDE_DATA. Вы можете затем
использовать WM_DDE_REQUEST и WM_DDE_DATA для получения данных. Этот общий
механизм называется "горячей связью". WM_DDE_UNADVISE используется для
прекращения этих последовательных обновлений.
3. Механизмы 1 и 2 могут быть скомбинированы, так что последовательные
сообщения WM_DDE_DATA действительно передают данные, а WM_DDE_REQUEST не
являются необходимыми.
4. Сообщение WM_DDE_POKE может указать серверу изменить значение элемента
данных.
5. Сообщение WM_DDE_EXECUTE может передать командный макрос приложению
сервера для интерпретации и выполнения сервером.

    Запрос одного элемента данных

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

    void TClientWindow::RequestData(WORD DataFormat, LPSTR Item,
                                    HWND ServerHWND)
    {
      WORD ItemGlobalAtom;

      ItemGlobalAtom = GlobalAddAtom(Item);
      if (!PostMessage(ServerHWND, WM_DEF_REQUEST, HWindow,
          MAKELONG(DataFormat, ItemGlobalAtom)))
        GlobalDeleteAtom(ItemGlobalAtom);
    }

    Параметр DataFormat это один из форматов Буфера информационного обмена,
такой как CF_TEXT. Если сервер может преобразовать данные в требуемый
формат, он может ответить сообщением WM_DDE_DATA:

    #include 
    #include 
    ...
    void TServerWindow::ACKData(LPSTR Item, WORD DataFormat, LPSTR Data,
                                WORD DataSize)
    {
      DDEDATA far * LPDataRecord;
      WORD ItemGlobalAtom;
      WORD DataGlobalHandle;

      DataGlobalHandle = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE,
                                     (LONG) (sizeof(DDEDATA) + DataSize));
      if (DataGlobalHandle)
      {
        LPDataRecord = (DDEDATA FAR * )GlobalLock(DataGlobalHandle);
        if (!LPDataRecord)
          GlobalFree(DataGlobalHandle);
        else
        {
          LPDataRecord->fAckReq = LPDataRecord->fResponse = TRUE;
          LPDataRecord->fRelease = FALSE;
          LPDataRecord->cfFormat = DataFormat;
          _fmemcpy(LPDataRecord->Value, Data, DataSize);
          GlobalUnlock(DataGlobalHandle);
          ItemGlobalAtom = GlobalAddAtom(Item);
          if (!PostMessage(ClientHWND, WM_DDE_DATA, HWindow,
              MAKELONG(DataGlobalHandle, ItemGlobalHandle)))
          {
            GlobalFree(DataGlobalHandle);
            GlobalDeleteAtom(ItemGlobalAtom);
          }
        }
      }
    }

    Имеется пара замечаний по этому примеру. Во-первых, глобальная память
размещается как GMEM_DDESHARE. Это позволяет и серверу, и пользователю
иметь доступ к данным по дескриптору. Во-вторых, запись блока данных
записывается перед действительными данными в глобальной памяти. Это
предоставляет способ задать и формат данных, и несколько битовых опций,
указывающих получающему пользователю вид ответа.
    Набором из двух опций является fResponse, которая указывает, что
сообщение WM_DDE_DATA посылается в ответ на сообщение WM_DDE_REQUEST, и
fAckReq, указывающая получающему пользователю послать подтверждение
WM_DDE_ACK, получив сообщение WM_DDE_DATA. Если данные имеют формат
CF_TEXT, каждая строка данных в передаваемом указателе данных должна быть
ограничена парой символов возврат-каретки/перевод-строки, включая последнюю
строку.
    Если сервер не может обработать сообщение WM_DDE_REQUEST, он должен
послать отрицательное подтверждение пользователю. Это будет необходимым,
если сервер не может преобразовать данные в требуемый формат. Общее правило
состоит в том, чтобы сначала запрашивать данные в наиболее сложном формате,
а затем пытаться запрашивать более простые форматы до тех пор, пока не
будет получено положительное подтверждение. Отрицательное подтверждение
выглядит подобно следующему:

    ...
    ItemGlobalAtom = GlobalAddAtom(Item);

    if (!PostMessage(ClientHWND, WM_DDE_ACK, HWindow /* для сервера */,
                     MAKELONG(0, ItemGlobalAtom)))
      GlobalDeleteAtom(ItemGlobalAtom);
    ...

    Если пользователь получил сообщение WM_DDE_DATA, он может обработать
полученные данные. Он должен также проверить запись DDEDATA в начале
указателя данных и, если установлен fAckReq, послать назад положительное
подтверждение:

    ...
    if (DataRecord->fAckReq)
    {
      ItemGlobalAtom = GlobalAddAtom(Item);
      if (!PostMessage(ServerHWND, WM_DDE_ACK, HWindow /* для польз-ля */,
          MAKELONG(0x8000, ItemGlobalAtom);
    }
    ...

    Если установлен fRelease, пользователь ответственен за освобождение
дескриптора памяти:

    ...
    GlobalUnlock(ItemGlobalHandle);
    if (DataRecord->fRelease)
      GlobalFree(ItemGlobalHandle);
    ...

    Флаг fAckReq или fRelease (или оба) должны быть установлены в сообщении
WM_DDE_DATA. Глобальная память освобождается или сервером, когда он
получает подтверждение, или пользователем. Если ни один из флагов не
установлен, сервер не будет знать, когда освобождать глобальные данные, и
пользователь не будет освобождать их.
    Хотя это может показаться довольно сложным, эта часть протокола DDE
разработана для достижения трех важных целей:

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

    Событийное преобразование данных

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

    void TClientWindow::Advise(LPSTR Item, WORD DataFormat)
    {
      DDEADVISE FAR * LPDataRecord;
      WORD ItemGlobalAtom;
      WORD DataGlobalHandle;

      DataGlobalHandle = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE,
                                     (LONG) (sizeof(DDEADVISE));
      if (DataGlobalHandle)
      {
        LPDataRecord = (DDEADVISE FAR *)GlobalLock(DataGlobalHandle);
        if (!LPDataRecord)
          GlobalFree(DataGlobalHandle);
        else
        {
          LPDataRecord->fAckReq = LPDataRecord->fDeferUpd = TRUE;
          LPDataRecord->cfFormat = DataFormat;
          GlobalUnlock(DataGlobalHandle);
          ItemGlobalAtom = GlobalAddAtom(Item);
          if (!PostMessage(ClientHWND, WM_DDE_ADVISE, HWindow,
              MAKELONG(DataGlobalHandle, ItemGlobalHandle)))
          {
            GlobalFree(DataGlobalHandle);
            GlobalDeleteAtom(ItemGlobalAtom);
          }
        }
      }
    }

    Если сервер может преобразовать данные в требуемый формат, он должен
послать положительное сообщение WM_DDE_ACK. Если он не может обслужить
запрос, он должен послать отрицательное сообщение WM_DDE_ACK. При получении
отрицательного сообщения WM_DDE_ACK пользователь может попытаться
установить связь с другим форматом данных Буфера информационного обмена.
    Заметим, что устанавливается флаг fDeferUpd сообщения WM_DDE_ADVISE.
Это подразумевает, что пользователь желает получить только предупреждение,
а не сами данные, когда данные изменяются. Последовательные сообщения
WM_DDE_DATA, посылаемые от сервера, будут посылаться без данных. Например,

    ...
    ItemGlobalAtom = GlobalAddAtom(Item);
    if (!PostMessage(ClientHWND, WM_DDE_DATA, HWindow /* для сервера */,
        MAKELONG(0, ItemGlobalAtom)))
      GlobalDeleteAtom(ItemGlobalAtom);
    ...

    Пользователь будет посылать сообщение WM_DDE_REQUEST, если захочет
получить данные. Если флаг fDeferUpd не установлен, последовательные
сообщения WM_DDE_DATA от сервера будут действительно содержать данные.
Сообщение WM_DDE_DATA посылается подобно тому, как было описано в ранее
рассмотренной функции ACKData, за исключением того, что флаг fRequest не
устанавливается.

    Запрос к серверу на изменение значения данных

    void TClientWindow::Poke(LPSTR Item, WORD DataFormat, LPSTR Data,
                             WORD DataSize)
    {
      DDEPOKE FAR * LPDataRecord;

      WORD ItemGlobalAtom;
      WORD DataGlobalHandle;

      DataGlobalHandle = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE,
                                     (LONG) (sizeof(DDEPOKE) + DataSize));
      if (DataGlobalHandle)
      {
        LPDataRecord = (DDEPOKE FAR * )GlobalLock(DataGlobalHandle);
        if (!LPDataRecord)
          GlobalFree(DataGlobalHandle);
        else
        {
          LPDataRecord->fRelease = FALSE;
          LPDataRecord->cfFormat = DataFormat;
          _fmemcpy(LPDataRecord->Value, Data, DataSize);
          GlobalUnlock(DataGlobalHandle);
          ItemGlobalAtom = GlobalAddAtom(Item);
          if (!PostMessage(ServerHWND, WM_DDE_POKE, HWindow,
              MAKELONG(DataGlobalHandle, ItemGlobalHandle)))
          {
            GlobalFree(DataGlobalHandle);
            GlobalDeleteAtom(ItemGlobalAtom);
          }
        }
      }
    }

    Программа для TClientWindow::Poke подобна программе для
TServerWindow::ACKData. Отличие состоит в том, что WM_DDE_POKE посылается
от пользователя к серверу, а WM_DDE_DATA всегда посылается от сервера к
пользователю. Сервер должен посылать положительное подтверждение, если он
обработал WM_DDE_POKE, и негативное подтверждение, если он не может этого
сделать. Сервер при обработке WM_DDE_POKE может получить имя элемента
подобным образом:

    ...
    char ItemName[128];

    ItemGlobalAtom = Msg.LP.Hi;
    GlobalGetAtomName(ItemGlobalAtom, ItemName, sizeof ItemName);
    ...

    Выполнение команд макросов в сервере

    Сообщение WM_DDE_EXECUTE позволяет пользователю послать строку,
завершающуюся пустым символом, серверу для выполнения в качестве командного
макроса. Если сервер является приложением, обрабатывающим текст, командная
строка должна быть в форме макроязыка текстового процессора. Как обычно
происходит в таком случае, сервер должен послать положительное или
отрицательное подтверждение выполнения сообщения WM_DDE_EXECUTE. Далее
приводится пример использования такого механизма:

    void TClientWindow::Execute(LPSTR Command) {
      WORD DataGlobalHandle;
      LPSTR DataGlobalPtr;

      DataGlobalHandle = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE,
                                     (LONG)_fstrlen(Command));
      if (DataGlobalHandle)
      {
        DataGlobalPtr = GlobalLock(DataGlobalHandle);
        if (!DataGlobalPtr)
          GlobalFree(DataGlobalHandle);
        else
        {
          _fstrcpy(DataGlobalPtr, Command);
          GlobalUnlock(DataGlobalHandle);
          if (!PostMessage(ServerHWND, WM_DDE_EXECUTE, HWindow,
              MAKELONG(0, DataGlobalHandle)))
          {
            GlobalFree(DataGlobalHandle);
          }
        }
      }
    }

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

    [macroCall] { [macroCall] }

где macroCall это

    macroName(parameter1 {, parameter2 ...})

    Например, если макроязык текстового процессора имеет возможность
перейти на определенный номер строки, "goto_line(lineno)", и выбрать
текущий параграф, "select_current_paragraph", вы можете послать командную
строку

    [goto_line(50)][select_current_paragraph]

для выбора параграфа, в котором находится 50-ая строка. Для приложений,
которые поддерживают DDE как сервер и имеют расширенный макроязык, список
возможных взаимодействий бесконечен.

    Системная тема

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

1. SysItems это список элементов Системной темы.
2. Topics это список всех поддерживаемых в данный момент тем. Так как
некоторые приложения определяют различные темы для каждого открытого файла,
этот список может постоянно изменяться.
3. Formats это список поддерживаемых форматов Буфера информационного обмена
для преобразования данных.
    Компоненты, составляющие элемент данных, должны разделяться метками
табуляции.


    ГЛАВА 18

         ВВЕДЕНИЕ В GDI

    Много типов приложений Windows нуждаются только в окнах, блоках
диалогов и управляющих элементов для полноценного пользовательского
интерфейса. Но некоторые приложения, такие как приложения рисования и
обработки изображений, требуют графику. Эта графика может быть в форме
линий, форм, текста или побитовых отображений.
    Для предоставления приложениям функциональных возможностей работы с
графикой Windows имеет набор функций, называемых Интерфейс с Графическими
Устройствами (GDI). GDI может рассматриваться как графический пакет,
который используют приложения Windows для представления и манипуляций с
графикой. Функции GDI дают вашему приложению возможности рисования, не
зависящие от используемого устройства представления. Например, вы можете
использовать одни и те же функции GDI для написания программ для дисплеев
EGA и VGA или для принтера PostScript. Независимость от устройств
достигается с помощью использования драйверов устройств, переводящих вызовы
функций GDI в команды, понятные использующемуся устройству вывода.

         Контекст представления

    В отличие от традиционных графических программ DOS, программы Windows
осуществляют запись не прямо в пиксели экрана, а в логический объект,
называемый контекстом представления. Контекст представления это виртуальная
поверхность с соответствующими атрибутами, такими как "карандаш", "кисть",
шрифт, цвет фона, цвет текста и текущая позиция.
    Когда вы вызываете функции GDI для рисования в контексте представления,
драйвер устройства, связанный с этим контекстом представления, преобразует
действия рисования в соответствующие команды. Эти команды воспроизводят
действия рисования так точно, как это возможно в устройстве представления.
Дисплей может быть и монохромным экраном с низким разрешением, и 24-битовым
цветным экраном.
    Вы можете рассматривать контекст представления как холст картины, тогда
как окно это целая картина с рамкой. Вместо того, чтобы рисовать прямо на
картине с рамкой, вы сначала рисуете на холсте, а затем переносите холст в
рамку. Точно также вы рисуете в оконном контексте представления. Контекст
представления содержит средства рисования, такие как "карандаш", "кисть" и
шрифт.
    Контекст представления это элемент, управляемый Windows, подобный
элементу окна, за исключением того, что контекст представления не имеет
соответствующего объекта ObjectWindows.

    Управление контекстом представления

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

    Что находится в контексте представления?

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

    Побитовая графика

    Действительная поверхность контекста представления это побитовое
отображение. Побитовые отображения представляют конфигурацию памяти
определенного устройства. Поэтому они зависят от типа адресуемого
устройства. Это порождает проблемы, так как побитовые отображения,
сохраненные для одного устройства, могут быть несовместимыми с другим
устройством. GDI предоставляет определенную технику для разрешения этой
проблемы, включая побитовые отображения, не зависящие от устройства (DIB).
Функции GDI, создающие побитовые отображения, включают в себя
CreateCompatibleDC, CreateCompatibleBitmap и CreateDIBitmap. Функции GDI
для обработки побитовых отображений включают в себя BitBlt, StretchBlt,
StretchDIBits и StretchDIBitsToDevice.

    Цвет

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

    Режимы отображения

    Сложно выбрать единичный элемент рисования, когда вы не знаете, какое
устройство будет использоваться для представления. Большинство приложений
игнорируют эту проблему и считают, что стандартный единичный элемент (один
пиксель) достаточен для работы. Однако, некоторые приложения требуют, чтобы
представление точно воспроизводило размеры желаемого изображения. Для таких
приложений GDI предлагает различные режимы отображения, некоторые из
которых независимы от устройства.
    Каждый режим отображения имеет единичный элемент и координатную
ориентацию. Стандартный режим отображения устанавливает начало отсчета в
левый верхний угол контекста представления с положительным направлением по
оси x вправо и положительным направлением по оси y вниз. Каждый контекст
представления имеет атрибут режима отображения для определения того, как
интерпретировать задаваемые вами координаты.
    Иногда приходится преобразовывать логические координаты, используемые
вами для рисования, и физические координаты побитового отображения текущего
режима отображения. Для большинства приложений точка отсчета для экрана
находится в левом верхнем углу, но для окон точкой отсчета является левый
верхний угол пользовательской области окна. Некоторые окна прокручивают их
пользовательскую поверхность так, что точка отсчета уходит из
пользовательской области. Некоторые функции GDI работают в специфической
системе координат, так что преобразования необходимы. GDI предоставляет
функции для управления этими координатными преобразованиями, такие как
ScreenToClient, ClientToScreen, DPtoLP и LPtoDP.

    Области усечения

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

    Средства рисования

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

         Средства рисования

    Контекст представления управляет представлением графики на экране. Для
представления графики различными способами вы можете модифицировать
средства рисования, с помощью которых вы хотите преобразовывать графику.
Атрибуты этих средств указывают внешний вид графики, рисуемой с помощью
функций GDI, таких как LineTo, Rectangle и TextOut. Карандаш управляет
внешним видом рисуемых линий; кисть управляет внешним видом внутреннего
пространства форм; шрифт управляет внешним видом текста.
    Для назначения атрибутов средству рисования приложение Windows выбирает
логическое или имеющееся средство в контексте представления. Логическое
средство это одно из созданных вашей программой с помощью заполнения полей
некоторой записи, LOGPEN, LOGBRUSH или LOGFONT. Имеющееся средство это
существующее, определенное Windows средство, представляющее наиболее общие
выборы атрибутов, такое как непрерывный черный карандаш, серая кисть или
системный шрифт.

    Имеющиеся средства

    Вы можете получить дескриптор имеющегося средства с помощью функции GDI
GetStockObject. Например,

    HBRUSH TheBrush;
    ...
      TheBrush = GetStockObject(LTGRAY_BRUSH);
    ...

где LTGRAY_BRUSH это целая константа, определенная WINDOWS.H. Далее
приводится список всех возможных констант имеющихся средств:

Таблица 18.1: Имеющиеся средства рисования

Кисти          Карандаши   Шрифты                Палитра
-----------------------------------------------------------------
WHITE_BRUSH    WHITE_PEN   OEM_FIXED_FONT        DEFAULT_PALETTE
LTGRAY_BRUSH   BLACK_PEN   ANSI_FIXED_FONT
GRAY_BRUSH     NULL_PEN    ANSI_VAR_FONT
DKGRAY_BRUSH               SYSTEM_FONT
BLACK_BRUSH                DEVICE_DEFAULT_FONT
NULL_BRUSH                 SYSTEM_FIXED_FONT
HOLLOW_BRUSH

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

    Логические средства

    Структуры логических средств LOGPEN, LOGBRUSH и LOGFONT содержат поля,
содержащие каждый атрибут средства. Например, LOGPEN.lopnColor содержит
атрибут цвета карандаша. Каждая структура определяет свой собственный набор
атрибутов, соответствующий типу средства.
    Для создания логического средства вы передаете создающей функции GDI
структуру логического средства, в которой вы определяете атрибуты средства.

    Логические карандаши

    Вы можете создавать логические карандаши с помощью функций GDI
CreatePen или CreatePenIndirect. Например,

    ThePen = CreatePen(PS_DOT, 3, RGB(0, 0, 210));
    ThePen = CreatePenIndirect(&ALogPen);

    Далее приводится определение структуры LOGPEN:

    typedef struct tagLOGPEN {
      WORD   lopnStyle;
      POINT  lopnWidth;
      DWORD  lopnColor;
    } LOGPEN;

    Поле стиля, lopnStyle, содержит константу, указывающую один из
следующих стилей линий.

Рис. 18.1  Стили линий для средств карандаша

Константа         Соответствующий стиль линии
---------------------------------------------------
  PS_SOLID        ЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋЋ
  PS_DASH         Ћ Ћ Ћ Ћ Ћ Ћ Ћ Ћ Ћ Ћ Ћ Ћ Ћ Ћ Ћ Ћ
  PS_DOT          ·······························
  PS_DASHDOT      Ћ·Ћ·Ћ·Ћ·Ћ·Ћ·Ћ·Ћ·Ћ·Ћ·Ћ·Ћ·Ћ·Ћ·Ћ·Ћ
  PS_DASHDOTDOT   Ћ··Ћ··Ћ··Ћ··Ћ··Ћ··Ћ··Ћ··Ћ··Ћ··Ћ
  PS_NULL

    Поле ширины, lopnWidth, содержит точку, координата x которой является
целым числом, указывающим ширину линии в координатах устройства. На экране
VGA, если значение равно нулю, рисуемая строка будет иметь ширину один
пиксель. Значение координаты y игнорируется.
    Поле цвета, lopnColor, содержит параметр типа DWORD, который содержит
значения интенсивности основных цветов, красного, зеленого и синего,
которые комбинируются для получения любого цвета. Значение lopnColor должно
быть в форме 00x00bbggrr, где bb это значение для синего цвета, gg -
значение для зеленого цвета, а rr - значение для красного цвета. Диапазон
интенсивности лежит в границах от 0 до 255 (от 0 до 0xFF). Следующая
таблица представляет некоторые простые значения цветов.

Таблица 18.2  Простые RGB-значения цветов

Значение     Цвет
----------------------
0x00000000   Черный
0x00FFFFFF   Белый
0x000000FF   Красный
0x0000FF00   Зеленый
0x00FF0000   Синий
0x00808080   Серый

    Как альтернативу, вы можете использовать макрос RGB для получения
цветов. RGB(0,0,0) возвращает черный, RGB(255,0,0) возвращает красный и
т. д.

    Логические кисти

    Вы можете создавать логические кисти с помощью функций GDI
CreateHatchBrush, CreatePatternBrush, CreateDIBPatternBrush или
CreateBrushIndirect. Например,

    TheBrush = CreateHatchBrush(HS_VERTICAL, RGB(0, 255, 0));
    TheBrush = CreateBrushIndirect(&ALogBrush);

    Далее приводится определение структуры LOGBRUSH:

    typedef struct tagLOGBRUSH {
      WORD   lbStyle;
      POINT  lbColor;
      DWORD  lbHatch;
    } LOGBRUSH;

    Поле lbStyle содержит константу типа слово, указывающую стиль кисти:

* BS_DIBPATTERN указывает, что кисть, определяющаяся шаблоном, определяется
независящим от устройства побитовым отображением.
* BS_HATCHED задает один из предопределенных штрихованных шаблонов (см.
lbHatch).
* BS_HOLLOW задает пустую кисть.
* BS_PATTERN использует левый верхний угол, размером 8 на 8 пикселей,
побитового отображения, находящегося в данный момент в памяти.
* BS_SOLID задает непрерывную кисть.

    Поле lbColor содержит значение цвета, подобное соответствующему
значению для записей LOGPEN. Это поле игнорируется кистями со стилями
BS_HOLLOW и BS_PATTERN.
    Поле lbHatch содержит целую константу, указывающую штрихованный шаблон
для кистей со стилем BS_HATCHED. Если используется стиль BS_DIBPATTERN,
lbHatch содержит дескриптор побитового отображения.

Рис. 18.2  Стили штриховки для средств кисти

    Логические шрифты

    Вы можете создавать логические шрифты, используя функции GDI CreateFont
или CreateFontIndirect.
    Далее приводится определение структуры LOGFONT:

    typedef struct tagLOGFONT {
      int   lfHeight;
      int   lfWidth;
      int   lfEscapement;
      int   lfOrientation;
      int   lfWeight;
      BYTE  lfItalic;
      BYTE  lfUnderline;
      BYTE  lfStrikeOut;
      BYTE  lfCharSet;
      BYTE  lfOutPrecision;
      BYTE  lfClipPrecision;
      BYTE  lfQuality;
      BYTE  lfPitchAndFamily;
      BYTE  lfFaceName[LF_FACESIZE];
    } LOGFONT;

    Когда вы используете LOGFONT для создания средства шрифта, вы задаете
атрибуты желаемого шрифта. Однако, ваша программа не использует этой
информации для генерации экранного шрифта. Вместо этого она отображает
запрос шрифта в экранный шрифт, который в данный момент определен в сеансе
Windows.
    Поле lfHeight задает желаемую высоту шрифта. Нулевое значение имеет
результатом стандартный размер. Положительное значение это запрашиваемая
высота ячейки в логических единицах. Отрицательное значение преобразуется в
положительное и является запрашиваемой высотой символа в логических
единицах.
    Поле lfWidth задает желаемую ширину в единицах устройства. Если оно
равно нулю, сохраняется коэффициент сжатия.
    Для повернутого текста установите lfEscapement в значение в десятых
долях градуса, на которое текст будет повернут против часовой стрелки.
fOrientation делает такой же поворот для каждого символа.
    Желаемая отчетливость задается в lfWeight. Во можете использовать
константы отчетливости шрифта, такие как FW_LIGHT, FW_NORMAL, FW_BOLD и
FW_DONTCARE, или значение между 0 и 1000.
    Три атрибута шрифта - курсив, подчеркнутый и зачеркнутый -
запрашиваются с помощью ненулевых значений в lfItalic, lfUnderline и
lfStrikeOut.
    Поле lfCharSet требует специального набора символов, ANSI_CHARSET,
OEM_CHARSET или SYMBOL_CHARSET. Набор символов ANSI представлен в
приложении B "Руководства пользователя" (User's Guide) Microsoft Windows.
OEM_CHARSET является системно-зависимым.
    Поле lfOutPrecision указывает, как точно шрифт, предоставляемый
Windows, должен соответствовать запросам размера и позиции. Стандартным
значением является OUT_DEFAULT_PRECIS. Поле lfClipPrecision указывает, как
усекать частично видимые символы. Стандартным значением является
CLIP_DEFAULT_PRECIS.
    Поле lfQuality указывает, как точно шрифт, предоставляемый Windows,
соответствует запрошенным атрибутам шрифта. Оно может быть установлено в
DEFAULT_QUALITY, DRAFT_QUALITY или PROOF_QUALITY. Со значением
PROOF_QUALITY жирные, курсивные, подчеркнутые и зачеркнутые шрифты
синтезируются, если не являются доступными.
    Поле lfPitchAndFamily запрашивает шаг и семейство шрифта. Оно может
быть результатом поразрядной операции ИЛИ | между одной константой шага и
одной константой семейства.

Таблица 18.3  Константы шага и семейства шрифта

Константы шага    Константы семейства
--------------------------------------
DEFAULT_PITCH     FF_MODERN
FIXED_PITCH       FF_ROMAN
VARIABLE_PITCH    FF_SCRIPT
                  FF_SWISS
                  FF_DECORATIVE
                  FF_DONTCARE

    Наконец, lfFaceName является строкой, которая задает требуемое
начертание. Если значение равно 0, вы получите начертание, базирующееся на
значениях в других полях LOGFONT.
    Далее приводится простой набор шрифтов с текстом программы, которая
определяет их записи LOGFONT:

    void MyWindow::MakeFont()
    {
      LOGFONT MyLogFont;

      MyLogFont.lfHeight         = 30;
      MyLogFont.lfWidth          = 0;
      MyLogFont.lfEscapement     = 0;
      MyLogFont.lfOrientation    = 0;
      MyLogFont.lfWeight         = FW_BOLD;
      MyLogFont.lfItalic         = 0;
      MyLogFont.lfUnderline      = 0;
      MyLogFont.lfStrikeOut      = 0;
      MyLogFont.lfCharSet        = ANSI_CHARSET;
      MyLogFont.lfOutPrecision   = OUT_DEFAULT_PRECIS;
      MyLogFont.lfClipPrecision  = CLIP_DEFAULT_PRECIS;
      MyLogFont.lfQuality        = DEFAULT_QUALITY
      MyLogFont.lfPitchAndFamily = VARIABLE_PITCH | FF_SWISS;
      strcpy(MyLogFont.lfFaceName, "Helv");

      TheFont =CreateFontIndirect(&MyLogFont);
    }


    void MyWindow::MakeFont()
    {
      LOGFONT MyLogFont;

      MyLogFont.lfHeight         = 10;
      MyLogFont.lfWidth          = 0;
      MyLogFont.lfEscapement     = 0;
      MyLogFont.lfOrientation    = 0;
      MyLogFont.lfWeight         = FW_NORMAL;
      MyLogFont.lfItalic         = TRUE;
      MyLogFont.lfUnderline      = TRUE;
      MyLogFont.lfStrikeOut      = 0;
      MyLogFont.lfCharSet        = ANSI_CHARSET;
      MyLogFont.lfOutPrecision   = OUT_DEFAULT_PRECIS;
      MyLogFont.lfClipPrecision  = CLIP_DEFAULT_PRECIS;
      MyLogFont.lfQuality        = DEFAULT_QUALITY;
      MyLogFont.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
      strcpy(MyLogFont.lfFaceName, "Courier");

      TheFont =CreateFontIndirect(&MyLogFont);
    }


    void MyWindow::MakeFont()
    {
      LOGFONT MyLogFont;

      MyLogFont.lfHeight         = 30;
      MyLogFont.lfWidth          = 0;
      MyLogFont.lfEscapement     = 0;
      MyLogFont.lfOrientation    = 0;
      MyLogFont.lfWeight         = FW_NORMAL;
      MyLogFont.lfItalic         = 0;
      MyLogFont.lfUnderline      = 0;
      MyLogFont.lfStrikeOut      = 0;
      MyLogFont.lfCharSet        = SYMBOL_CHARSET;
      MyLogFont.lfOutPrecision   = OUT_DEFAULT_PRECIS;
      MyLogFont.lfClipPrecision  = CLIP_DEFAULT_PRECIS;
      MyLogFont.lfQuality        = PROOF_QUALITY;
      MyLogFont.lfPitchAndFamily = VARIABLE_PITCH | FF_ROMAN;
      strcpy(MyLogFont.lfFaceName, "Tms Rmn");

      TheFont =CreateFontIndirect(&MyLogFont);
    }

         Представление графики в окнах

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

    Рисование в окнах

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

    Управление контекстом представления

    Обычно вы будете определять компоненту данных окна для сохранения
дескриптора текущего контекста представления подобно тому, как HWindow
сохраняет дескриптор окна:

    class TMyWindow : public TWindow
    {
      HDC TheDC;
    ...
    };

    Для получения контекста представления для окна надо вызвать функцию
Windows GetDC:

    TheDC = GetDC(HWindow);

    Теперь вы можете выполнять операции рисования в контексте
представления, передавая дескриптор контекста представления в функции GDI,
такие как Rectangle:

    Rectangle (TheDC, 10, 10, 100, 1000);

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

    ReleaseDC(HWindow, TheDC);

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

    Вызов оконных графических функций

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

    void TMyWindow::DrawText
    {  TheDC = GetDC(HWindow);
       TextOut(TheDC, 50, 50, "Sample Text", 11);
       ReleaseDC(HWindow, TheDC);
    }

    Отрисовка окон

    Когда окно нуждается в отрисовке, оно становится некорректным, что
означает, что его представление не является более корректным и нуждается в
обновлении. Это происходит, когда окно появляется первоначально, когда оно
восстанавливается после минимизации в пиктограмму, или когда появляется
из-под другого окна. Во всех этих случаях Windows посылает сообщение
WM_PAINT соответствующему приложению.
    TWindow определяет функцию-компоненту реакции на сообщение WM_PAINT,
TWindow::WMPaint, которая (помимо других вещей)

* получает контекст представления
* вызывает виртуальную функцию-компоненту Paint, передавая контекст
представления в качестве параметра
* освобождает контекст представления

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

    Стратегии графики

    Функция-компонента Paint ответственна за отрисовку текущего содержания
окна в любое время, включая первое появление окна. Поэтому,
функция-компонента Paint должна быть способной рисовать всю оконную
"постоянную" графику. Кроме того, она должна иметь возможность заново
создавать всю графику, добавляемую в окно после его начального появления.
Для создания этой "динамической" графики функция-компонента Paint должна
иметь доступ к командам или данным, с помощью которых создается графика.
    Имеется два подхода для получения такого доступа. Первый состоит в
изоляции вашей программы графики в функциях-компонентах и вызове этих
функций-компонент, когда создается графика, причем и из функции-компоненты
Paint. Другой подход, представленный в пятом Шаге в Главе 3, состоит в
сохранении данных, относящихся к графическому содержанию окна, в компоненте
объекта окна. Эти данные могут включать в себя, например, координаты,
формулы и побитовые отображения. Тогда вы можете в функции-компоненте Paint
выполнять процедуры графики, требуемые для преобразования данных в графику.
    Используя эти стратегии и возможность объекта сохранять его собственные
данные и функции, вы можете создавать работоспособные графические
приложения.

    Использование средств рисования

    Контекст представления кроме предоставления возможности рисовать в окне
содержит средства рисования: карандаши, кисти, шрифты и палитры, которые
используются для рисования текста и графики. Когда вы рисуете линию в
контексте представления, линия появляется с атрибутами текущего карандаша,
такими как цвет, стиль (непрерывный, точечный и т. д.)и толщина. Когда вы
рисуете область, она появляется с атрибутами текущей кисти, такими как
шаблон заполнения и цвет. Когда вы рисуете текст в контексте представления,
он появляется со шрифтом (Modern, Roman, Swiss и т. д.), размером и стилем
(курсив, выделенный и т. д.) текущего средства шрифта. Палитра содержит
набор доступных в данный момент цветов.
    Контекст представления содержит по одному из каждого типа средств
рисования. Заново полученный контекст представления содержит стандартные
средства: тонкий черный карандаш, непрерывную черную кисть, системный шрифт
и стандартную палитру. Если вы удовлетворены этими средствами, вам не нужно
их модифицировать.
    Для изменения этих стандартных средств вы должны получить дескриптор
нового элемента средства и выбрать его в контексте представления. Когда вы
выбираете, например, новый карандаш, выбор старого карандаша автоматически
удаляется. Мы рекомендуем вам сохранять предыдущие средства и выбирать их
заново после использования нового средства:

    {
      HPEN NewPen, OldPen;
      HDC  TheDC;

      // создание карандаша с толщиной 10
      NewPen = CreatePen(PS_SOLID, 10, RGB(0,0,0));
      TheDC = GetDC(AWindow->HWindow);
      OldPen = SelectObject(TheDC, NewPen);

      // выполнение рисования
      SelectObject(TheDC, OldPen);
      ReleaseDC(AWindow->HWindow, TheDC);
      DeleteObject(NewPen);
    }

    Как показано в этом примере, новые средства рисования создаются, а
после уничтожаются. Подобно контекстам представления они являются
элементами, сохраняемыми в памяти Windows. Отсутствие удаления их вызывает
потери в памяти и возможные сбои. Также, подобно контекстам представления
вы должны сохранять дескрипторы средств рисования в переменных типа HPEN,
HBRUSH, HFONT или HPALETTE.
    Функция Windows DeleteObject удаляет средства рисования из памяти
Windows. Не удаляйте средства рисования, являющиеся выбранными в данный
момент в контексте представления!
    Хотя контекст представления одновременно может иметь только один
выбранный элемент в каждом типе средства рисования, вы можете хранить
доступные средства рисования. Важно всегда удалять их перед прекращением
работы вашего приложения. Один подход, используемый в примере в Главе 3,
состоит в определении компоненты данных окна, называющейся ThePen, для
сохранения дескриптора текущего средства карандаша. Когда пользователь
выбирает новый стиль карандаша, новое средство карандаша создается, а
старое удаляется. Последний карандаш удаляется в деструкторе главного окна.
Вы не должны удалять стандартные средства, предоставляемые с новым
полученным контекстом представления.
    Имеются два способа получить дескриптор новых средств рисования. Самый
легкий подход состоит в использовании альтернативных имеющихся средств,
которые перечислены в Таблице 18.1.
    Для установления имеющегося средства в объект контекста представления
используйте функции-компоненты SetStockPen, SetStockBrush, SetStockFont и
SetStockPalette. Например,

    ThePen = GetStockObject(BLACK_PEN);

    Не удаляйте имеющиеся средства из памяти Windows, как это вы делаете
для собственных средств.
    Иногда не находится имеющегося средства с желаемыми атрибутами.
Например, все имеющиеся карандаши производят тонкие линии, а вам хочется
получить толстую линию. В этом случае имеются два пути для создания
адаптированных средств рисования. Один путь состоит в вызове функций
Windows CreatePen, CreateFont, CreateSolidBrush или CreateDIBPatternBrush.
Эти функции используют параметры, описывающие желаемое средство, и
возвращают дескрипторы средств, которые могут использоваться в вызовах
SelectObject.
    Другой путь создания адаптированных средств состоит в построении
описания атрибутов средства, называющегося логическим средством. Логическое
средство воплощается структурами данных Windows LOGPEN, LOGBRUSH, LOGFONT и
LOGPALETTE. Например, LOGPEN имеет поля, содержащие толщину, цвет и стиль.
Когда вы установили структуру данных логического средства, вы можете
передать ее в качестве параметра в CreatePenIndirect, CreateBrushIndirect,
CreateFontIndirect или CreatePalette. Эти функции возвращают дескрипторы
средств, которые могут использоваться в вызовах SelectObject. Этот пример
устанавливает цвет карандаша контекста представления в синий:

    void SampleWindow::ChangePenToBlue()
    {
      LOGPEN ALogPen;
      HPEN ThePen;

      ALogPen.lopnColor = RGB(0, 0, 255);
      ALogPen.lopnStyle = PS_SOLID;
      ALogPen.lopnWidth.X = 0;
      ALogPen.lopnWidth.Y = 0;
      ThePen = CreatePenIndirect(&ALogPen);
      SelectObject(TheDC, ThePen);
    }

         Функции рисования GDI

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

    Рисование текста

    Функции GDI для рисования текста используют текущий шрифт заданного
контекста представления. TextOut рисует текст с заданной точки. В
зависимости от значения флагов текущего форматирования текста TextOut
выравнивает текст. Стандартным является выравнивание слева. Текущее
выравнивание может быть получено с помощью GetTextAlign и установлено с
помощью SetTextAlign.
    TextOut это наиболее просто используемая функция рисования текста.
Используя стандартный набор флагов форматирования текста, следующая
функция-компонента Paint рисует массив символов с выравниванием слева в
координатах (10,15) пользовательской области окна.

    void TMyWindow::Paint(HDC PaintDC, PAINTSTRUCT& PaintInfo)
    {
      char MyTextString[20];

      strcpy(MyTextString, "Hello, World");
      TextOut(PaintDC, 10, 15, MyTextString, strlen(MyTextString));
    }

Рис. 18.3  Результат работы функции TextOut

    Рисование линий

    Функции GDI, рисующие линии, используют для рисования текущий карандаш
заданного контекста представления. Большинство линий рисуются с
использованием функций MoveTo и LineTo. Эти функции влияют на атрибут
контекста представления, называемый текущей позицией. Если использовать
аналогию с рисованием авторучкой на бумаге, то текущая позиция будет
точкой, где авторучка касается бумаги.

    Move To и LineTo

    MoveTo перемещает текущую позицию в заданную координату. Функция LineTo
рисует линию, используя карандаш, начиная с текущей позиции, до заданной
координаты, но не включая ее. Заданная координата затем становится текущей
позицией. Следующая функция-компонента Paint рисует линию от (100,150) до
(10,15):

    void TMyWindow::Paint(HDC PaintDC, PAINTSTRUCT& PaintInfo) {
      MoveTo(PaintDC, 100, 150);
      LineTo(PaintDC, 10, 15);
    }

Рис. 18.4  Результат работы функции LineTo

    Polyline

    Функция Polyline рисует серию линий, соединенных в точках. Это подобно
первоначальному вызову MoveTo и последующих вызовах функций LineTo; однако,
Polyline выполняет эту операцию намного быстрее и не влияет на текущую
позицию карандаша. Следующая функция-компонента Paint рисует правый нижний
угол.

    void TMyWindow::Paint(HDC PaintDC, PAINTSTRUCT& PaintInfo) {
      POINT Points[3];

      Points[0].x = 10;
      Points[0].y = 15;
      Points[1].x = 10;
      Points[1].y = 150;
      Points[2].x = 100;
      Points[2].y = 150;
      Polyline(PaintDC, Points, 3);
    }

Рис. 18.4  Результат работы функции Polyline

    Arc

    Функция Arc рисует дугу по периметру эллипса, ограниченного заданным
прямоугольником. Дуга начинается с пересечения границы эллипса и линии,
проходящей из центра ограничивающего прямоугольника в заданную начальную
точку. Дуга рисуется против часовой стрелки до точки пересечения границы
эллипса с линией, проходящей из центра эллипса в заданную конечную точку.
    Следующая функция-компонента Paint рисует выделенную цветом дугу с
помощью задания ограничивающего прямоугольника с координатами (10,10),
(40,40), начальной точкой (0,0) и конечной точкой (50,0). Дуга рисуется,
даже если заданные начальные и конечные точки не лежат на дуге.

    void TMyWindow::Paint(HDC PaintDC, PAINTSTRUCT& PaintInfo) {
      Arc(PaintDC, 10, 10, 40, 40, 50, 0, 0, 0);
    }

Рис. 18.6  Результат работы функции Arc

    Arc не влияет на текущую позицию.

    Рисование форм

    Функции GDI, рисующие формы, используют текущий карандаш заданного
контекста представления для рисования периметра и текущую кисть для
заполнения внутреннего пространства. Они не влияют на текущую позицию.

    Rectangle

    Rectangle рисует прямоугольник от левого верхнего угла до правого
нижнего. Например, следующее утверждение в функции-компоненте Paint рисует
прямоугольник от (10,15) до (100,150).

    Rectangle(PaintDC, 10, 15, 100, 150);

Рис. 18.7  Результат работы функции Rectangle

    RoundRect

    RoundRect рисует прямоугольник с закругленными углами. Закругленные
углы определяются как четверти эллипса. Например, следующее утверждение в
функции-компоненте Paint рисует прямоугольник от (10,15) до (100,150), углы
которого будут четвертями эллипса с шириной 9 и высотой 11.

    RoundRect(PaintDC, 10, 15, 100, 150, 9, 11);

Рис. 18.8  Результат работы функции RoundRect

    Ellipse

    Ellipse рисует эллипс, определенный ограничивающим прямоугольником.
Следующий пример рисует эллипс внутри прямоугольника от (10,15) до
(100,150).

    Ellipse(PaintDC, 10, 50, 100, 150);

Рис. 18.9  Результат работы функции Ellipse

    Pie и Chord

    Pie и Chord рисуют участки эллипса. Они обе рисуют дугу подобно функции
Arc. Однако, Pie и Chord создают формы. Функция Pie соединяет центр эллипса
с концами дуги. Следующая функция Pie рисует верхнюю четверть круга внутри
ограничивающего прямоугольника (10,10), (40,40).

    Pie(PaintDC, 10, 10, 40, 40, 50, 0, 0, 0);

Рис. 18.10  Результат работы функции Pie

    Функция Chord соединяет два конца дуги.

    Chord(PaintDC, 10, 10, 40, 40, 50, 0, 0, 0);

Рис. 18.11  Результат работы функции Chord

    Polygon

    Polygon рисует последовательные сегменты линий подобно функции Polyline
за исключением того, что функция Polygon замыкает форму соединением
последней заданной точки с первой заданной точкой. Она заполняет
многоугольник с помощью текущей кисти. Следующая функция-компонента Paint
рисует правильный треугольник.

    void TMyWindow::Paint(HDC PaintDC, PAINTSTRUCT& PaintInfo)
    {
      POINT Points[3];

      Points[0].x = 10;
      Points[0].y = 15;
      Points[1].x = 10;
      Points[1].y = 150;
      Points[2].x = 100;
      Points[2].y = 150;
      Polygon(PaintDC, Points, 3);
    }

Рис. 18.12  Результат работы функции Polygon

         Использование палитр

    Некоторые типы компьютерных устройств отображения имеют возможность
представлять много цветов, но только несколько сразу. Системная или
физическая палитра это группа или набор цветов, которые в данный момент
возможны для одновременного представления на устройстве. Windows дает
вашему приложению частичное управление тем, какие цвета использовать в
текущей палитре. Если ваше приложение использует только простые цвета, вам
не нужно прямо использовать палитру.
    Однако, модификация системной палитры влияет на все рисование на
экране, включая окна других приложений. Модификация одного приложения
сделает некорректными цвета всех остальных. Менеджер палитры Windows решает
эту проблему с помощью посредничества между приложениями, пытающимися
изменить системную палитру. Windows предоставляет каждому приложению
логическую палитру, которая является группой цветов, необходимых
приложению. Менеджер палитры преобразует требуемые цвета из логической
палитры в доступные цвета в системной палитре. Если требуемый цвет не
перечислен в системной палитре, менеджер палитры может добавить цвет в
системную палитру. Если в системной палитре не осталось свободного
пространства, дополнительные цвета приводятся в соответствие с наиболее
близким доступным цветом в системной палитре.
    Когда приложение становится активным, оно получает возможность
заполнить системную палитру цветами из своей логической палитры. Это
действие может выбросить цвета, заданные логическими палитрами других
приложений. Во всех случаях Windows резервирует 20 постоянных цветов в
системной палитре для общего сохранения цветовой схемы каждого приложения и
собственно Windows.

    Установка палитры

    Подобно карандашам и кистям, описанным в параграфе "Средства
рисования", логические палитры являются средствами рисования. Для создания
логической палитры используйте CreatePalette, которая возвращает дескриптор
логической палитры, который вы можете передать функции SelectPalette для
выбора палитры в контекст представления. Вы можете передать указатель на
структуру LOGPALETTE в CreatePalette. Структура LOGPALETTE содержит
информацию заголовка и массив элементов палитры. Каждый элемент палитры
является структурой PALETTEENTRY, которая имеет три компоненты для задания
цвета (peRed, peGreen и peBlue) и одну для флагов (peFlags). Следующий
пример создает стандартную палитру, содержащую 20 цветов, которые всегда
представлены в системной палитре:

    GetStockObject(DEFAULT_PALETTE);

    После того, как приложение выбрало свою палитру в контекст
представления, используя SelectPalette, оно должно "реализовать" палитру
перед использованием ее. Это делается с помощью функции Windows
RealizePalette:

    ThePalette = CreatePalette(&ALogPalette);
    SelectPalette(TheDC, ThePalette, 0);
    RealizePalette(TheDC);

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

    Рисование с помощью палитр

    Когда ваша палитра реализована, вы можете рисовать, используя ее цвета.
Вы можете задавать цвета палитры прямо или косвенно. Для прямого задания
цвета палитры используйте индекс палитры COLORREF. Индекс палитры COLORREF
это значение типа DWORD со старшим байтом, установленным в 1 и индексом
элемента логической палитры в двух младших байтах. Например, 0x01000009
задает девятый элемент в логической палитре. Это значение может
использоваться везде, где предполагается аргумент COLORREF. Например,

    ALogPen.lopnColor = 0x01000009;

    Если ваше устройство отображения допускает полный 24-х битовый цвет с
несистемной палитрой, использование индекса палитры излишне ограничивает
вас цветами вашей логической палитры. Для того, чтобы избежать это
ограничение, вы можете задать цвета палитры косвенно, относительное
значение палитры COLORREF. Это значение подобно явному RGB COLORREF за
исключением того, что старший бит устанавливается в 2. Младшие три байта
содержат RGB значение цвета. Например, 0x020000FF будет задавать
относительное значение палитры COLORREF для чистого красного цвета. Если
устройство поддерживает системную палитру, Windows будет подбирать RGB
информации наиболее близкий цвет в выбранной логической палитре; если
устройство не поддерживает системную палитру, COLORREF используется, как
если бы было задано явное RGB значение.

    Запросы к палитрам

    Вы можете вызвать функцию GetPaletteEntries для получения информации о
палитре. GetPaletteEntries заполняет заданный массив структур PALETTEENTRY
элементами палитры. Индекс первого элемента палитры и число возвращаемых
элементов также задаются в вызове GetPaletteEntries.

    Модификация палитры

    Вы можете изменить элементы логической палитры вызовом функции
SetPaletteEntries или AnimatePalette. SetPaletteEntries использует те же
аргументы, что и GetPaletteEntries, но изменяет заданные элементы на
элементы, указанные третьим аргументом. Заметим, что изменения не будут
отображаться в системную палитру, пока не будет вызвана функция
RealizePalette, и не будут видны, пока не будет перерисована
пользовательская область. AnimatePalette использует те же аргументы, что и
SetPaletteEntries, но используется, когда приложение хочет изменить палитру
и сделать эти изменения немедленно видимыми. Когда вызывается
AnimatePalette, элементы палитры с полем peFlags, установленным в константу
PC_RESERVED, будут заменены соответствующими новыми элементами и немедленно
преобразованы в системной палитре. Остальные элементы не изменяются.
    Например, вы можете получить десять первых элементов палитры, изменить
их значения, увеличивая красную компоненту и уменьшая синюю и зеленую
компоненты, а затем сделать изменения вступившими в силу (подразумеваются
элементы палитры, имеющие PC_RESERVED):

    GetObject(ThePalette, sizeof (NumEntries), &NumEntries);
    if (NumEntries >= 10) {
      GetPaletteEntries(ThePalette, 0, 10, &PaletteEntries);
      for (int i = 0; i <= 9; ++i)
      {
        PaletteEntries[i].peRed = PaletteEntries[i].peRed + 40;
        PaletteEntries[i].peGreen = PaletteEntries[i].peGreen - 40;
        PaletteEntries[i].peBlue = PaletteEntries[i].peBlue - 40;
      }
      AnimatePalette(ThePalette, 0, 10, &PaletteEntries);
    }

    Вместо AnimatePalette вы можете использовать

    SetPaletteEntries(ThePalette, 0, 10, &PaletteEntries);
    RealizePalette(ThePalette);

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

    Реакция на изменение палитры

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

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

    UpdateColors обычно работает быстрее, чем перерисовка пользовательской
области, но при ее использовании могут быть некоторые потери в точности
цветов.
    Поле WParam в сообщении WM_PALETTECHANGED содержит дескриптор окна,
реализовавшего палитру. Если вы в качестве реакции реализуете свою
логическую палитру, вы можете избежать создания бесконечного цикла,
убедившись в начале, что этот дескриптор не является дескриптором вашего
окна.
    Приложение PALETTE.APP, находящееся в директории EXAMPLES, создает и
реализует логическую палитру с восемью цветами в ней. Когда вы делаете
щелчок левой кнопки мыши, она рисует квадраты каждого цвета. Когда вы
делаете щелчок правой кнопки мыши, она вращает цвета в логической палитре.
Индекс палитры COLORREF используется так, что когда логическая палитра
изменяется, и квадраты перерисовываются, их цвета будут изменяться. Макрос
PALETTEINDEX используется для создания индекса палитры COLORREF.




    ГЛАВА 19

         РЕСУРСЫ В ПОДРОБНОСТЯХ

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

* Меню
* Блоки диалога
* Пиктограммы
* Курсоры
* Клавиатурные акселераторы
* Побитовые отображения
* Символьные строки

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

         Создание ресурсов

    Вы можете создавать ресурсы с помощью редактора ресурсов или
Компилятора ресурсов. Borland C++ поддерживает оба метода, так что вы
можете выбрать наиболее подходящий способ. В большинстве случаев легче
использовать редактор ресурсов и визуально создавать ресурсы. Однако,
иногда удобно использовать компилятор ресурсов для компиляции файлов
описания ресурсов, которые представлены в книгах или журналах.
    Вне зависимости от используемого подхода вы обычно создаете файл
ресурсов (.RES) для каждого приложения. Этот файл ресурсов содержит
бинарную информацию для всех меню, диалогов, побитовых отображений и других
ресурсов, используемых вашим приложением.
    Бинарный файл ресурсов (.RES) добавляется к вашему выполняемому файлу
(.EXE) с помощью Компилятора ресурсов, как описано ниже в этой главе. Вы
должны также описать в программе загрузку ресурсов в памяти. Каждый ресурс
должен загружаться в память отдельно. Тогда программа будет использовать
память только для необходимого в данный момент ресурса. Загрузка ресурсов в
память описана в этой главе.

         Добавление ресурсов к выполняемому файлу

    Когда ресурсы будут сохранены в бинарном формате в файле .RES, вы
должны будете добавить их к выполняемому файлу программы (.EXE). В
результате получится файл, содержащий скомпилированную программу
приложения, а также его ресурсы.
    Имеется два способа добавить ресурсы к выполняемому файлу:

* Использовать редактор ресурсов для копирования ресурсов из файла .RES в
уже скомпилированный файл программы .EXE.
* Использовать Компилятор ресурсов (RC) как показано в следующем параграфе
для того, чтобы скопировать ресурсы из файла .RES в уже скомпилированный
файл программы .EXE.

         Использование Компилятора ресурсов

    Этот параграф описывает использование Компилятора ресурсов Microsoft
Resource Compiler для создания файлов ресурсов. Компилятор ресурсов это
программа для DOS, использующая на входе файл описания ресурсов и
генерирующая бинарный файл ресурсов. Компилятор ресурсов может также
добавлять ресурсы прямо в выполняемый файл.
    Компилятор ресурсов запускается в DOS. Формат командной строки имеет
вид:

    RC [параметры] <.RC исходный файл> [.EXE результирующий файл]

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

    RC SAMPPROG.RC

создает файл SAMPPROG.RES из исходного файла описания SAMPPROG.RC и
добавляет его прямо в файл SAMPPROG.EXE, заменяя существующие ресурсы.
    В каждом случае программа SAMPPROG.CPP на языке C++ должна включать в
себя команды для загрузки ресурсов в память.
    Допустимые параметры командной строки Компилятора ресурсов представлены
в следующей таблице.

Таблица 19.1  Параметры командной строки Компилятора ресурсов

Параметр   Значение
---------------------------------------------------------------------------
 -r        Создать .RES файл
 -l        Создать приложение, использующее LIM 3.2 EMS
 -e        Создать драйвер, использующий EMS память
 -m        Установить флаг многоэкземплярности
 -p        Создать библиотеку типа private
 -t        Создать приложение только для защищенного режима
 -v        Информативный режим (печатать сообщения о ходе выполнения)
 -d        Определить идентификатор
 -fo       Переименовать .RES файл
 -fe       Переименовать .EXE файл
 -i        Добавить маршрут для поиска INCLUDE
 -x        Игнорировать переменную среды INCLUDE
 -k        Сохранить сегменты в порядке .DEF файла (не сортировать сегменты
           для быстрой загрузки)

    Файлы описания Компилятора ресурсов

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

    ; DAMPPROG.RC -- пример файла описания ресурсов.
    ;             -- Это описание иллюстрирует использование ресурсов
    ;                пиктограмм, строк, меню и диалогов в Windows.
    ;             -- Это описание должно компилироваться с помощью
    ;                компилятора ресурсов, а затем добавляться к программе
    ;                C++ с помощью директивы Компилятора ресурсов.

    ; подключаемые файлы заголовков для определения констант:
    #include 
    #include 
    #include 

    ; определение пиктограмм в отдельных файлах
    SampleIcon ICON sample.ico

    ; строки могут определяться численно или символьно
    STRINGTABLE
    BEGIN
      IDS_NOPROGRM, "Program unavailable"
      IDS_INVALID, "Invalid entry"
      IDS_FATAL, "Fatal error!"
    ;  .
    ;  . здесь идут другие строки
    ;  .
    END

    ; Меню определяют команды для пользователя
    SampleMenu MENU
    BEGIN
      POPUP "&File"
        BEGIN
          MENUITEM "&New..\tCtrl+N",        CM_FILENEW
          MENUITEM "&Open..\tCtrl+O",       CM_FILEOPEN
          MENUITEM "&Save..\tCtrl+S",       CM_FILESAVE
          MENUITEM SEPARATOR
          MENUITEM "&Print \tCtrl+P",       CM_FILEPRINT
        END
      POPUP "&Edit"
        BEGIN
          MENUITEM "&Undo \tAlt+BkSp",      CM_EDITUNDO
          MENUITEM SEPARATOR
          MENUITEM "&Cut \tShift+Del",      CM_EDITCUT
          MENUITEM "&Copy \tIns",           CM_EDITCOPY
          MENUITEM "&Paste \tShift+Ins",    CM_EDITPASTE
          MENUITEM "&Delete \tDel",         CM_EDITDELETE
          MENUITEM "&Clear All \tCtrl+Del", CM_EDITCLEAR
        END
      POPUP "&View"
        BEGIN
          MENUITEM "Summary \tF2",          CM_VIEWSUMMARY
          MENUITEM "Graph \tF3",            CM_VIEWGRAPH
        END
    END

    ; Акселераторы предоставляют "горячие ключи" для меню, команд
    SampleAccelerators ACCELERATORS
    BEGIN
      "^n", CM_FILENEW
      "^o", CM_FILEOPEN
      "^s", CM_FILESAVE
      "^p", CM_FILEPRINT
      VK_DELETE, CM_EDITCUT, VIRTKEY, SHIFT
      VK_INSERT, CM_EDITCOPY, VIRTKEY
      VK_INSERT, CM_EDITPASTE, VIRTKEY, SHIFT
      VK_DELETE, CM_EDITDELETE, VIRTKEY
      VK_DELETE, CM_EDITCLEAR, VIRTKEY, CONTROL
      VK_BACK, CM_EDITUNDO, VIRTKEY, ALT
      VK_F2, CM_VIEWSUMMARY, VIRTKEY
      VK_F3, CM_VIEWGRAPH, VIRTKEY
    END

    ; Блоки диалогов представляют информацию для пользователя
    AboutBox DIALOG 20, 20, 160, 80
    CAPTION "About SAMPPROG"
    STYLE DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
    FONT 8, "Helv"
    BEGIN
      CTEXT "Sample Program"           -1,   0,  10, 160,  10
      CTEXT "Written in ObjectWindows" -1,   0,  26, 160,  10
      CTEXT "for Windows 3.0"          -1,   0,  36, 160,  10
      ICON  "SAMPLE_ICON"              -1,   8,   8,   0,   0
      DEFPUSHBUTTON "OK",            IDOK,  60,  56,  40,  14
    END

    FileOpen DIALOG 20, 20, 204, 124
    CAPTION "File Open"
    STYLE DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
    FONT 8, "Helv"
    BEGIN
      LTEXT    "File&name:",    -1,   6,   8,  36,  10
      EDITTEXT                 100,  42,   6,  98,  12, ES_AUTOHSCROLL
      LTEXT    "Directory:",    -1,   6,  20,  36,  10
      LTEXT    ""              101,  42,  20,  98,  10
      LTEXT    "&Files:",       -1,   6,  32,  64,  10
      LISTBOX  102, 6, 44, 64, 82, WS_TABSTOP | WS_VSCROLL | LBS_SORT
      LTEXT    "&Directories:", -1,  76,  32,  64,  10
      LISTBOX  103, 76, 44, 64, 82, WS_TABSTOP | WS_VSCROLL | LBS_SORT
      DEFPUSHBUTTON "OK",         IDOK, 146,  24,  50,  14
      PUSHBUTTON    "Cancel", IDCANCEL, 146,  24,  50,  14
    END

    Файл заголовков SAMPPROG.H содержит определения констант C-стиля. Далее
представлен типичный файл заголовков:

    /* SAMPPROG.H -- файл заголовков с константами для SAMPPROG.RC */
    #define IDS_NOPROGRM      601
    #define IDS_INVALID       602
    #define IDS_FATAL         603

    #define CM_FILENEW        701
    #define CM_FILESAVE       702
    /*
       .
       . здесь указаны остальные константы
       .
    */

    Советы по работе с Компилятором ресурсов

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

    1. Комментарии начинаются с точки с запятой (;) и продолжаются до конца
строки или заключаются в /* и */.
    2. Компилятор ресурсов не чувствителен к регистру букв ключевых слов,
таких как BEGIN, END.
    3. Компилятор ресурсов чувствителен к регистру букв имен
идентификаторов, таких как CM_FILENEW, AboutBox.
    4. Компилятор ресурсов чувствителен к использованию пробелов.
    5. Для подключения файлов заголовков с константами, определенных с
помощью #define, вы должны использовать директивы #include.
    6. Вы можете использовать директиву rcinclude для подключения других
файлов описания ресурсов (*.RC или *.DLG); например, для подключения файла
блока диалога ObjectWindows.
    7. Каждый идентификатор элемента меню или идентификатор строки должен
быть уникальным.
    8. Меню могут быть вложенными.
    9. Внутри меню и управляющих элементов диалогов амперсенд (&) указывает
программе подчеркнуть следующий символ. Подчеркнутый управляющий элемент
меню или диалога может быть доступен с помощью нажатия клавиши Alt и
подчеркнутой буквы.
    10. Внутри элементов меню текст \t генерирует символ табуляции для
выравнивания текста. Это часто используется для выравнивания эквивалентных
команд с клавиатуры.
    11. Горячие клавиши могут определяться в таблице акселераторов.
    12. Управляющие элементы, такие как статичный текст, которые никогда не
становятся доступными под управлением программы, обычно имеют идентификатор
-1.
    13. Местоположение диалогов и управляющих элементов определяются в
порядке Левая граница, Верх, Ширина, Высота.
    14. Элементы меню, диалоги и управляющие элементы диалогов, такие как
линейки прокрутки, редактируемые поля и кнопки, могут модифицироваться с
помощью констант Windows-стиля. Добавляют их с помощью оператора побитового
ИЛИ |. Например, управляющие элементы могут иметь стили WS_CHILD,
WS_TABSTOP и WS_BORDER с помощью задания WS_CHILD |, WS_TABSTOP | и
WS_BORDER |.
    15. В большинстве случаев легче использовать редактор ресурсов, а не
писать сложные определения ресурсов, используя файлы описания.
    16. Вы должны описать в вашем приложении загрузку и использование
ресурсов.

         Загрузка ресурсов в приложении

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

    Загрузка меню

    Меню окна это один из атрибутов его создания. Другими словами, это
характеристика окна, которая должна быть задана перед созданием
соответствующего элемента окна (функцией-компонентой Create). Поэтому, меню
может быть задано в конструкторе класса окна или иногда сразу после
конструирования. Ресурсы меню загружаются вызовом функции-компоненты
AssignMenu (порожденной TWindow) со строкой идентификатора меню, когда
конструируется новый объект окна. Например,

    SampleMainWindow::SampleMainWindow(PTWindowsObject AParent,
      LPSTR ATitle) : TWindow(AParent, ATitle)
    {
      AssignMenu(100);
    ...
    }

    AssignMenu загружает ресурс меню с идентификатором 100 в новый объект
окна. Ресурс может иметь символьное имя (строку), такую как "SampleMenu", а
не численный идентификатор. В этом случае предыдущий текст программы будет
выглядеть следующим образом:

    SampleMainWindow::SampleMainWindow(PTWindowsObject AParent,
      LPSTR ATitle) : TWindow(AParent, ATitle)
    {
      AssignMenu("SampleMenu");
    ...
    }

    Более подробную информацию о создании объектов окон можно найти в Главе
10 "Оконные объекты".
    Для выполнения пункта меню просто определите функцию-компоненту для
окна, которое владеет меню, используя специальное расширение заголовка
определения функции-компоненты с идентификатором CM_FIRST:

    virtual void HandleMenu101(RTMessage Msg) = [CM_FIRST + 101];

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

    Загрузка таблиц акселераторов

    Акселераторы это горячие ключи или средства оперативного доступа
клавиатуры, используемые для вызова команд приложения. Обычно акселераторы
определяются как замена пунктов меню. Например, клавиша удаления это
стандартный акселератор, который может использоваться в качестве
альтернативы выбора команды Удалить в меню Редактирование. Акселераторы
могут, однако, выполнять команды, не соответствующие пунктам меню.
    Ресурсы акселераторов сохраняются в таблице акселераторов. Для загрузки
таблицы акселераторов используйте функцию Windows LoadAccelerators, которая
возвращает дескриптор таблицы. В отличие от ресурса меню, который связан с
некоторым окном, ресурс акселератора относится ко всему приложению. Каждое
приложение может иметь одновременно только одну таблицу акселераторов.
Объекты приложений резервируют одну компоненту данных, HAccTable, для
сохранения дескриптора ресурса акселераторов. Обычно ресурс акселераторов
загружают в функции-компоненте объекта приложения InitInstance:

    void SampleApplication::InitInstance()
    {
      TApplication::InitInstance();
      HAccTable = LoadAccelerators(GetApplication()->HInstance,
                                   "SampleAccelerators");
    }

    Часто акселераторные клавиши определяются как средства оперативного
доступа для пунктов меню. Например, Shift-Ins обычно используется как
средство оперативного доступа для команды Вклейка. Выбор акселератора
генерирует сообщение, базирующееся на команде, идентичное сообщению,
генерируемому пунктом меню. Для связи функции-компоненты реакции на выбор
пункта меню с соответствующей клавишей акселератора убедитесь, что
определенное в ресурсе значение акселератора идентично идентификатору
пункта меню.

    Загрузка блоков диалога

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

    ADlg = new TSampleDialog(this, "AboutBox");

или

    ADlg = new TSampleDialog(this, 120);

    Более подробная информация о создании объектов диалога находится в
Главе 5 "Поддержка диалога".

    Загрузка курсоров и пиктограмм

    Каждый класс окон имеет специальные атрибуты, называемые атрибутами
регистрации. Среди этих атрибутов есть курсоры и пиктограммы. Для установки
этих атрибутов класса окон вы должны определить функцию-компоненту,
называющуюся GetWindowClass (также, как и функцию, называющуюся
GetClassName).
    Например, предположим, что вы создаете курсор для выбора элементов в
блоке списка. Курсор выглядит подобно указывающему пальцу и сохраняется в
качестве ресурса курсора с именем "Finger". Кроме того, предположим, что вы
создаете ресурс пиктограммы с именем "SampleIcon", которая выглядит как
улыбающееся лицо. Вы должны определить функцию-компоненту GetWindowClass
следующим образом:

    void SampleWindow::GetWindowClass(WNDCLASS& AWndClass)
    {
      TWindow::GetWindowClass(AWndClass);
      AWndClass.hCursor = LoadCursor(GetApplication()->HInstance,
                                     "Finger");
      AWndClass.hIcon = LoadIcon(GetApplication()->HInstance,
                                 "SampleIcon");
    }

    Однако, курсоры и пиктограммы имеют отличие, которое состоит в том, что
курсор задается для одного окна, а пиктограмма представляет целое
приложение. Поэтому, вы устанавливаете пиктограмму только в классе главного
окна. Далее приводятся два исключения из правила "одна пиктограмма на
приложение":

* Каждое дочернее окно MDI в приложении, использующем стандарт
мультидокументального интерфейса (MDI), имеет собственную пиктограмму.
* Каждое всплывающее окно в приложении имеет собственную пиктограмму.

    При использовании существующих курсоров или пиктограмм Windows вы
передаете 0 в качестве HInstance и используете значение IDC_, такое как
IDC_BEAM, для курсоров и значение IDI_, такое как IDI_HAND, для пиктограмм.
Например,

    void SampleWindow::GetWindowClass(WNDCLASS& AWndClass)
    {
      TWindow::GetWindowClass(AWndClass);
      AWndClass.hCursor = LoadCursor(NULL, IDC_IBEAM);
      AWndClass.hIcon = LoadIcon(NULL, IDI_HAND);
    }

    Более подробная информация об атрибутах регистрации окна находится в
Главе 10 "Оконные объекты".

    Загрузка ресурсов строк

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

    LoadString(GetApplication()->HInstance, StringID, TextItem, MaxChars);

* Параметр StringID это номер идентификатора строки, такой как 601, в
таблице строк. Вы можете использовать константу для этого номера.
* Параметр TextItem имеет тип LPSTR и указывает на буфер.
* Параметр MaxChars это максимальное число символов для передачи в буфер.
Максимальный размер ресурса строки равен 255 символам, так что передача в
буфер 256 символов гарантирует получение целой строки.

    LoadString возвращает число символов, копируемых в буфер, или ноль,
если ресурс не существует.
    Вы можете использовать ресурс строки для представления текста внутри
блока сообщений. Например, вы можете представить сообщение об ошибке.
Допустим, вы определяете строку "Program unavailable" в таблице строк и
определяете константу ID_NOPRGRM для идентификатора этой строки. Для
использования этого ресурса строки в блоке ошибок вы должны написать
следующую процедуру:

    virtual void TestDialog::RunErrorBox(int ErrorNumber) {
      char TextItem[255];

      LoadString(GetApplication()->HInstance, IDS_NOPRGRM, TextItem, 20);
      MessageBox(HWindow, TextItem, "Error", MB_OK | MB_ICONEXCLAMATION);
    }

    Этот пример загружает одну строку в блок ошибок. Для загрузки списка
строк в блок списка используйте LoadString для получения каждой строки, а
затем AddString для добавления ее в блок списка.
    Кроме этого, ресурсы строк можно использовать для элементов меню,
которые добавляются к меню в исходном тексте вашей программы. В этом случае
сначала получите ресурс строки с помощью функции LoadString. Затем
передайте строку в качестве параметра в функции Windows CreateMenu или
AppendMenu. Например,

    SampleWindow::SampleWindow(PTWindowsObject AParent,
      LPSTR ATitle) : TWindow(AParent, ATitle)
    {
      char TextItem[255];

      AssignMenu(100);
      LoadString(GetApplication()->HInstance, 301, TextItem, 20);
      AppendMenu(GetMenu(HWindow), MF_STRING | MF_ENABLED, 501, TextItem);
    }

    Загрузка побитовых отображений

    Функция LoadBitmap загружает ресурсы побитовых отображений. LoadBitmap
загружает побитовое отображение в память и возвращает дескриптор. Например,

    HMyBit = LoadBitmap(GetApplication()->HInstance, MAKEINTRESOURCE(501));

загружает ресурс побитового отображения с идентификатором 501 и сохраняет
дескриптор побитового отображения в переменной HMyBit. Когда побитовое
отображение загружено, оно остается в памяти, пока его явно не удалят. В
отличие от других ресурсов оно будет оставаться в памяти, даже если
пользователь уже закрыл приложение.
    Windows предоставляет набор предопределенных побитовых отображений,
которые используются как часть графического интерфейса Windows. Ваше
приложение может загружать эти побитовые отображения, такие как
OBM_DNARROW, OBM_CLOSE и OBM_ZOOM. Подобно предопределенным пиктограммам и
курсорам предопределенные побитовые отображения могут загружаться с помощью
подстановки NULL в GetApplication()->HInstance в вызове LoadBitmap:

    HMyBit = LoadBitmap(NULL, OBM_CLOSE);

    Когда побитовое отображение загружено, оно может использоваться в вашем
приложении различными способами:

* Для рисования картинки на дисплее. Например, вы можете загрузить
побитовое отображение в блок About приложения для рисования титула
приложения.
* Для создания кисти, которую вы можете использовать для заполнения области
дисплея или обеспечения фона окна. С помощью создания кисти, используя
побитовое отображение, вы можете заполнить область фона, например,
полосками.
* Для представления рисунков (а не текста) для пунктов меню или для
представления элементов в блоке списка. Например, вы можете представить
рисунок стрелки для пункта в меню, а не использовать текст меню "Стрелка".

    Более подробную информацию о использовании побитовой графики можно
найти в Главе 18 "Введение в GDI".
    Если побитовое отображение не используется, вы должны удалить его из
памяти. В противном случае, занимаемая им память будет недоступной для
других приложений. Если вы не удалили его после того, как приложение
закончило работу с ним, то нужно удалить его перед прекращением работы
приложения.
    Удаляйте побитовое отображение из памяти с помощью функции Windows
DeleteObject:

    if (DeleteObject(HMyBit)) ...  // успешно

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

    Использование побитового отображения для создания кисти

    Вы можете использовать побитовые отображения для создания кистей,
которые заполняют область дисплея. Область может быть заполнена сплошным
цветом или с помощью шаблона.
    Минимальный размер побитового отображения, используемого в качестве
кисти, равен 8 на 8 пикселей. Если вы используете большее побитовое
отображение, для кисти используется только левый верхний угол 8 на 8
пикселей.
    Допустим, вы хотите заполнить область полосками, как показано на Рис.
19.1. Для того, чтобы сделать это, вы можете использовать редактор ресурсов
для создания шаблона с двумя полосками, показанного на Рис. 19.2, который
может быть минимум 8 на 8 пикселей. Тогда в исходном тексте вашей программы
вы можете загрузить побитовое отображение и создать с помощью него кисть.
Для заполнения целой области, показанной на следующем рисунке, Windows
повторяет копирование кисти.

Рис. 19.1  Шаблон с полосками, заполняющий область дисплея

    Действительное побитовое отображение имеет размер только 8 на 8
пикселей, но кисть может использоваться для заполнения всего дисплея.

Рис. 19.2 Ресурс побитового отображения для создания кисти для шаблона,
показанного на Рис. 19.1

    Следующий текст устанавливает побитовый шаблон кисти:

    void SampleWindow:MakeBrush() {
      LOGBRUSH MyLogBrush;

      HMyBit = LoadBitmap(GetApplication()->HInstance,
                          MAKEINTRESOURCE(502));
      MyLogBrush.lbStyle = BS_PATTERN;
      MyLogBrush.lbHatch = HMyBit;
      TheBrush = CreateBrushIndirect(&MyLogBrush);
    }

    Для тестирования шаблона представьте его в прямоугольнике:

    void MyWindow:Paint(HDC PaintDC, PAINTSTRUCT& PaintInfo)
    {
      SelectObject(PaintDC, TheBrush);
      Rectangle(PaintDC, 20, 20, 200, 200);
    }

    После того, как вы использовали кисть, вы должны удалить побитовое
отображение и кисть:

    DeleteObject(HMyBit);
    DeleteObject(TheBrush);

    Представление побитовых отображений в меню

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

    SampleWindow::SampleWindow(PTWindowsObject AParent, LPSTR ATitle) :
      TWindow(AParent, ATitle)

      WORD HMyBit; {
      AssignMenu(100);
      HMyBit = LoadBitmap(GetApplication()->HInstance,
                          MakeIntResource(503));
      ModifyMenu(GetMenu(HWindow), 111, MF_BYCOMMAND | MF_BITMAP,
                 211, (LPSTR)HMyBit);
    }

    В описанном примере 111 является идентификатором команды изменяемого
пункта меню, а 211 является ее новым идентификатором. Однако, вы можете
использовать один и тот же идентификатор.

Рис. 19.3  Меню, использующее побитовое отображение в качестве одного из
своих пунктов


    ГЛАВА 20

         СТАНДАРТНЫЕ РУКОВОДЯЩИЕ ПРИНЦИПЫ РАЗРАБОТКИ ПРИЛОЖЕНИЙ

    Одно из основных соображений при разработке программного обеспечения
под Windows это удобство изучения и использования программных продуктов.
Пользователи Windows получают большую продуктивность главным образом
потому, что общие для различных программ операции выполняются одними и теми
же способами. Например, меню Редактирование имеет одни и те же пункты меню
и средства оперативного доступа клавиатуры в текстовых процессорах и
приложениях рисования. Другими словами, если вы знаете, как использовать
одно приложение Windows, вы знаете, как использовать и все остальные.
    Для формализации руководящих принципов общего оперативного доступа IBM
разработала стандарт интерфейсов с пользователем, называемый Общий
пользовательский доступ (CUA). Предназначавшиеся для приложений Менеджера
презентаций OS/2 руководящие принципы CUA подходят и для приложений Windows
и считаются производственным стандартом. Этот параграф основывается на
руководящих принципах CUA, опубликованных IBM.

         Принципы разработки

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

    Предоставление реакции, ожидаемой пользователем

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

* Используйте метафоры. Если вы моделируете поведение приложения на основе
программного продукта, с которым пользователь хорошо знаком, как, например,
существующая система работы с документами, пользователь будет лучше
понимать основополагающую концептуальную модель приложения.
* Будьте последовательны. Придерживайтесь последовательности при разработке
внешнего вида и использования вашего приложения. Используйте общие команды
(выборы в меню, щелчки мышью на пиктограммах и т. д.) для общих операций.
Делайте экраны для похожих задач подобными.
* Избегайте режимов. Режим это состояние приложения, в котором пользователь
имеет ограниченный доступ к некоторым средствам. Им часто злоупотребляют
для форсирования активности пользователя в одном определенном направлении.
Хорошо использовать режимы для коротких сообщений об ошибках или во время
выбора средств. Например, когда курсор является карандашом, пользователь
может рисовать, но не может осуществлять буксировку. Используйте визуальное
уведомление о наличии режима с помощью изменения курсора или полутоновых
пунктов меню.

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

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

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

         Стандартные внешний вид и режим работы

    Большинство стандартов Windows для внешнего вида и режима работы
приложений очевидны при рассмотрении Менеджера программ и предоставленных
примеров программ. Например, каждое приложение представляется в главном
окне. Щелчки и двойные щелчки мыши обычно имеют один и тот же результат в
разных приложениях.

    Общее представление

* Используйте процесс объект-действие. Обычная последовательность событий
при работе приложения такова: пользователь выбирает элемент или объект, а
затем выполняет над ним действие. Например, в приложении обработки текста
пользователь может выбрать текст, а затем изменить его шрифт или размер. В
программе рисования пользователь может выбрать прямоугольник и переместить
его в новое местоположение на экране. Когда имеется несколько способов
выбрать объект и еще больше способов выбрать действие, пытайтесь
придерживаться этой общей модели.
* Разумно выбирайте ваши окна. Приложение Windows может быть представлено в
одном или нескольких окнах. Для разработки оптимального использования
вашего приложения вы должны быть знакомы с различными типами окон и
способами их использования. Главное окно отождествляет приложение и служит
его основой. Главное окно фактически представляет приложение, минимизация
его в пиктограмму приостанавливает действие приложения. Оно может также
перейти в полноэкранное или максимизированное состояние. Другие окна, такие
как всплывающие окна и блоки диалога, должны порождаться главным окном как
результат некоторых действий главного окна. Всплывающие окна и блоки
диалога могут представлять отдельные модули приложения, но не должны
минимизироваться.

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

* Показывайте выбор объекта. Когда объект выбирается с помощью щелчка
мышью, полезно выделять его визуально, например, выделять углы
прямоугольника или изменять цвет или толщину. Пометка выбора не должна
постоянно воздействовать на объект. После того, как пользователь произвел
действия над объектом, сохраняйте выбор. Это позволит пользователю
предпринять дополнительные действия. Убирайте выбор объекта, когда
пользователь выбирает что-нибудь еще.
* Не ограничивайте указатель мыши. Указатель мыши должен свободно
перемещаться из одного окна или приложения в другое. Не ограничивайте и не
навязывайте его перемещение независимо от мыши. И то, и другое нарушает
физическую связь между указателем мыши и мышью.
* Используйте курсор выбора клавиатуры. Курсор выбора клавиатуры отмечает
место, на которое будет воздействовать ввод с клавиатуры. Например,
мерцающий символ ^ представляет курсор выбора клавиатуры в приложениях
обработки текста. Курсор выбора клавиатуры существует независимо от
указателя мыши. Однако, они оба соединяются, когда осуществляется щелчок
мышью.
* Отмечайте фокус ввода. Фокус ввода это точка на экране, которая в данный
момент принимает ввод от клавиатуры или мыши. Например, если пользователь
выбрал выпадающее меню, это меню получает фокус ввода. Всегда выделяйте
визуально объекты, имеющие фокус ввода. Например, выбранный пункт меню
обычно высвечивается в негативном цвете, а управляющие элементы в диалоге
окантованы линией из точек. Часто, когда пользователь буксирует мышью
выбранную группу элементов, область буксировки окантована движущейся линией
из точек, называемой шатром.
* Разработка клавиатурного интерфейса. Используйте клавиши стрелок,
табуляции и клавиатурные акселераторы для предоставления альтернативы
пользователям, не имеющим мыши или не желающим использовать ее.
* Не нарушайте соглашений об использовании щелчков мышью. Windows
использует определенные соглашения о щелчках мышью, но не настаивает на
них. В общем, используйте левую кнопку мыши для выбора объектов и
манипуляций ими, а правую кнопку для других задач, например, для изменения
режимов или представления всплывающего списка вариантов выбора. Для левой
кнопки мыши придерживайтесь следующих соглашений:

+ Щелчок выбирает объект.
+ Двойной щелчок выбирает объект и вызывает некоторые стандартные или
подразумевающиеся действия.
+ Щелчок и буксировка воздействует на размер и позицию объекта или выбирает
и выделяет текст.
+ Ctrl и щелчок добавляет выбранный объект к набору ранее выбранных
объектов.
+ Shift и щелчок расширяет границы ранее выбранного набора объектов между
первым объектом набора (его якорем) и объектом, на котором осуществлялся
щелчок (например, см. Менеджер файлов Windows).

    Меню

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

* Используйте уникальную мнемонику для клавиатурного доступа для каждого
пункта меню верхнего уровня и выпадающего меню.
* Используйте многоточие (...), следующее за пунктом меню, выбор которого
приводит к появлению блока диалога.
* Используйте затенение неактивного пункта меню.
    Большинство приложений, графические, коммерческие, образовательные,
научные или инженерные, выполняют общий набор операций для манипуляций с
файлами, редактирования или доступа к системе помощи. Исходя из этих
соображений, CUA определяет форматы для меню Файл, Редактирование и
Подсказка. Кроме того, он предлагает руководящие принципы для меню Просмотр
и Опции. CUA требует, чтобы вы располагали пункты меню верхнего уровня в
следующем порядке, где Подсказка всегда последний пункт, независимо от
того, как много имеется других меню верхнего уровня.

Рис. 20.1  Стандартная линейка меню CUA

    Следующий рисунок изображает пункты меню, рекомендованные CUA. Отметим
рекомендованные клавиатурные акселераторы, которые возникают после
некоторых пунктов меню.

Рис. 20.2  Пункты меню Файл, рекомендованные CUA

Рис. 20.3  Пункты меню Редактирование, рекомендованные CUA

Рис. 20.4  Пункты меню Просмотр, рекомендованные CUA

Рис. 20.5  Пункты меню Опции, рекомендованные CUA

Рис. 20.6  Пункты меню Подсказка, рекомендованные CUA

         Блоки диалога

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

    Другие соглашения разработки

* Выбирайте подходящий размер главного окна. Делайте ваше главное окно
достаточно большим, чтобы представлять требуемую информацию и обеспечивать
достаточную пользовательскую область.
* Внимательно относитесь к перерисовке главного окна. У вас есть два
способа перерисовать окно после изменения его размера. Вы можете отрисовать
содержание в исходных пропорциях, отсекая некоторую часть, если окно
слишком мало. Это лучший способ для приложений, работающих в режиме полного
соответствия (WYSIWYG). Или вы можете переформатировать содержание так, что
оно по прежнему будет находится внутри границ окна. Этот вариант более
подходит для игр или утилит.
* Умеренно используйте цвета. Цвет должен подкреплять установленную
коммуникационную связь с пользователем. Он должен использоваться
последовательно, и пользователь должен иметь возможность модифицировать
любой используемый цвет.

         Написание надежных программ

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

    Элементарные операции

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

    Пул безопасности

    ObjectWindows устанавливает фиксированный объем памяти, называемый пул
безопасности. Стандартный размер пула безопасности SAFEPOOL.H определяется
константой DEFAULT_SAFETY_POOL_SIZE в SAFEPOOL.H. Если размещаемая память в
динамически распределяемой области попадает в пул безопасности,
функция-компонента объекта приложения LowMemory возвращает значение TRUE.
Это указывает, что дальнейшие размещения небезопасны и могут привести к
сбою.
    Для того, чтобы пул безопасности был эффективным, он должен быть
больше, чем наибольшее элементарное размещение. Другими словами, он должен
быть настолько большим, чтобы обеспечить то, что все размещения между
проверок с помощью LowMemory будут успешными; стандартный размер должен
удовлетворять большинство приложений.
    После того, как ваш объект приложения создаст свое главное окно (и его
дочерние окна), он проверяет состояние памяти. Однако, если ваша программа
создает окна или диалоги как динамические объекты после представления
главного окна, вы должны явно вызывать механизм, проверяющий состояние
памяти. Эти ситуации включают в себя создание блоков диалога или
всплывающих окон в качестве реакции на выбор в меню. Окна MDI, однако,
всегда проверяют состояние памяти при создании дочерних окон.
    Не вызывайте функцию-компоненту Create прямо для ваших объектов блоков
диалога, так как Create создает окно без проверки состояния памяти:

Неправильно!

    void TMyWindow::CMHelp(RTMessage Msg)
    {
      PTHelpWindow HelpWindow;

      HelpWnd = new THelpWindow(this, "Help");
      HelpWnd -> Create();
    }

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

Правильно!

    void TMyWindow::CMHelp(RTMessage Msg)
    {
      PTHelpWindow HelpWindow;

      HelpWnd = new THelpWindow(this, "Help");
      if (GetApplication()->MakeWindow(HelpWindow) )
      // окно успешно создано
    ...
    }

    MakeWindow возвращает указатель на объект окна или NULL, если при
создании произошел сбой.
    Для проверки состояния памяти при выполнении модального диалога
вызывайте GetApplication()->ExecDialog, как показано в следующем примере:

Правильно!

    void TMyWindow::CMRunDialog(RTMessage Msg)
    {
      PTTestDialog TestDialog;
      int RetValue;

      TestDialog = new TTestDialog(this, "Test Dialog");
      RetValue = GetApplication()->ExecDialog(TestDialog);
      if (RetValue == IDCANCEL )
        // сбой при создании диалога или прекращение пользователем
    ...
    }

    В случае успеха ExecDialog возвращает значение возврата диалога, обычно
ноль или положительное целое. Если диалог не может безопасно выполняться,
ExecDialog возвращает константу ошибки (всегда отрицательное целое
значение).
    MakeWindow и ExecDialog предупреждают объект приложения о состоянии
недостаточной памяти с помощью вызова функции-компоненты объекта приложения
Error, передавая в качестве аргумента константу EM_OUTOFMEMORY.
Функция-компонента Error, определенная классом TModule, представляет блок
сообщений, предупреждающий пользователя об ошибке. ObjectWindows
переопределяет глобальный оператор delete для проверки пула безопасности
после каждого удаления. Если пул безопасности исчерпан, оператор delete
пытается переразместить его, предоставляя автоматическое восстановление
пула безопасности всякий раз, когда такое восстановление вероятно будет
успешным. Более подробную информацию о пуле безопасности можно найти в
Главе 3 "Различные компоненты ObjectWindows" Справочного руководства
(Reference Guide) ObjectWindows for C++.

    Другие ошибки при создании окон

    Кроме состояния недостаточной памяти имеются другие состояния, которые
могут мешать создания и представлению должным образом окна или блока
диалога. Например, Windows может не выполнить создания элемента интерфейса
в связи с недостаточной памятью Windows или в связи с неправильными
атрибутами создания.
    Ошибки создания окон, отличные от состояния недостаточной памяти,
автоматически сообщаются функциями-компонентами ObjectWindows, создающими
объекты интерфейса. TWindowsObject определяет компоненту данных Status типа
int, которая содержит ненулевое значение после возникновения ошибки. Далее
приводятся некоторые из констант ошибок, определенных ObjectWindows:

Таблица 20.1  Значения ошибок создания окон

Ошибка                 Значение
---------------------------------------------------------------------------
EM_OUTOFMEMORY         Размещение памяти попало в буфер безопасности.
EM_INVALIDWINDOW       Элемент окна не может быть создан.
EM_INVALIDCLIENT       Для объекта окна MDI невозможно создать
                       пользовательское окно.
EM_INVALIDCHILD        Одно или несколько дочерних окон объекта
                       сгенерировали ошибку.
EM_INVALIDMAINWINDOW   Главное окно не может быть создано.

    Полный список констант ошибок и их значений можно найти в параграфе
"Константы EM_XXXX" в Главе 3 "Различные компоненты ObjectWindows"
Справочного руководства (Reference Guide) ObjectWindows for C++.

    Для проверки ошибок в написанных вами конструкторов окон и
функциях-компонентах создания установите Status равным одной из этих
констант или одной из своих собственных констант. Например, если окно
должно открывать файл, но произошел сбой, оно может сообщить об ошибке с
помощью константы, такой как EM_FILEOPENERROR. Если TWindow::Create
возвращает FALSE, или TDialog::Execute устанавливает Status в ненулевое
значение, когда вызывается функцией MakeWindow или ExecDialog,
соответственно, вызывающая функция-компонента будет автоматически сообщать
об ошибке функции-компоненте Error вашего приложения, которая, по
умолчанию, просто вызывает функцию-компоненту Error класса вашего
приложения. Вы можете переопределить функцию-компоненту Error класса вашего
приложения или окна для соответствующей обработки каждой ошибки.

    TEditorWindow::TEditorWindow(PTWindowsObject AParent, LPSTR ATitle,
      LPSTR AFileName) : TWindow(AParent, ATitle)
    {
      if ((Status == 0) && !OpenTheFile(AFileName))
        Status = EM_FILEOPENERROR;
    }

    Проверка потребителей памяти

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









helloworld.ru © 2001-2016
Все права защищены
Rambler's Top100 TopList Rambler's Top100