Основы программирования с помощью MFC

       

Основы программирования с помощью библиотеки MFC


Картузов А. В., Гончаров В. И.

Основы программирования с помощью библиотеки

Microsoft Foundation Classes

ОГЛАВЛЕНИЕ

ВВЕДЕНИЕ

Что необходимо знать для изучения MFC

Необходимые инструменты

Установка примеров

ОСНОВЫ MFC

Иерархия классов MFC

Информация о типе времени выполнения



Диагностика

Сериализация

Основные классы

Функции-члены в MFC

Глобальные функции в MFC

Файл AFXWIN.H

Каркас MFC-программы

Исходные тексты примера

Подробнее о создании масштабируемых окон

ОБРАБОТКА СООБЩЕНИЙ

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

Включение макрокоманд в карту сообщений

Включение обработчиков сообщений в описание класса

Пример программы с обработкой сообщений

Контекст устройства

Сообщение WM_PAINT

Генерация сообщения WM_PAINT

Пример программы

Сообщения WM_TIMER и WM_DESTROY

Сообщение WM_TIMER

Сообщение WM_DESTROY

Пример программы

РЕСУРСЫ. МЕНЮ И АКСЕЛЕРАТОРЫ

Понятие ресурсов

Меню

Включение меню в окно приложения

Сообщение WM_COMMAND

Акселераторы

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

Пример программы, использующей меню, акселераторы и окна сообщений

ДИАЛОГИ. ЗНАКОМСТВО С ЭЛЕМЕНТАМИ УПРАВЛЕНИЯ

Взаимодействие между диалогом и пользователем

Классы MFC для элементов управления

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

Диалоги как ресурсы

Класс CDialog

Обработка сообщений от диалогов

Вызов диалога

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

Пример программы с диалоговым окном

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

Списки

Основы работы со списками

Прием идентификационных кодов списка

Передача сообщений списку

Получение указателя на список

Инициализация списка

Пример программы

Поле ввода

Пример программы с полем ввода

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

Пример программы с немодальным диалогом

ДОПОЛНИТЕЛЬНЫЕ ЭЛЕМЕНТЫ УПРАВЛЕНИЯ

Контрольные переключатели

Сообщения контрольного переключателя

Установка и чтение состояния контрольного переключателя

Инициализация контрольных переключателей

Пример программы

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

Радиокнопки

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


Полосы прокрутки

Создание стандартных полос прокрутки

Независимые полосы прокрутки в диалогах

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

Управление полосой прокрутки

Пример программы с пропорциональными полосами прокрутки



ИКОНКИ И КУРСОРЫ

Создание иконки и курсора

Загрузка иконки и курсора из ресурсов

Изменение иконки и курсора окна

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

Пример программы

Стандартные иконки и курсоры



БИТОВЫЕ ОБРАЗЫ

Создание битовых образов

Вывод битового образа на экран

Получение системных метрик

Пример программы



ВЫВОД ТЕКСТА И ШРИФТЫ

Небольшое введение

Координаты при выводе текста

Задание цвета текста и фона

Задание режима отображения фона

Получение метрик текста

Изменение шрифтов

Инициализация объекта шрифта: выбор шрифта

Пример программы



ВВЕДЕНИЕ В ГРАФИЧЕСКИЕ ФУНКЦИИ. ОРГАНИЗАЦИЯ ВЫВОДА В ВИРТУАЛЬНОЕ ОКНО

Система графических координат

Перья

Кисти

Отображение точки

Рисование прямоугольников

Рисование эллипсов

Организация виртуального окна

Дополнительные функции

Создание виртуального окна

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

Пример программы, демонстрирующей некоторые графические функции и использование виртуального окна



ЗАКЛЮЧЕНИЕ

РЕКОМЕНДУЕМАЯ ЛИТЕРАТУРА

Введение



На сегодняшний день, Windows является безусловно лидирующей операционной системой на рынке персональных компьютеров. Поэтому успех современного программиста напрямую зависит от его умения разрабатывать качественные и эффективные приложения Windows. Приложения постоянно усложняются и требуют все большего времени для их создания. Для облегчения программирования Windows-приложений фирмой Microsoft была разработана библиотека MFC (Microsoft Foundation Classes - Базовые Классы Microsoft), которая впервые была представлена на рынке в 1992 г вместе с компилятором Microsoft C/C++ 7.0. Сейчас она представляет собой мощный набор классов C++, которые позволяют программировать приложения Windows 95,98/NT на достаточно высоком уровне абстракции, и вместе с тем открывают для опытных программистов легкий доступ к функциям более низкого уровня, что позволяет писать эффективные приложения и полностью использовать все возможности операционной системы.



MFC является альтернативой системам визуального программирования, таким как Delphi или Visual Basic, предназначенной для опытных программистов. На сегодняшний день подавляющее большинство программ разрабатывается при помощи Microsoft Visual С++ и MFC. MFC - это стандарт программирования под Windows и "интернациональный язык общения". Такая ситуация объясняется многими причинами. В частности, только MFC позволяет создавать наиболее эффективные и устойчивые приложения, которые будут корректно вести себя не только в системе разработчика, но и в системах реальных пользователей. Также очень важно, что MFC поддерживает все современные технологии, реализованные в Windows, и при дополнении Windows почти сразу же дополняется и MFC.

MFC - это инструмент для программирования сложных приложений, от которых требуется высокая эффективность и надежность. MFC поощряет использование объектно-ориентированного программирования, что дает ощутимые преимущества при решении сложных (не с точки зрения только интерфейса пользователя) задач, по сравнению с компонентно-ориентированным подходом, применяемым в системах RAD (быстрой разработки приложений). Разрабатывая приложение в системе RAD, программист часто вообще не использует ООП, по крайней мере в явном виде, до тех пор, пока не соберется разработать собственный компонент. Это негативно сказывается на возможности последующего расширения возможностей. Тем не менее, не стоит воспринимать сказанное как критику систем RAD. Есть много классов приложений (например, базы данных), которые разумнее всего разрабатывать именно при помощи систем RAD, что и делают даже опытные Windows-программисты.



Что необходимо знать для изучения MFC



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


Если Вы не так свободно ориентируетесь в С++, то настоятельно рекомендуется перед изучением MFC укрепить свои знания по С++, например, с помощью книги Стенли Б. Липпмана "С++ для начинающих", или одной из множества других книг, подобных этой.

Кроме того, необходимо иметь опыт работы с одной из современных версий Windows, а также представлять себе ее внутреннее устройство и принципы работы. Также необходим хотя бы минимальный опыт работы со средой Microsoft Visual C++ - данные методические указания не являются руководством пользователя по этой среде (если Вы хорошо знаете С++, то вероятно, Вы уже пользовались этой средой для создания консольных приложений). Предполагается, что Вы по крайней мере умеете создавать проекты, добавлять к ним файлы, менять настройки и собирать проекты.

Однако, библиотека MFC построена так, что она повторяет структуру подсистем и объектов Windows API. Это сделано ради эффективности. Поэтому, если Вы уже программировали на С/С++ с использованием только "чистого" Windows API, то это существенно облегчит процесс изучения MFC. Опыт работы с системами RAD тоже будет полезен.



Необходимые инструменты



В первую очередь, необходима Windows 95, Windows NT 4.0 или их более поздние версии. Также необходим компилятор Microsoft Visual C++ 4.0 или более поздняя версия. Необходимо отметить, что компилятор Visual C++ очень медленный и требует много памяти. Поэтому минимальные рекомендуемые конфигурации компьютеров для Visual C++ 4.0 следующие: для Windows 95 - 486DX4/100, 32 МБ RAM, для Windows NT 4.0 - Pentium 100, 64 МБ RAM.



Установка примеров



К методическим указаниям прилагается дискета, содержащая текст всех примеров, EXE-файлы и файлы проектов для Visual C++ 4.0. На дискете находится инсталляционная программа setup.exe, которая установит примеры в каталог C:\LEARN.MFC, а также скопирует в системный каталог Windows файлы DLL для динамически компонуемой версии MFC и библиотеки времени выполнения для Visual C++ (DLL-версии использовались для того, чтобы не включать довольно большой код MFC в каждый выполняемый файл примеров).


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



Основы MFC

Иерархия классов MFC



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

Информация о типе времени выполнения

Если указатель или ссылка ссылается на объект, производный от класса CObject, то в этом случае предусмотрен механизм определения реального типа объекта с помощью макроса RUNTIME_CLASS(). Хотя в С++ имеется механизм RTTI, механизм, реализованный в MFC, намного более эффективен по производительности.

Диагностика

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

Сериализация

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

Основные классы

Некоторые классы порождаются непосредственно от CObject. Наиболее широко используемыми среди них являются CCmdTarget, CFile, CDC, CGDIObject

и CMenu. Класс ССmdTarget предназначен для обработки сообщений. Класс СFile предназначен для работы с файлами. Класс CDC обеспечивает поддержку контекстов устройств. Об контекстах устройств мы будем говорить несколько позднее. В этот класс включены практически все функции графики GDI. CGDIObject является базовым классом для различных DGI-объектов, таких как перья, кисти, шрифты и другие.


Класс СMenu предназначен для манипуляций с меню.

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



От класса CCmdTarget, через класс CWinThread, порождается, наверное, единственный из наиболее важных классов, обращение к которому в MFC-программах происходит напрямую: CWinApp. Это один из фундаментальных классов, поскольку предназначен для создания самого приложения. В каждой программе имеется один и только один объект этого класса. Как только он будет создан, приложение начнет выполняться.



Функции-члены в MFC



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



Глобальные функции в MFC



В библиотеке есть ряд глобальных функций. Все они начинаются с префикса Afx. (Когда MFC только разрабатывалась, то проект назывался AFX, Application Framework. После ряда существенных изменений AFX была переработана в MFC, но прежнее название сохранилось во многих идентификаторах библиотеки и в названиях файлов.) Например, очень часто используется функция AfxMessageBox(), отображающая заранее определенное окно сообщения. Но есть и член-функция MessageBox(). Таким образом, часто глобальные функции перекрываются функциями-членами.



Файл AFXWIN.H



Все MFC-программы включают заголовочный файл AFXWIN.H. В нем, а также в различных вспомогательных файлах, содержатся описания классов, структур, переменных и других объектов MFC. Он автоматически подключает большинство заголовочных файлов, относящихся к MFC, в том числе и WINDOWS.H, в котором определены все функции Windows API и другие объекты, которые используются при традиционном программировании на С и "чистом" API.





Каркас MFC-программы



Теперь мы создадим с помощью MFC

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

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

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

Ниже приведен исходный код программы. Мы всегда будем помещать декларации классов и их реализацию в отдельные файлы. Это соответствует принятой практике программирования на С++.

Исходные тексты примера



simpwin.hpp



#include <afxwin.h>

// Класс основного окна приложения

class CMainWin: public CFrameWnd {

public:

CMainWin();

// Декларирование карты сообщений

DECLARE_MESSAGE_MAP()

};

// Класс приложения. Должен существовать только один экземпляр этого класса.

// Член-функция InitInstance() вызывается при запуске приложения.

class CApp: public CWinApp {

public:

BOOL InitInstance();

};



simpwin.cpp



#include <afxwin.h>

#include <string.h>

#include "SIMPWIN.HPP"

// Создание одного и только одного экземпляра приложения

CApp App;

//

// Реализация

//

BOOL CApp::InitInstance()

{

// Создание главного окна приложения и его отображение.



// Член CApp::m_pMainWnd - это указатель на объект главного окна.

m_pMainWnd = new CMainWin;

m_pMainWnd->ShowWindow(SW_RESTORE);

m_pMainWnd->UpdateWindow();

// Сигнализируем MFC об успешной инициализации приложения.

return TRUE;

}

CMainWin::CMainWin()

{

// Создание окна с заголовком. Используется встроенный в MFC

// класс окна, поэтому первый параметр 0.

this->Create(0, "Простейшее приложение на MFC");

}

// Реализация карты сообщений

BEGIN_MESSAGE_MAP(CMainWin /*класс окна*/, CFrameWnd /*класс-предок*/)

END_MESSAGE_MAP()

Рис. 2. Окно, созданное простейшим приложением на MFC

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

Для создания стандартного окна в приложении должен наследоваться класс от CFrameWnd. В данном примере он называется CMainWin. Он содержит конструктор и макрос DECLARE_MESSAGE_MAP(). Макрос на самом деле разворачивается в декларацию карты сообщений, которая определяет, какая член-функция класса должна вызываться в ответ на сообщение Windows. Этот макрос применяется для любого окна, в котором обрабатываются сообщения. Он должен быть последним в декларации класса.

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

Класс CApp

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

virtual BOOL CWinApp::InitInstance();

Это виртуальная функция, которая вызывается каждый раз при запуске программы. В ней должны производиться все действия, связанные с инициализацией приложения. Функция должна возвращать TRUE при успешном завершении и FALSE в противном случае. В нашем случае, в функции сначала создается объект класса CMainWin, и указатель на него запоминается в переменной m_pMainWnd.


Эта переменная является членом класса CWinThread. Она имеет тип CWnd* и используется почти во всех MFC-программах, потому что содержит указатель на главное окно. В последующих двух строчках через нее вызываются функции-члены окна. Когда окно создано, вызывается функция c прототипом:

BOOL CWnd::ShowWindow(int How);

Параметр определяет, каким образом окно будет показано на экране. Наиболее распространенные значения следующие:



Константа
Действие
SW_HIDE Окно становится невидимым
SW_MAXIMIZE Окно максимизируется
SW_MINIMIZE Окно минимизируетс
SW_SHOW Окно отображается, если было невидимо
SW_RESTORE Окно приводится к нормальному размеру
При первом вызове в качестве первого параметра функции можно подставить член m_nCmdShow, в котором хранится значение кода отображения окна, переданное в программу при запуске (например, в ярлыке Windows 95 пользователь может определить, как показывать окно при запуске приложения).

Функция UpdateWindow() посылает окну сообщение о том, что его содержимое нужно перерисовать. В дальнейшем мы рассмотрим этот процесс подробнее.

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

BEGIN_MESSAGE_MAP(CMainWin /*класс окна*/, CFrameWnd /*класс-предок*/)

END_MESSAGE_MAP()

Первый макрос всегда имеет два параметра, первый - класс окна, второй - класс, от которого порожден класс окна. В данном примере карта сообщений пустая, то есть все сообщения обрабатывает MFC.

Подробнее о создании масштабируемых окон

В примере была использована функция Create(), которая на самом деле имеет много параметров. Ее прототип таков:

BOOL CFrameWnd::Create(LPCSTR ClassName, // Имя Windows-класса окна

LPCSTR Title, // Заголовок

DWORD Style = WS_OVERLAPPEDWINDOW, // Стиль

const RECT &XYSize = rectDefault, // Область

CWnd *Parent = 0, // Окно-предок

LPCSTR MenuName = 0, // Имя ресурса меню

DWORD ExStyle = 0, // Расширенные стили

CCreateContext *Context = 0

// Доп. данные

);

Первый параметр, ClassName, определяет имя класса окна для оконной подсистемы Windows.


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



Константа
Элемент окна
WS_OVERLAPPED Стандартное окно с рамкой
WS_MAXIMIZEBOX Кнопка максимизации
WS_MINIMIZEBOX Кнопка минимизации
WS_SYSMENU Системное меню
WS_HSCROLL Горизонтальная полоса прокрутки
WS_VSCROLL Вертикальная полоса прокрутки
В дальнейшем мы не будем так подробно расписывать здесь все возможные значения констант, потому что всегда можно обратиться к справке Visual C++ и получить подробную информацию.

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

Все остальные параметры, которые мы будем использовать, будут рассмотрены в соответствующих разделах.



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



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



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



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



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

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

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

3.  В программу должна быть включена реализация функции-обработчика.

Включение макрокоманд в карту сообщений

Чтобы программа могла ответить на сообщение, в карту сообщений должна быть включена соответствующая макрокоманда. Названия макрокоманд соответствуют именам стандартных сообщений Windows, но дополнительно имеют префикс ON_ и заканчиваются парой круглых скобок. Из этого правила есть исключение: сообщению WM_COMMAND соответствует макрокоманда ON_COMMAND(). Причина в том, что это сообщение обрабатывается особым образом.

Чтобы включить макрокоманду в очередь сообщений, нужно просто поместить ее между командами BEGIN_MESSAGE_MAP() и END_MESSAGE_MAP(). Например, если необходимо обработать в программе сообщение WM_CHAR, то очередь должна выглядеть так:

BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd)

ON_WM_CHAR()

END_MESSAGE_MAP()

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

Включение обработчиков сообщений в описание класса

Каждое сообщение, явно обрабатываемое в программе, должно быть связано с одним из обработчиков. Обработчик - это член-функция класса, принимающего сообщения. Прототипы для обработчиков всех сообщений заранее заданы в MFC. Как правило, имя обработчика состоит из имени сообщения и префикса On. Например, обработчик сообщения WM_CHAR называется OnChar(), а для WM_LBUTTONDOWN - OnLButtonDown(). Последнее сообщение генерируется при нажатии левой кнопки мыши.

Например, объявим класс с обработчиком сообщения WM_PAINT. Это сообщение посылается окну, когда оно должно перерисовать свою клиентскую область.

Class CMainWin: public CFrameWnd {



public:

CMainWin();

afx_msg void OnPaint();

DECLARE_MESSAGE_MAP()

}

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

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



Пример программы с обработкой сообщений



Рассмотрим программу, которая реагирует на нажатие левой и правой кнопок мыши в клиентской области окна, а также на нажатие клавиш. При нажатии левой кнопки в клиентскую область окна, начиная с текущих координат курсора мыши, выводится строка "Нажата левая кнопка", а при нажатии правой кнопки - "Нажата правая кнопка".

При нажатии на левую и правую кнопки соответственно генерируются сообщения WM_LBUTTONDOWN

и WM_RBUTTONDOWN, и им соответствуют обработчики с прототипами:

afx_msg void OnLButtonDown(UINT Flags, CPoint Loc);

afx_msg void OnRButtonDown(UINT Flags, CPoint Loc);

Первый параметр указывает на то, была ли при генерации сообщения нажата какая-нибудь клавиша или кнопка мыши. Этот параметр нас не будет пока интересовать. Второй параметр определяет координаты курсора мыши во момент нажатия кнопки. Класс CPoint порождается от структуры POINT, определенной так:

typedef struct tagPOINT {

LONG x;

LONG y;

} POINT;

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

Обработчик сообщения WM_CHAR имеет прототип:

afx_msg void OnChar(UINT Char, UINT Count, UINT Flags);

Нас здесь будет интересовать только первый параметр. Он представляет собой ASCII-код символа, соответствующего нажатой клавише. При нажатии несимвольных клавиш сообщение WM_CHAR не посылается.

Контекст устройства

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


Например, в Windows нет совершенно никакой возможности выводить точки прямо на экран, как это делалось в DOS. Контекст устройства - это достаточно условное название (даже для английского языка, device context), не отражающее сути понятия. На самом деле, это структура данных, обеспечивающая связь графических функций с драйвером конкретного устройства. Эта структура определяет состояние драйвера, и способ вывода графики. В MFC есть классы, инкапсулирующие контексты устройств, поэтому нам не придется работать с ними напрямую.

Например, в MFC можно получить контекст клиентской области окна, создав экземпляр класса CClientDС. Конструктор этого класса принимает один параметр - указатель на объект окна, обычно подставляется this. После создания этого объекта можно выводить графику в окно, используя функции-члены класса.

Сообщение WM_PAINT

Windows устроена таким образом, что за обновление содержимого окна отвечает программа. Например, если часть окна была перекрыта другим окном, а затем вновь открыта, или минимизированное окно было восстановлено, то окну посылается сообщение WM_PAINT. В ответ на него окно должно обновить свою клиентскую область.

Прототип обработчика WM_PAINT следующий:

afx_msg void OnPaint();

Макрокоманда называется ON_WM_PAINT(). Рассмотрим простой обработчик, который выводит строку "Привет" в клиентскую область по координатам x = 10, y = 20:

afx_msg void CMainWin::OnPaint()

{

CPaintDC paintDC(this);

paintDC.TextOut(10, 20, CString("Привет"));

}

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

Функция TextOut() предназначена для вывода текста в контекст устройства (в данном случае - в окно). При ее использовании по умолчанию первые два параметра определяют координаты верхнего левого угла текстовой строки. По умолчанию координаты представляют собой реальные пиксели, ось x направлена слева направо, ось y - сверху вниз.


Эта функция перегруженная, наиболее удобный для нас вариант - когда третий параметр имеет тип CString. Этот класс входит в MFC и является очень удобной заменой для строк, завершаемых нулем. Он имеет много полезных функций - аналогов стандартных строковых функций, и поддерживает преобразования типов, в частности, из char* в CString (поэтому в примере можно было обойтись без явного создания объекта этого класса). Вы можете легко самостоятельно изучить этот класс и многие другие полезные классы общего назначения из состава MFC, пользуясь справочной системой Visual C++.

Большинство реальных окон (за исключением диалогов, которые мы рассмотрим позднее) должны обрабатывать сообщение WM_PAINT. Более того, если Вы хотите написать корректную программу, то весь вывод в окно должен осуществляться только в обработчике WM_PAINT, и никак иначе. Например, крайне нежелательно получать контекст устройства в обработчике сообщения мыши и пытаться там что-то выводить. Это будет работать в большинстве случаев, но далеко не всегда. Дело в том, что Windows может предоставить одновременно всем программам лишь очень небольшое число контекстов устройств (Windows 95 - всего пять). Поэтому при запросе контекста не из обработчика WM_PAINT создание класса контекста может потерпеть провал, если другие приложения уже заняли все контексты. Тогда выводить информацию будет вообще невозможно. Это может вылиться в непредсказуемое поведение программы. В случае же получения контекста из обработчика WM_PAINT, обязательно с помощью класса CPaintDC, Windows гарантирует наличие свободного контекста. На самом деле, Windows не пошлет программе это сообщение до тех пор, пока в системе не будет свободного контекста. К сожалению, эта проблема не так широко известна, и во многих книгах сплошь и рядом правило вывода информации только по сообщению WM_PAINT не соблюдается. Это вызвано тем, что описанная проблема не проявляется до тех пор, пока в системе не будет запущено несколько задач, постоянно что-то рисующих (кроме отображения видео).


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

Генерация сообщения WM_PAINT

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

void CWnd::InvalidateRect(LPRECT pRegion, BOOL EraseBackground = TRUE);

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

Пример программы



message handling.hpp



#include <afxwin.h>

// Класс основного окна

class CMainWin: public CFrameWnd {

public:

CMainWin();

// Функции обработки сообщений

afx_msg void OnChar(UINT ch, UINT, UINT);

afx_msg void OnPaint();

afx_msg void OnLButtonDown(UINT flags, CPoint Loc);

afx_msg void OnRButtonDown(UINT flags, CPoint Loc);

// Вспомогательные член-данные

char str[50];

int nMouseX, nMouseY, nOldMouseX, nOldMouseY;

char pszMouseStr[50];

// Декларирование карты откликов на сообщения

DECLARE_MESSAGE_MAP()

};

// Класс приложения

class CApp: public CWinApp {

public:

BOOL InitInstance();

};



message handling.cpp



#include <afxwin.h>

#include <string.h>

#include "MESSAGE HANDLING.HPP"

//

// Реализация

//

BOOL CApp::InitInstance()

{

m_pMainWnd = new CMainWin;

m_pMainWnd->ShowWindow(SW_RESTORE);

m_pMainWnd->UpdateWindow();

return TRUE;

}

CMainWin::CMainWin()

{

// Создать основное окно



this->Create(0, "Обработка сообщений");

// Инициализировать переменные объекта

strcpy(str, "");

strcpy(pszMouseStr, "");

nMouseX = nMouseY = nOldMouseX = nOldMouseY = 0;

}

// Реализация карты сообщений главного окна

BEGIN_MESSAGE_MAP(CMainWin /* класс */, CFrameWnd /* базовый класс */)

ON_WM_CHAR()

ON_WM_PAINT()

ON_WM_LBUTTONDOWN()

ON_WM_RBUTTONDOWN()

END_MESSAGE_MAP()

// Реализация функций отклика на сообщения

afx_msg void CMainWin::OnChar(UINT ch, UINT, UINT)

{

sprintf(str, "%c", ch);

// Посылаем сообщение WM_PAINT

// с необходимостью стереть и обновить все окно

this->InvalidateRect(0);

}

afx_msg void CMainWin::OnPaint()

{

// Создадим контекст устройства для обработки WM_PAINT

CPaintDC dc(this);

// Затираем текст и снова выводим (возможно уже другой текст)

dc.TextOut(nOldMouseX, nOldMouseY,

" ", 30);

dc.TextOut(nMouseX, nMouseY, pszMouseStr);

dc.TextOut(1, 1, " ");

dc.TextOut(1, 1, str);

}

afx_msg void CMainWin::OnLButtonDown(UINT, CPoint loc)

{

// Запоминаем в переменных класса координаты мыши и текст.

// Затем посылаем сообщение WM_PAINT - его обработчик

// выведет все на экран.

nOldMouseX = nMouseX; nOldMouseY = nMouseY;

strcpy(pszMouseStr, "Нажата левая кнопка");

nMouseX = loc.x; nMouseY = loc.y;

this->InvalidateRect(0);

}

afx_msg void CMainWin::OnRButtonDown(UINT, CPoint loc)

{

// Запоминаем в переменных класса координаты мыши и текст.

// Затем посылаем сообщение WM_PAINT - его обработчик

// выведет все на экран.

nOldMouseX = nMouseX; nOldMouseY = nMouseY;

strcpy(pszMouseStr, "Нажата правая кнопка");

nMouseX = loc.x; nMouseY = loc.y;

this->InvalidateRect(0);

}

CApp App; // Единственный экземпляр приложения

Рис. 3. Пример работы программы с обработкой сообщений.

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


Это делается с помощью вывода достаточено длинной строки из пробелов; строка выводится с белым фоном, поэтому предыдущая строка стирается. По умолчанию вывод в окно осуществляется системным шрифтом, ширина символов у которого различна. Поэтому, например, вывод символа "i" сразу поверх "W" не сотрет предыдущий символ.



Сообщения WM_TIMER и WM_DESTROY



Сообщение WM_TIMER

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

Для запроса таймера у системы используется следующая функция:

UINT CWnd::SetTimer(UINT Id, UINT Interval,

void (CALLBACK EXPORT *TFunc)(HWND, UINT, UINT, DWORD));

Третьим параметром пользуются очень редко, и обычно он равен 0. Мы не будем его использовать. Параметр Id задает уникальный идентификационный номер таймера. По этим номерам обычно различаются несколько созданных таймеров. Параметр Interval задает интервал между двумя посылками сообщений (интервал таймера) в миллисекундах. Разрешающая способность таймеров 55 мс, поэтому интервалы измеряются с такой точностью. Если даже задать значение интервала равным 1, то все равно будет использовано значение 55. Если задать 0, то таймер приостановит свою работу.

Сообщения таймера обрабатываются функцией:

afx_msg void OnTimer(UINT Id);

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

Сообщение WM_DESTROY

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


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

afx_msg void OnDestroy();

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

BOOL CWnd::KillTimer(int Id);

Функция освобождает таймер с идентификатором Id.

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

Пример программы



message handling II.hpp



#include <afxwin.h>

// Класс основного окна

class CMainWin: public CFrameWnd {

public:

CMainWin();

afx_msg void OnPaint();

// Обработчик сообщения WM_DESTROY

afx_msg void OnDestroy();

// Обработчик сообщения WM_TIMER

afx_msg void OnTimer(UINT ID);

char str[50];

DECLARE_MESSAGE_MAP()

};

// Класс приложения

class CApp: public CWinApp {

public:

BOOL InitInstance();

};



message handling II.cpp



#include <afxwin.h>

#include <time.h>

#include <string.h>

#include "MESSAGE HANDLING - II.HPP"

//

// Реализация

//

BOOL CApp::InitInstance()

{

m_pMainWnd = new CMainWin;

m_pMainWnd->ShowWindow(SW_RESTORE);

m_pMainWnd->UpdateWindow();

// Установка таймера с идентификатором 1 и интервалом 500 мс

m_pMainWnd->SetTimer(1, 500, 0);

return TRUE;

}

CMainWin::CMainWin()

{

// Определение прямоугольника, в котором будет размещено окно

RECT rect;

rect.left = rect.top = 10;

rect.right = 200;

rect.bottom = 60;

// Создание окна в определенном экранном прямоугольнике

this->Create(0, "CLOCK", WS_OVERLAPPEDWINDOW, rect);

strcpy(str, "");

}

// Реализация карты сообщений главного окна

BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd)

ON_WM_PAINT()

ON_WM_DESTROY()

ON_WM_TIMER()

END_MESSAGE_MAP()



afx_msg void CMainWin::OnPaint()

{

CPaintDC dc(this);

// Выводим в окно строку текущего времени

dc.TextOut(1, 1, " ", 3);

dc.TextOut(1, 1, str);

}

afx_msg void CMainWin::OnTimer(UINT ID)

{

// Предполагаем, что в программе один таймер, поэтому

// не проверяем ID.

// Получаем строку текущего времени

CTime curtime = CTime::GetCurrentTime();

tm *newtime;

newtime = curtime.GetLocalTm();

sprintf(str, asctime(newtime));

str[strlen(str) - 1] = '\0';

// Посылаем сообщение WM_PAINT -- его обработчик отобразит строку.

this->InvalidateRect(0);

}

afx_msg void CMainWin::OnDestroy()

{

// При закрытии окна удаляем связанный с ним таймер.

KillTimer(1);

}

CApp App; // Единственный экземпляр приложения.

Рис. 4. Программа-"часы"

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



Ресурсы. меню и акселераторы

Понятие ресурсов



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



При сборке проекта приложения, ресурсы добавляются к EXE-файлу уже после линковки. Для описания ресурсов существует специальный язык, а сами описания хранятся в текстовых файлах с расширением rc. Раньше программисты вручную писали сценарии ресурсов на языке ресурсов, сейчас же так никто не делает, а используются визуальные редакторы. Перед добавлением к исполняемому файлу сценарии преобразуются в бинарный вид с помощью компилятора ресурсов, в результате чего получается файл с расширением res. Как правило, все эти шаги выполняются автоматически при работе из интегрированной среды Visual C++. Вообще, ручное редактирование ресурсных сценариев требуется сейчас уже очень редко, лишь при определении нестандартных ресурсов в сложных проектах. Мы будем использовать только интегрированную среду, как это сейчас делают многие программисты.

Каждый ресурс имеет свой уникальный идентификатор. Это может быть либо строка, либо число (константа). Числа можно использовать всегда, а строки - не всегда. Мы будем использовать и то, и другое. Редактор ресурсов из Visual C++ помещает имена констант в файл resource.h, который нужно включить в файлы программы. Стандартные идентификаторы хранятся в файле afxres.h, который обычно используется автоматически ресурсным редактором.



Меню



Меню обычно создаются визуально. В Visual C++ нужно нажать клавиши Ctrl+2

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

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

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


При редактировании символьные идентификаторы заключаются в кавычки. Также, каждому пункту меню должен быть присвоен уникальный числовой идентификатор. Это позволит программе реагировать на выбор пункта в меню, в этом случае MFC будет вызывать соответствующий обработчик (об этом несколько позже). По принятому соглашению, все идентификаторы пунктов меню начинаются с IDM_. В самих названиях пунктов можно указывать ключевые клавиши, поставив перед буквой символ &. В этом случае, если меню активно, пункт можно выбрать также и с клавиатуры.

На рис. 5 показан процесс создания меню в среде Visual C++. Другие ресурсы создаются аналогично. Этот процесс довольно прост.

Рис. 5. Процесс визуального создания меню в среде Visual C++

Включение меню в окно приложения

Когда ресурс меню создан, его можно использовать в окне программы. Это можно сделать, указывая меню при создании окна: строковый идентификатор ресурса меню нужно указать в качестве последнего параметра в функции Create():

this->Create(0, "Приложение с меню", WS_OVERLAPPEDWINDOW,

rectDefautl, 0, "MYMENU");

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

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



Сообщение WM_COMMAND



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

ON_COMMAND(Идентификатор, ИмяОбработчика);

Каждый обработчик для WM_COMMAND должен возвращать void. Обработчики не имеют параметров.


Имя выбирается произвольно, обычно используется префикс On.

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



Акселераторы



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

BOOL CFrameWnd::LoadAccelTable(LPCSTR ResourceName);

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

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



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



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

int CWnd::MessageBox(LPCSTR MessageText,

LPCSTR WindowTitle = 0,

UINT MessageBoxType = MB_OK);

Параметр MessageText определяет само сообщение. Параметр WindowTitle - заголовок окна сообщения. И параметр MessageBoxType задает стиль окна, иконку, отображаемую слева от сообщения, и одну или несколько кнопок. Этот параметр задается комбинацией констант с помощью операции "|", начинающихся на префикс MB_. Все наборы кнопок заранее определены. Функция возвращает идентификатор нажатой кнопки: IDABORT, IDRETRY, IDIGNORE, IDCANCEL, IDNO, IDYES, или IDOK.

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

Пример программы, использующей меню, акселераторы и окна сообщений



menu.hpp



#include "stdafx.h"

// Класс основного окна

class CMainWin: public CFrameWnd {



public:

CMainWin();

afx_msg void OnPaint();

// Обработчики команд меню и акселераторов

afx_msg void OnCommand_Alpha();

afx_msg void OnCommand_Beta();

afx_msg void OnCommand_Gamma();

afx_msg void OnCommand_Epsilon();

afx_msg void OnCommand_Zeta();

afx_msg void OnCommand_Eta();

afx_msg void OnCommand_Theta();

afx_msg void OnCommand_Help();

afx_msg void OnCommand_Time(); //Только акселератор

DECLARE_MESSAGE_MAP()

};

// Класс приложения

class CApp: public CWinApp {

public:

BOOL InitInstance();

};



menu.cpp



#include "stdafx.h"

#include <string.h>

#include <stdio.h>

#include "MENU.HPP"

#include "IDS.H"

CMainWin::CMainWin()

{

// Прямоугольник для построения окна

RECT rect;

rect.left = 20; rect.top = 10;

rect.right = 600; rect.bottom = 460;

this->Create(0, "Menus", WS_OVERLAPPEDWINDOW, rect, 0, "MYMENU");

// Загрузка таблицы акселераторов

this->LoadAccelTable("MYMENU");

}

BOOL CApp::InitInstance()

{

m_pMainWnd = new CMainWin;

m_pMainWnd->ShowWindow(SW_RESTORE);

m_pMainWnd->UpdateWindow();

return TRUE;

}

// Карта откликов на сообщения

BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd)

ON_WM_PAINT()

ON_COMMAND(IDM_ALPHA, OnCommand_Alpha)

ON_COMMAND(IDM_BETA, OnCommand_Beta)

ON_COMMAND(IDM_GAMMA, OnCommand_Gamma)

ON_COMMAND(IDM_EPSILON, OnCommand_Epsilon)

ON_COMMAND(IDM_ZETA, OnCommand_Zeta)

ON_COMMAND(IDM_ETA, OnCommand_Eta)

ON_COMMAND(IDM_THETA, OnCommand_Theta)

ON_COMMAND(IDM_HELP, OnCommand_Help)

ON_COMMAND(IDM_TIME, OnCommand_Time)

END_MESSAGE_MAP()

afx_msg void CMainWin::OnPaint()

{

CPaintDC dc(this);

CString s("Press Ctrl- T to get current date and time");

dc.TextOut(100, 200, s);

}

//

// Далее идут обработчики сообщений WM_COMMAND.

// В них используется функция для вывода окон сообщений

// CWnd::MessageBox(LPCTSTR, LPCTSTR, UINT).

//

afx_msg void CMainWin::OnCommand_Alpha()

{

// Использование окна сообщений

this->MessageBox("OnCommand_Alpha() handler called.", "WM_COMMAND message",



MB_OK | MB_ICONINFORMATION);

}

afx_msg void CMainWin::OnCommand_Beta()

{

this->MessageBox("OnCommand_Beta() handler called.", "WM_COMMAND message",

MB_OK | MB_ICONINFORMATION);

}

afx_msg void CMainWin::OnCommand_Gamma()

{

this->MessageBox("OnCommand_Gamma() handler called.", "WM_COMMAND message",

MB_OK | MB_ICONINFORMATION);

}

afx_msg void CMainWin::OnCommand_Epsilon()

{

this->MessageBox("OnCommand_Epsilon() handler called.", "WM_COMMAND message",

MB_OK | MB_ICONINFORMATION);

}

afx_msg void CMainWin::OnCommand_Zeta()

{

this->MessageBox("OnCommand_Zeta() handler called.", "WM_COMMAND message",

MB_OK | MB_ICONINFORMATION);

}

afx_msg void CMainWin::OnCommand_Eta()

{

this->MessageBox("OnCommand_Eta() handler called.", "WM_COMMAND message",

MB_OK | MB_ICONINFORMATION);

}

afx_msg void CMainWin::OnCommand_Theta()

{

this->MessageBox("OnCommand_Theta() handler called.", "WM_COMMAND message",

MB_OK | MB_ICONINFORMATION);

}

afx_msg void CMainWin::OnCommand_Help()

{

this->MessageBox("OnCommand_Help() handler called.", "WM_COMMAND message",

MB_OK | MB_ICONINFORMATION);

}

afx_msg void CMainWin::OnCommand_Time()

{

// Получаем текущее время, используя класс MFC CTime

CTime currentTime = CTime::GetCurrentTime();

CString s = currentTime.Format("%A %B %#d, %Y, %#I:%M%p");

s = "OnCommand_Time() handler called.\n\n" + ("Current date and time:\n" + s);

this->MessageBox(s, "WM_COMMAND message");

}

CApp App; // Единственный объект приложения

Рис. 6. Программа, использующая меню, акселераторы и окна сообщений

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


Попробуйте выбирать различные пункты в меню.

Также внимательно изучите исходные тексты. Попробуйте изменить меню и акселераторы с помощью редактора ресурсов. Обратите внимание, что мы использовали в проекте два новых файла: stdafx.h

и stdafx.cpp. Первый из них включает все самые распространенные заголовочные файлы MFC, а второй - ничего не делающий модуль, использующий заголовочный файл. Это сделано для облегчения использования прекомпилированных заголовочных файлов. Также, в этом случае файл stdafx.obj

включает всю необходимую информацию о типах.



Диалоги. Знакомство с элементами управления



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

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



Взаимодействие между диалогом и пользователем



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

  • Обыкновенная кнопка (push button) - это кнопка, которую пользователь "нажимает" мышью или клавишей Enter, переместив предварительно на нее фокус ввода.




  • Контрольный переключатель ( check box) может быть либо выбранным, либо нет. Если в диалоге есть несколько контрольных переключателей, то могут быть выбраны одновременно несколько из них.


  • Радиокнопка (radio button) - это почти то же самое, что и контрольный переключатель. Только при наличии нескольких кнопок в группе может быть выбрана только одна.


  • Список (list box) содержит набор строк, из которого можно выбрать одну или несколько. Широко используется при отображении имен файлов.


  • Поле ввода (edit box) - это элемент, позволяющий ввести строку текста.


  • Комбинированный список (combo box) представляет собой список со строкой ввода.


  • Статический элемент (static control) предназначен для вывода текста или графики, но не предназначен для ввода.


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



    Классы MFC для элементов управления



    В MFC содержатся классы для всех стандартных элементов управления. Эти классы описывают сами элементы, а также содержат функции для работы с ними. Их называют классами управления. Они порождаются от класса CWnd. Таким образом, все они обладают характеристиками окна. Ниже приведены основные классы управления:



    Класс
    Элемент управления
    CButton Кнопки, селекторные кнопки и контрольные переключатели
    CEdit Поля ввода
    CListBox Списки
    CComboBox Комбинированные списки
    CScrollBar Полосы прокрутки
    Cstatic Статические элементы
    В MFC допускается также и непосредственное обращение к элементам управления, но на практике это происходит очень редко. Намного удобнее пользоваться соответствующими классами. Наиболее часто элементы управления используются с диалоговыми окнами, хотя можно создавать и отдельные элементы, расположенные в главном окне.



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



    Есть два типа диалогов: модальные и немодальные.


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



    Диалоги как ресурсы



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

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



    Класс CDialog



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

    CDialog::CDialog(LPCSTR ResourceName, CWnd *Owner = 0);

    CDialog::CDialog(UINT ResourceID, CWnd *Owner = 0);

    CDialog::CDialog();

    Параметр ResourceName или ResourceID определяет идентификатор диалога в ресурсах, строковый или числовой. Параметр Owner - это указатель на окно-собственник, если равен 0, то собственником будет главное окно приложения. Последняя форма конструктора предназначена для создания немодальных диалогов, о которых будет сказано в дальнейшем.

    Обработка сообщений от диалогов

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


    Для каждого диалога организуется собственная очередь сообщений, так же точно, как и для главного окна.

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

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

    Вызов диалога

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

    virtual int CDialog::DoModal();

    Функция возвращает код завершения, генерируемый диалогом при закрытии, или -1, если окно не может быть отображено. Если при отображении диалога произошла ошибка, возвращается IDABORT. Функция не завершается, пока диалог не будет закрыт.

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

    По умолчанию диалог закрывается при получении сообщения с идентификатором либо IDOK, либо IDCANCEL. Они предопределены и обычно связаны с кнопками подтверждения и отмены. Класс CDialog содержит встроенные обработчики для этих двух случаев, OnOK() и OnCancel(). Их не нужно включать в очередь сообщений диалога. Но их можно переопределить, что дает возможность программисту управлять закрытием диалога. Для программного закрытия диалога нужно вызвать член-функцию с прототипом:

    void CDialog::EndDialog(int RetCode);

    Параметр определят значение, которое вернет функция DoModal(). Обычно возвращаются значения IDOK или IDCANCEL, другие значения используются редко.

    Пример программы с диалоговым окном

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


    Он имеет три элемента управления - кнопки "Red", "Green" и "Cancel". Также переопределена функция OnCancel(), что позволяет при попытке закрытия диалога вывести окно с подтверждением. Диалог закрывается программно, с возвратом кода завершения 7, который затем возвращается функцией DoModal() и отображается в окне. Это сделано лишь для демонстрации, реально же обычно возвращается IDCANCEL. Диалог создавался визуально в среде Visual C++. Здесь приводится также файл с идентификаторами, сгенерированный средой. В дальнейшем этот файл приводиться не будет, потому что, вообще говоря, он не предназначен для чтения человеком. Так как все идентификаторы в примерах имеют осмысленные имена, то загрузив проект в среду IDE, Вы сможете сразу узнать, какие идентификаторы каким элементам управления соответствуют.



    resource.h



    //{{NO_DEPENDENCIES}}

    // Microsoft Developer Studio generated include file.

    // Used by Resources.rc

    //

    #define IDC_RED 1007

    #define IDC_GREEN 1008

    #define IDM_DIALOG 40001

    #define IDM_HELP 40002

    #define IDM_EXIT 40003

    // Next default values for new objects

    //

    #ifdef APSTUDIO_INVOKED

    #ifndef APSTUDIO_READONLY_SYMBOLS

    #define _APS_NO_MFC 1

    #define _APS_3D_CONTROLS 1

    #define _APS_NEXT_RESOURCE_VALUE 106

    #define _APS_NEXT_COMMAND_VALUE 40010

    #define _APS_NEXT_CONTROL_VALUE 1008

    #define _APS_NEXT_SYMED_VALUE 101

    #endif

    #endif



    Dialogs.hpp



    #include "stdafx.h"

    #include "resource.h"

    // Класс главного окна

    class CMainWin: public CFrameWnd {

    public:

    // Конструктор немного изменен.

    // Title -- заголовок окна, HowShow -- код показа окна

    CMainWin(CString Title, int HowShow);

    afx_msg void OnCommand_Dialog();

    afx_msg void OnCommand_Exit();

    afx_msg void OnCommand_Help();

    afx_msg void OnPaint();

    // Установка строки, отображаемой в верхней части окна

    virtual void SetInfoStr(CString str);

    private:

    CString infoStr; // Строка, отображаемая в верхней части окна

    DECLARE_MESSAGE_MAP()

    };



    // Класс приложения

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };

    // Класс диалогового окна

    class CSampleDialog: public CDialog {

    public:

    // DialogName -- идентификатор диалога в ресурсах,

    // Owner -- окно-владелец (если NULL, то главное окно)

    CSampleDialog(char *DialogName, CWnd *Owner = NULL);

    afx_msg void OnCommand_Red();

    afx_msg void OnCommand_Green();

    afx_msg void OnCancel();

    private:

    DECLARE_MESSAGE_MAP()

    };



    Dialogs.cpp



    #include "stdafx.h"

    #include "Dialogs.hpp"

    #include "resource.h"

    CApp App;

    //---------------------------------------------------------------------------

    CMainWin::CMainWin(CString Title, int HowShow)

    :infoStr("")

    {

    RECT rect;

    rect.top = 10; rect.left = 50;

    rect.bottom = 460, rect.right = 630;

    this->Create(0, Title, WS_OVERLAPPEDWINDOW, rect, 0, "DIALOGMENU");

    this->ShowWindow(HowShow);

    this->UpdateWindow();

    this->LoadAccelTable("DIALOGMENU");

    }

    afx_msg void CMainWin::OnCommand_Dialog()

    {

    CSampleDialog sampleDialog("SAMPLEDIALOG", this);

    int result = sampleDialog.DoModal();

    char s[20];

    sprintf(s, "%i", result);

    this->SetInfoStr(infoStr + ". " +

    "Функция CDialog::DoModal() возвратила " + CString(s));

    }

    afx_msg void CMainWin::OnCommand_Exit()

    {

    this->MessageBox("Завершение приложения.", "Dialogs", MB_ICONEXCLAMATION);

    this->SendMessage(WM_CLOSE);

    }

    afx_msg void CMainWin::OnCommand_Help()

    {

    this->MessageBox("Программа, использующая диалог.\n"

    "Написана с помощью MFC 4.0.",

    "Dialogs", MB_ICONINFORMATION);

    }

    afx_msg void CMainWin::OnPaint()

    {

    CPaintDC paintDC(this);

    paintDC.TextOut(10, 10, infoStr);

    }

    void CMainWin::SetInfoStr(CString str)

    {

    infoStr = str;

    this->InvalidateRect(0);

    }

    // Карта откликов на сообщения главного окна

    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd)

    ON_COMMAND(IDM_DIALOG, OnCommand_Dialog)



    ON_COMMAND(IDM_HELP, OnCommand_Help)

    ON_COMMAND(IDM_EXIT, OnCommand_Exit)

    ON_WM_PAINT()

    END_MESSAGE_MAP()

    //---------------------------------------------------------------------------

    BOOL CApp::InitInstance()

    {

    m_pMainWnd = new CMainWin("Dialogs - приложение с диалогом", SW_RESTORE);

    return TRUE;

    }

    //---------------------------------------------------------------------------

    CSampleDialog::CSampleDialog(char *DialogName, CWnd *Owner)

    :CDialog(DialogName, Owner)

    {

    }

    afx_msg void CSampleDialog::OnCommand_Red()

    {

    // Член-функция CWnd::GetOwner() возвращает указатель

    // на окно-собственник данного окна.

    // Так как в данном случае это главное окно,

    // мы проводим преобразование к CMainWin

    // и вызываем метод SetInfoStr()

    ((CMainWin *) (this->GetOwner()))

    ->SetInfoStr("Нажата кнопка 'Red'");

    }

    afx_msg void CSampleDialog::OnCommand_Green()

    {

    // См. комментарий в CSampleDialog::OnCommand_Red()

    ((CMainWin *) (this->GetOwner()))

    ->SetInfoStr("Нажата кнопка 'Green'");

    }

    afx_msg void CSampleDialog::OnCancel()

    {

    // См. комментарий в CSampleDialog::OnCommand_Red()

    ((CMainWin *) (this->GetOwner()))

    ->SetInfoStr("Нажата кнопка 'Cancel'");

    // Закрываем диалог, только если пользователь подтвердил намерение.

    // Значение 7 будет возвращено DoModal().

    int response = this->MessageBox("Вы уверены ?", "Cancel", MB_YESNO);

    if(response == IDYES)

    this->EndDialog(7);

    }

    // Карта откликов на сообщения диалога

    BEGIN_MESSAGE_MAP(CSampleDialog, CDialog)

    ON_COMMAND(IDC_RED, OnCommand_Red)

    ON_COMMAND(IDC_GREEN, OnCommand_Green)

    END_MESSAGE_MAP()

    //---------------------------------------------------------------------------

    Рис. 7. Пример приложения с диалогом



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



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


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

    virtual BOOL CDialog::OnInitDialog();

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

    Первым действием в переопределенной функции должен быть вызов функции CDialog::OnInitDialog().

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



    Списки



    Список является одним из наиболее распространенных элементов управления. В MFC

    работа со списком осуществляется через класс CListBox.



    Основы работы со списками



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

    Прием идентификационных кодов списка

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

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



    Для обработки сообщения LBN_DBLCLK необходимо поместить его обработчик в карту сообщений. Но это будет не макрос ON_COMMAND(). Вместо этого в данном случае используются специальные макрокоманды. Для нашего сообщения это будет ON_LBN_DBLCLK(). Она имеет такой вид:

    ON_LBN_DBLCLK(ИдентификаторСписка, ИмяОбработчика)

    Многие сообщения обрабатываются подобным образом. Названия всех макросов для таких сообщений начинаются с префикса ON_LBN_.

    Передача сообщений списку

    В традиционных Windows-программах сообщения посылаются элементам управления с помощью API-функций, например SendDlgItemMessage(). Но в программах на MFC для этих целей применяются соответствующие функции-члены. Они автоматически посылают нужное сообщение элементу управления. В этом заключается преимущество MFC по сравнению с традиционным методом программирования.

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

    int CListBox::AddString(LPCSTR StringToAdd);

    int CListBox::GetCurSel() const;

    int CListBox::GetText(int Index, LPCSTR StringVariable);

    Функция AddString() вставляет указанную строку в список. По умолчанию она вставляется в конец списка. Начало списка имеет индекс 0. Функция GetCurSel()

    возвращает индекс текущего выделенного элемента. Если ни один элемент не выбран, возвращает LB_ERR.

    Функция GetText() получает строку, связанную с указанным индексом. Строка копируется в символьный массив по адресу StringVariable.

    Получение указателя на список

    Функции CListBox работают с объектами CListBox. Значит, необходимо получить указатель на объект списка, что делается с помощью функции GetDlgItem(), являющейся членом класса CWnd:

    СWnd *CWnd::GetDlgItem(int ItemIdentifier) const;

    Эта функция возвращает указатель на объект, чей идентификатор передан как параметр. Если такой объект не существует, то возвращается 0. Необходимо понимать, что указатель, возвращаемый функцией, является временным.


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

    Значение, возвращенное функцией, должно быть приведено к типу указателя на конкретный класс управления. Например, в нашем случае это тип CListBox*.

    Инициализация списка

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

    Пример программы

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



    Listbox.hpp



    #include "stdafx.h"

    #include "resource.h"

    // Класс основного окна

    class CMainWin: public CFrameWnd {

    public:

    CMainWin(CString Title, int HowShow);

    afx_msg void OnCommand_Dialog();

    afx_msg void OnCommand_Exit();

    afx_msg void OnCommand_Help();

    afx_msg void OnPaint();

    virtual void SetInfoStr(CString str);

    private:

    CString infoStr;

    DECLARE_MESSAGE_MAP()

    };

    // Класс приложения

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };

    // Класс диалога

    class CSampleDialog: public CDialog {

    public:

    CSampleDialog(char *DialogName, CWnd *Owner = NULL);

    BOOL OnInitDialog();

    afx_msg void OnCommand_Red();

    afx_msg void OnCommand_Green();

    afx_msg void OnSelectFruit();

    afx_msg void OnCancel();

    private:

    DECLARE_MESSAGE_MAP()

    };



    Listbox.cpp



    #include "stdafx.h"

    #include <stdlib.h>

    #include "Listbox.hpp"

    #include "Resource.h"

    //---------------------------------------------------------------------------



    //

    // Реализация CMainWin

    //

    CMainWin::CMainWin( CString Title, int HowShow)

    :infoStr("")

    {

    RECT rect;

    rect.top = 10; rect.left = 50;

    rect.bottom = 460, rect.right = 630;

    this->Create(0, Title, WS_OVERLAPPEDWINDOW, rect, 0, "MENU");

    this->ShowWindow(HowShow);

    this->UpdateWindow();

    this->LoadAccelTable("DIALOGMENU");

    }

    afx_msg void CMainWin::OnCommand_Dialog()

    {

    CSampleDialog sampleDialog("SAMPLEDIALOG");

    sampleDialog.DoModal();

    this->SetInfoStr("Диалог закрыт.");

    }

    afx_msg void CMainWin::OnCommand_Exit()

    {

    this->SendMessage(WM_CLOSE);

    }

    afx_msg void CMainWin::OnCommand_Help()

    {

    this->MessageBox("Демонстрация элемента управления Listbox",

    "Listbox", MB_ICONINFORMATION);

    }

    afx_msg void CMainWin::OnPaint()

    {

    CPaintDC paintDC(this);

    paintDC.TextOut(10, 10, infoStr, infoStr.GetLength());

    }

    void CMainWin::SetInfoStr(CString str)

    {

    infoStr = str;

    this->InvalidateRect(0);

    }

    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd)

    ON_COMMAND(IDM_DIALOG, OnCommand_Dialog)

    ON_COMMAND(IDM_HELP, OnCommand_Help)

    ON_COMMAND(IDM_EXIT, OnCommand_Exit)

    ON_WM_PAINT()

    END_MESSAGE_MAP()

    //---------------------------------------------------------------------------

    BOOL CApp::InitInstance()

    {

    m_pMainWnd = new CMainWin("Listbox", SW_RESTORE);

    return TRUE;

    }

    //---------------------------------------------------------------------------

    CSampleDialog::CSampleDialog(char *DialogName, CWnd *Owner)

    :CDialog(DialogName, Owner)

    {

    }

    afx_msg void CSampleDialog::OnCommand_Red()

    {

    ((CMainWin *) (this->GetOwner()))

    ->SetInfoStr("Нажата кнопка 'Red'");

    }

    afx_msg void CSampleDialog::OnCommand_Green()

    {

    ((CMainWin *) (this->GetOwner()))

    ->SetInfoStr("Нажата кнопка 'Green'");

    }

    // Инициализация диалога

    BOOL CSampleDialog::OnInitDialog()

    {

    CDialog::OnInitDialog(); // Вызвать метод базового класса

    // Получить объект списка Listbox



    CListBox *listBoxPtr = (CListBox *) (this->GetDlgItem(IDC_LB1));

    // Добавить строки в список

    listBoxPtr->AddString("Яблоко");

    listBoxPtr->AddString("Слива");

    listBoxPtr->AddString("Груша");

    listBoxPtr->AddString("Апельсин");

    listBoxPtr->AddString("Мандарин");

    listBoxPtr->AddString("Абрикос");

    listBoxPtr->AddString("Персик");

    listBoxPtr->AddString("Ананас");

    return TRUE;

    }

    // Обработчик сообщения выбора элемента

    // ( двойной щелчок на списке или нажатие специальной кнопки)

    afx_msg void CSampleDialog::OnSelectFruit()

    {

    CListBox *listBoxPtr = (CListBox *) (this->GetDlgItem(IDC_LB1));

    // Получить номер текущего выделенного элемента списка

    int index = listBoxPtr->GetCurSel();

    // Если результат LB_ERR, то ни один элемент еще не выделен

    if(index == LB_ERR) {

    ((CMainWin *) (this->GetOwner()))

    ->SetInfoStr("Ни один фрукт не выделен");

    }

    else {

    char s[100];

    // Получить текст выделенного элемента

    listBoxPtr->GetText(index, s);

    CString infoStr;

    infoStr.Format("Выбран фрукт %s с индексом %i", s, index);

    // Вывести сообщение

    ((CMainWin *) (this->GetOwner()))

    ->SetInfoStr(infoStr);

    }

    }

    afx_msg void CSampleDialog::OnCancel()

    {

    ((CMainWin *) (this->GetOwner()))

    ->SetInfoStr("Нажата кнопка 'Cancel'");

    this->EndDialog(TRUE);

    }

    BEGIN_MESSAGE_MAP(CSampleDialog, CDialog)

    ON_COMMAND(IDC_RED, OnCommand_Red)

    ON_COMMAND(IDC_GREEN, OnCommand_Green)

    ON_COMMAND(IDC_SELFRUIT, OnSelectFruit)

    ON_LBN_DBLCLK(IDC_LB1, OnSelectFruit) // Сообщение от списка

    END_MESSAGE_MAP()

    //---------------------------------------------------------------------------

    CApp App;

    Рис. 8. Пример приложения со списком



    Поле ввода



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


    Даже зная, как создавать только поля ввода и списки, уже можно писать полезные программы.

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

    Для получения текущего содержимого поля вода, состоящего из одной строки, используется функция GetWindowText(). Ее прототип таков:

    int CWnd::GetWindowText(LPSTR StringVariable, int MaxStringLen) const;

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

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

    void CWnd::SetWindowText(LPCSTR String);

    Пример программы с полем ввода



    edbox.hpp



    #include "stdafx.h"

    #include "resource.h"

    // Класс основного окна

    class CMainWin: public CFrameWnd {

    public:

    CMainWin(CString Title, int HowShow);

    afx_msg void OnCommand_Dialog();

    afx_msg void OnCommand_Exit();

    afx_msg void OnCommand_Help();

    afx_msg void OnPaint();

    virtual void SetInfoStr(CString str);

    private:

    CString infoStr;

    DECLARE_MESSAGE_MAP()

    };

    // Класс приложения

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };

    // Класс диалога

    class CSampleDialog: public CDialog {

    public:

    CSampleDialog(char *DialogName, CWnd *Owner);

    BOOL OnInitDialog();

    afx_msg void OnCommand_Red();

    afx_msg void OnCommand_Green();

    afx_msg void OnCommand_EditOK(); // Здесь читаются данные поля ввода

    afx_msg void OnSelectFruit();



    afx_msg void OnCancel();

    private:

    DECLARE_MESSAGE_MAP()

    };



    edbox.hpp



    #include "stdafx.h"

    #include <stdlib.h>

    #include "Edbox.hpp"

    #include "resource.h"

    //

    // Реализация

    //

    //---------------------------------------------------------------------------

    CMainWin::CMainWin( CString Title, int HowShow)

    :infoStr("")

    {

    RECT rect;

    rect.top = 10; rect.left = 50;

    rect.bottom = 460, rect.right = 630;

    this->Create(0, Title, WS_OVERLAPPEDWINDOW, rect, 0, "DIALOGMENU");

    this->ShowWindow(HowShow);

    this->UpdateWindow();

    this->LoadAccelTable("DIALOGMENU");

    }

    afx_msg void CMainWin::OnCommand_Dialog()

    {

    CSampleDialog sampleDialog("SAMPLEDIALOG", this);

    sampleDialog.DoModal();

    this->SetInfoStr("Диалог закрыт");

    }

    afx_msg void CMainWin::OnCommand_Exit()

    {

    this->SendMessage(WM_CLOSE);

    }

    afx_msg void CMainWin::OnCommand_Help()

    {

    this->MessageBox("Программа, использующая Editbox\n",

    "Editbox", MB_ICONINFORMATION);

    }

    afx_msg void CMainWin::OnPaint()

    {

    CPaintDC paintDC(this);

    paintDC.TextOut(10, 10, infoStr);

    }

    void CMainWin::SetInfoStr(CString str)

    {

    infoStr = str;

    this->InvalidateRect(0);

    }

    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd)

    ON_COMMAND(IDM_DIALOG, OnCommand_Dialog)

    ON_COMMAND(IDM_HELP, OnCommand_Help)

    ON_COMMAND(IDM_EXIT, OnCommand_Exit)

    ON_WM_PAINT()

    END_MESSAGE_MAP()

    //---------------------------------------------------------------------------

    BOOL CApp::InitInstance()

    {

    m_pMainWnd = new CMainWin("Editbox", SW_RESTORE);

    return TRUE;

    }

    //---------------------------------------------------------------------------

    CSampleDialog::CSampleDialog(char *DialogName, CWnd *Owner)

    :CDialog(DialogName, Owner)

    {

    }

    BOOL CSampleDialog::OnInitDialog()

    {

    CDialog::OnInitDialog();

    CListBox *listBoxPtr = (CListBox *) (this->GetDlgItem(IDC_LB1));

    listBoxPtr->AddString("Яблоко");

    listBoxPtr->AddString("Слива");



    listBoxPtr->AddString("Груша");

    listBoxPtr->AddString("Апельсин");

    listBoxPtr->AddString("Мандарин");

    listBoxPtr->AddString("Абрикос");

    listBoxPtr->AddString("Персик");

    listBoxPtr->AddString("Ананас");

    // Получить объект Editbox

    CEdit *editBoxPtr = (CEdit *) (this->GetDlgItem(IDC_EDIT1));

    // Установить текст в Editbox

    editBoxPtr->SetWindowText("Вася");

    return TRUE;

    }

    afx_msg void CSampleDialog::OnCommand_Red()

    {

    ((CMainWin *) (this->GetOwner()))->SetInfoStr("Нажата кнопка 'Red'");

    }

    afx_msg void CSampleDialog::OnCommand_Green()

    {

    ((CMainWin *) (this->GetOwner()))->SetInfoStr("Нажата кнопка 'Green'");

    }

    // Здесь читается текст из Editbox

    afx_msg void CSampleDialog::OnCommand_EditOK()

    {

    // Получить объект Editbox

    CEdit *pCEdit = (CEdit *) (this->GetDlgItem(IDC_EDIT1));

    char s[1024];

    // Прочитать текст из Editbox

    pCEdit->GetWindowText(s, sizeof(s) / sizeof(s[0]) - 1);

    // Вывести информацию в окно

    ((CMainWin *) (this->GetOwner()))->SetInfoStr(CString("Введена строка \"") + s + "\"");

    }

    afx_msg void CSampleDialog::OnSelectFruit()

    {

    CListBox *listBoxPtr = (CListBox *) (this->GetDlgItem(IDC_LB1));

    int index = listBoxPtr->GetCurSel();

    if(index == LB_ERR) {

    ((CMainWin *) (this->GetOwner()))->SetInfoStr("Ни один фрукт не выделен");

    }

    else {

    char s[100];

    listBoxPtr->GetText(index, s);

    CString infoStr;

    infoStr.Format("Выбран фрукт %s с индексом %i", s, index);

    ((CMainWin *) (this->GetOwner()))->SetInfoStr(infoStr);

    }

    }

    afx_msg void CSampleDialog::OnCancel()

    {

    this->EndDialog(TRUE);

    }

    BEGIN_MESSAGE_MAP(CSampleDialog, CDialog)

    ON_COMMAND(IDC_RED, OnCommand_Red)

    ON_COMMAND(IDC_GREEN, OnCommand_Green)

    ON_COMMAND(IDC_EDITOK, OnCommand_EditOK)

    ON_COMMAND(IDC_SELFRUIT, OnSelectFruit)

    ON_LBN_DBLCLK(IDC_LB1, OnSelectFruit)



    END_MESSAGE_MAP()

    //---------------------------------------------------------------------------

    CApp App;

    Рис. 9. Приложение со строкой редактирования в диалоге



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



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

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

    Для создания объекта немодального диалога нужно использовать конструктор CDialog::CDialog() без параметров. Он объявлен как protected-член класса. Это означает, что он может быть вызван только изнутри член-функции порожденного класса. Это сделано для того, чтобы программист обязательно определял свой порожденный класс для немодального диалога, и определял в нем дополнительные операции для немодального диалога.

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

    BOOL CDialog::Create(LPCSTR ResourceName, CWnd *Owner = 0);

    BOOL CDialog::Create(UINT ResourceId, CWnd *Owner = 0);

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

    Необходимо помнить о том, что объект немодального диалога должен существовать в течение всего времени использования диалога. Функция Create() отображает окно и после этого немедленно завершается. А объект окна должен существовать.

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

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



    и/или OnOK() должны быть переопределены.

    Пример программы с немодальным диалогом



    ModelessDialog.hpp



    #include "stdafx.h"

    #include "resource.h"

    class CMainWin;

    // Класс немодального диалога

    class CSampleDialog: public CDialog {

    public:

    CSampleDialog();

    // Функция привязки диалога к ресурсам

    BOOL Create(LPCSTR DialogName, CWnd *Owner = 0);

    BOOL OnInitDialog();

    afx_msg void OnCommand_Red();

    afx_msg void OnCommand_Green();

    afx_msg void OnCommand_EditOK();

    afx_msg void OnSelectFruit();

    afx_msg void OnCancel();

    private:

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

    BOOL inUse;

    CMainWin *owner;

    DECLARE_MESSAGE_MAP()

    };

    class CMainWin: public CFrameWnd {

    public:

    CMainWin(CString Title, int HowShow);

    afx_msg void OnCommand_Dialog();

    afx_msg void OnCommand_Exit();

    afx_msg void OnCommand_Help();

    afx_msg void OnPaint();

    virtual void SetInfoStr(CString str);

    private:

    // Экземпляр объекта диалога -

    // существует все время, пока существует основное окно

    CSampleDialog dialog;

    CString infoStr;

    DECLARE_MESSAGE_MAP()

    };

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };



    ModelessDialog.cpp



    #include "stdafx.h"

    #include <stdlib.h>

    #include "ModelessDialog.hpp"

    #include "resource.h"

    static CApp App;

    //---------------------------------------------------------------------------

    CMainWin::CMainWin(CString Title, int HowShow)

    // Вызываем конструктор без параметров класса диалога

    :dialog(), infoStr("")

    {

    RECT rect;

    rect.top = 10; rect.left = 50;

    rect.bottom = 460, rect.right = 630;

    this->Create(0, Title, WS_OVERLAPPEDWINDOW, rect, 0, "DIALOGMENU");

    this->ShowWindow(HowShow);

    this->UpdateWindow();

    this->LoadAccelTable("DIALOGMENU");

    }

    afx_msg void CMainWin::OnCommand_Dialog()

    {

    dialog.Create("SAMPLEDIALOG", this);

    }

    afx_msg void CMainWin::OnCommand_Exit()

    {

    this->SendMessage(WM_CLOSE);



    }

    afx_msg void CMainWin::OnCommand_Help()

    {

    this->MessageBox("Программа, использующая немодальный диалог.",

    "О программе", MB_ICONINFORMATION);

    }

    afx_msg void CMainWin::OnPaint()

    {

    CPaintDC paintDC(this);

    paintDC.TextOut(10, 10, infoStr);

    }

    void CMainWin::SetInfoStr(CString str)

    {

    infoStr = str;

    this->InvalidateRect(0);

    }

    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd)

    ON_COMMAND(IDM_DIALOG, OnCommand_Dialog)

    ON_COMMAND(IDM_HELP, OnCommand_Help)

    ON_COMMAND(IDM_EXIT, OnCommand_Exit)

    ON_WM_PAINT()

    END_MESSAGE_MAP()

    //---------------------------------------------------------------------------

    BOOL CApp::InitInstance()

    {

    m_pMainWnd = new CMainWin("Приложение с немодальным диалогом", SW_RESTORE);

    return TRUE;

    }

    //---------------------------------------------------------------------------

    CSampleDialog::CSampleDialog()

    :CDialog()

    {

    // Объект создан, но пока не используется

    inUse = FALSE;

    }

    BOOL CSampleDialog::Create(LPCSTR DialogName, CWnd *Owner)

    {

    // Если диалог уже используется (был отображен), то только отобразить его

    if(inUse) {

    this->ShowWindow(SW_SHOW);

    return TRUE;

    }

    inUse = TRUE; // Диалог используется

    // Создать диалог и получить результат создания

    BOOL success = (CDialog::Create(DialogName, Owner) != FALSE);

    owner = (CMainWin *) Owner; // Собственник

    return success;

    }

    BOOL CSampleDialog::OnInitDialog()

    {

    CDialog::OnInitDialog();

    CListBox *listBoxPtr = (CListBox *) (this->GetDlgItem(IDC_LB1));

    listBoxPtr->AddString("Яблоко");

    listBoxPtr->AddString("Слива");

    listBoxPtr->AddString("Груша");

    listBoxPtr->AddString("Апельсин");

    listBoxPtr->AddString("Мандарин");

    listBoxPtr->AddString("Абрикос");

    listBoxPtr->AddString("Персик");

    listBoxPtr->AddString("Ананас");

    CEdit *editBoxPtr = (CEdit *) (this->GetDlgItem(IDC_EDIT1));

    editBoxPtr->SetWindowText("Вася");

    return TRUE;



    }

    afx_msg void CSampleDialog::OnCommand_Red()

    {

    owner->SetInfoStr("Нажата кнопка 'Red'");

    }

    afx_msg void CSampleDialog::OnCommand_Green()

    {

    owner->SetInfoStr("Нажата кнопка 'Green'");

    }

    afx_msg void CSampleDialog::OnCommand_EditOK()

    {

    CEdit *pCEdit = (CEdit *) (this->GetDlgItem(IDC_EDIT1));

    char s[1024];

    pCEdit->GetWindowText(s, sizeof(s) / sizeof(s[0]) - 1);

    owner->SetInfoStr(CString("Введена строка \"") + s + "\"");

    }

    afx_msg void CSampleDialog::OnSelectFruit()

    {

    CListBox *listBoxPtr = (CListBox *) (this->GetDlgItem(IDC_LB1));

    int index = listBoxPtr->GetCurSel();

    if(index == LB_ERR) {

    owner->SetInfoStr("Ни один фрукт не выделен");

    }

    else {

    char s[100];

    listBoxPtr->GetText(index, s);

    CString infoStr;

    infoStr.Format("Выбран фрукт %s с индексом %i", s, index);

    owner->SetInfoStr(infoStr);

    }

    }

    afx_msg void CSampleDialog::OnCancel()

    {

    owner->SetInfoStr("Нажата кнопка 'Cancel'");

    // Удалить диалог

    this->DestroyWindow();

    // Больше диалог не используется

    inUse = FALSE;

    }

    BEGIN_MESSAGE_MAP(CSampleDialog, CDialog)

    ON_COMMAND(IDC_RED, OnCommand_Red)

    ON_COMMAND(IDC_GREEN, OnCommand_Green)

    ON_COMMAND(IDC_EDITOK, OnCommand_EditOK)

    ON_COMMAND(IDC_SELFRUIT, OnSelectFruit)

    ON_LBN_DBLCLK(IDC_LB1, OnSelectFruit)

    END_MESSAGE_MAP()

    Рис. 9. Пример приложения с немодальным диалогом



    Дополнительные элементы управления



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



    Контрольные переключатели



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


    Кроме того, с переключателем связано текстовое поле с описанием предоставляемой переключателем опции. Если в переключателе стоит метка выбора, то говорится, что он выбран (установлен). Контрольные переключатели в MFC описываются с помощью класса CButton (так как контрольный переключатель - разновидность кнопки).

    Контрольные переключатели могут быть автоматическими

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

    Сообщения контрольного переключателя

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

    Установка и чтение состояния контрольного переключателя

    Чтобы установить контрольный переключатель в заданное состояние, нужно использовать функцию SetCheck() c прототипом:

    void CButton::SetCheck(int Status);

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

    Текущее состояние переключателя можно определить с помощью функции GetCheck():

    int CButton::GetCheck() const;

    Функция возвращает 1, если переключатель установлен, и 0 в противном случае.



    Инициализация контрольных переключателей

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

    Пример программы



    CheckBox.hpp



    #include "stdafx.h"

    #include "resource.h"

    class CMainWin: public CFrameWnd {

    public:

    CMainWin(CString Title, int HowShow);

    afx_msg void OnCommand_Dialog();

    afx_msg void OnCommand_Status();

    afx_msg void OnCommand_Exit();

    afx_msg void OnPaint();

    virtual void SetInfoStr(CString str);

    private:

    CString infoStr;

    DECLARE_MESSAGE_MAP()

    };

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };

    class CMyDialog: public CDialog {

    public:

    CMyDialog(char *DialogName, CWnd *Owner = 0);

    BOOL OnInitDialog();

    // Обработчик кликов мышью на неавтоматическом переключателе

    afx_msg void OnCheckbox1();

    afx_msg void OnOK();

    private:

    DECLARE_MESSAGE_MAP()

    };



    CheckBox.cpp



    #include "stdafx.h"

    #include "CheckBox.hpp"

    #include "resource.h"

    // Переменные, хранящие состояния переключателей

    int cb1status = 0, cb2status = 0;

    CApp App;

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    CMainWin::CMainWin(CString Title, int HowShow)

    :infoStr("")

    {

    RECT rect;

    rect.top = 10; rect.left = 50; rect.bottom = 460; rect.right = 630;

    this->Create(0, Title, WS_OVERLAPPEDWINDOW, rect, 0, "MENU");

    this->ShowWindow(HowShow);

    this->UpdateWindow();

    this->LoadAccelTable("MENU");

    }

    afx_msg void CMainWin::OnCommand_Dialog()

    {

    CMyDialog dialog("DIALOG", this);

    dialog.DoModal();

    }

    // Отображаем состояние переключателей

    afx_msg void CMainWin::OnCommand_Status()

    {

    CString str;

    if(cb1status != 0)

    str = "Checkbox1 выбран.\n";

    else

    str = "Checkbox1 очищен.\n";



    if(cb2status != 0)

    str = str + "Checkbox2 выбран.\n";

    else

    str = str + "Checkbox2 очищен.\n";

    this->MessageBox(str, "Состояние");

    }

    afx_msg void CMainWin::OnCommand_Exit()

    {

    this->SendMessage(WM_CLOSE);

    }

    afx_msg void CMainWin::OnPaint()

    {

    CPaintDC paintDC(this);

    paintDC.TextOut(10, 10, infoStr);

    }

    void CMainWin::SetInfoStr(CString str)

    {

    infoStr = str;

    this->InvalidateRect(0);

    }

    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd)

    ON_COMMAND(IDM_DIALOG, OnCommand_Dialog)

    ON_COMMAND(IDM_STATUS, OnCommand_Status)

    ON_COMMAND(IDM_EXIT, OnCommand_Exit)

    ON_WM_PAINT()

    END_MESSAGE_MAP()

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    BOOL CApp::InitInstance()

    {

    m_pMainWnd = new CMainWin("Использование контрольных переключателей",

    SW_RESTORE);

    return TRUE;

    }

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    CMyDialog::CMyDialog(char *DialogName, CWnd *Owner)

    :CDialog(DialogName, Owner)

    {

    }

    BOOL CMyDialog::OnInitDialog()

    {

    CDialog::OnInitDialog();

    // Получаем адреса объектов переключателей

    CButton *pcb1 = (CButton *) (this->GetDlgItem(IDC_CHECK1));

    CButton *pcb2 = (CButton *) (this->GetDlgItem(IDC_CHECK2));

    pcb1->SetCheck(cb1status);

    pcb2->SetCheck(cb2status);

    return TRUE;

    }

    afx_msg void CMyDialog::OnCheckbox1()

    {

    // Получаем адрес объекта

    CButton *pcb1 = (CButton *) (this->GetDlgItem(IDC_CHECK1));

    // Реализуем переключение

    if(pcb1->GetCheck() == 1)

    pcb1->SetCheck(0);

    else

    pcb1->SetCheck(1);

    }

    afx_msg void CMyDialog::OnOK()

    {

    // Получаем адреса объектов

    CButton *pcb1 = (CButton *) (this->GetDlgItem(IDC_CHECK1));

    CButton *pcb2 = (CButton *) (this->GetDlgItem(IDC_CHECK2));

    // Считываем состояния переключателей

    cb1status = pcb1->GetCheck();

    cb2status = pcb2->GetCheck();

    // Печатаем в окне информацию о состоянии переключателей

    CString str;

    str.Format("Состояние первого переключателя %i, второго %i.",



    cb1status, cb2status);

    ((CMainWin *) (this->GetOwner()))->SetInfoStr(str);

    this->EndDialog(0);

    }

    BEGIN_MESSAGE_MAP(CMyDialog, CDialog)

    // Обработка кликов на певом (неавтоматическом) переключателе

    ON_BN_CLICKED(IDC_CHECK1, OnCheckbox1)

    END_MESSAGE_MAP()

    Рис. 10. Приложение с контрольными переключателями



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



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

    Если элементу присвоен идентификатор IDC_STATIC (-1), то он не будет принимать и генерировать сообщений. Но вообще статические элементы управления могут генерировать и принимать сообщения. Для этого элементу нужно присвоить другой идентификатор. Тогда элемент уже не будет статическим. Это часто используется. Например, можно поменять текст в текстовой строке с помощью функции SetWindowText(), чтобы отобразить некоторую информацию. Мы будем использовать это в примере, демонстрирующем работу с полосами прокрутки.



    Радиокнопки



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

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



    Радиокнопки управляются с помощью класса CButton. Также, как для контрольных переключателей, состояние радиокнопок можно изменять с помощью функции SetCheck()

    и читать с помощью функции GetCheck().

    При создании диалога все радиокнопки сброшены. Таким образом, в функции OnInitDialog()

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

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



    Radio.hpp



    #include "stdafx.h"

    #include "resource.h"

    class CMainWin: public CFrameWnd {

    public:

    CMainWin(CString Title, int HowShow);

    afx_msg void OnCommand_Dialog();

    afx_msg void OnCommand_Status();

    afx_msg void OnCommand_Exit();

    afx_msg void OnPaint();

    virtual void SetInfoStr(CString str);

    private:

    CString infoStr;

    DECLARE_MESSAGE_MAP()

    };

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };

    // Класс диалога с радиокнопками и статическими элементами управления

    class CMyDialog: public CDialog {

    public:

    CMyDialog(char *DialogName, CWnd *Owner);

    BOOL OnInitDialog();

    afx_msg void OnOK();

    private:

    DECLARE_MESSAGE_MAP()

    };



    Radio.cpp



    #include "stdafx.h"

    #include "radio.hpp"

    #include "resource.h"

    // Переменные, хранящие состояния радиокнопок

    // и контрольных переключателей

    int cb1status = 0, cb2status = 0;

    int rb1status = 1, rb2status = 0;

    CApp App;

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    CMainWin::CMainWin(CString Title, int HowShow)

    :infoStr("")

    {

    RECT rect;

    rect.top = 10; rect.left = 50; rect.bottom = 460; rect.right = 630;

    this->Create(0, Title, WS_OVERLAPPEDWINDOW, rect, 0, "MENU");

    this->ShowWindow(HowShow);

    this->UpdateWindow();

    this->LoadAccelTable("MENU");



    }

    afx_msg void CMainWin::OnCommand_Dialog()

    {

    CMyDialog dialog("DIALOG", this);

    dialog.DoModal();

    }

    // Вывод окна состояния

    afx_msg void CMainWin::OnCommand_Status()

    {

    CString str;

    if(cb1status != 0)

    str = "Checkbox1 выбран.\n";

    else

    str = "Checkbox1 очищен.\n";

    if(cb2status != 0)

    str = str + "Checkbox2 выбран.\n";

    else

    str = str + "Checkbox2 очищен.\n";

    if(rb1status != 0)

    str = str + "Radio1 выбран.\n";

    else

    str = str + "Radio1 очищен.\n";

    if(rb2status != 0)

    str = str + "Radio2 выбран.";

    else

    str = str + "Radio2 очищен.";

    this->MessageBox(str, "Состояние");

    }

    afx_msg void CMainWin::OnCommand_Exit()

    {

    this->SendMessage(WM_CLOSE);

    }

    afx_msg void CMainWin::OnPaint()

    {

    CPaintDC paintDC(this);

    paintDC.TextOut(10, 10, infoStr, infoStr.GetLength());

    }

    void CMainWin::SetInfoStr(CString str)

    {

    infoStr = str;

    this->InvalidateRect(0);

    }

    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd)

    ON_COMMAND(IDM_DIALOG, OnCommand_Dialog)

    ON_COMMAND(IDM_STATUS, OnCommand_Status)

    ON_COMMAND(IDM_EXIT, OnCommand_Exit)

    ON_WM_PAINT()

    END_MESSAGE_MAP()

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    BOOL CApp::InitInstance()

    {

    m_pMainWnd = new CMainWin("Радиокнопки",

    SW_RESTORE);

    return TRUE;

    }

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    CMyDialog::CMyDialog(char *DialogName, CWnd *Owner)

    :CDialog(DialogName, Owner)

    {

    }

    BOOL CMyDialog::OnInitDialog()

    {

    CDialog::OnInitDialog();

    CButton *pcb1 = (CButton *) (this->GetDlgItem(IDC_CHECK1));

    CButton *pcb2 = (CButton *) (this->GetDlgItem(IDC_CHECK2));

    pcb1->SetCheck(cb1status);

    pcb2->SetCheck(cb2status);

    // Получаем адреса радиокнопок

    CButton *prb1 = (CButton *) (this->GetDlgItem(IDC_RADIO1));

    CButton *prb2 = (CButton *) (this->GetDlgItem(IDC_RADIO2));

    // Устанавливаем состояние радиокнопок

    prb1->SetCheck(rb1status);



    prb2->SetCheck(rb2status);

    return TRUE;

    }

    // Здесь читаем состояние кнопок и запоминаем его в переменных

    afx_msg void CMyDialog::OnOK()

    {

    CButton *pcb1 = (CButton *) (this->GetDlgItem(IDC_CHECK1));

    CButton *pcb2 = (CButton *) (this->GetDlgItem(IDC_CHECK2));

    cb1status = pcb1->GetCheck();

    cb2status = pcb2->GetCheck();

    // Получем адреса радиокнопок

    CButton *prb1 = (CButton *) (this->GetDlgItem(IDC_RADIO1));

    CButton *prb2 = (CButton *) (this->GetDlgItem(IDC_RADIO2));

    // Запоминаем состояние радиокнопок

    rb1status = prb1->GetCheck();

    rb2status = prb2->GetCheck();

    // Выводим состояние в окно

    CString str;

    str.Format("Check1: %i, Check2: %i, "

    "Radio1: %i, Radio2: %i.",

    cb1status, cb2status, rb1status, rb2status);

    ((CMainWin *) (this->GetOwner()))->SetInfoStr(str);

    this->EndDialog(IDOK);

    }

    BEGIN_MESSAGE_MAP(CMyDialog, CDialog)

    END_MESSAGE_MAP()

    Рис. 11. Пример приложения с радиокнопками



    Полосы прокрутки



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

    Элементы первого типа описываются классом CWnd, а второго - CScrollBar. Работа с независимыми полосами прокрутки требует чуть больше усилий. Мы рассмотрим здесь оба типа.



    Создание стандартных полос прокрутки



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



    Независимые полосы прокрутки в диалогах



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


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



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



    Так как полоса прокрутки пришла из 16-разрядной Windows 3.1, управлять полосой прокрутки довольно сложно. Во всяком случае, полоса прокрутки сама ничего не делает. Даже для того, чтобы она "прокручивалась" на экране, необходим дополнительный программный код.

    Полосы прокрутки при выполнении над ними действий посылают сообщения WM_VSCROLL и WM_HSCROLL при активизации соответственно вертикальной или горизонтальной полосы прокрутки. Эти сообщения обрабатываются функциями со следующими прототипами:

    afx_msg void CWnd::OnVScroll(UINT SBCode, int Pos, CScrollBar *SB);

    afx_msg void CWnd::OnHScroll(UINT SBCode, int Pos, CScrollBar *SB);

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

    Если работа ведется с вертикальной полосой прокрутки, то при каждом изменении положения ползунка на одну позицию вверх посылается код SB_LINEUP. При изменении позиции на одну вниз посылается код SB_LINEDOWN. Аналогично, при постраничном перемещении генерируются коды SB_PAGEUP и SB_PAGEDOWN.

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

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


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

    Если сообщение сгенерировано стандартной полосой прокрутки, то параметр SB будет равен 0. Если же оно было сгенерировано независимой полосой прокрутки, то этот параметр будет содержать указатель на объект. Это предоставляет весьма неуклюжий способ различать, какая конкретно независимая полоса прокрутки сгенерировала сообщение. Для этого нужно использовать функцию CWnd:: GetDlgCtrlID(), которая возвращает идентификатор элемента управления. Такое неудобство связано с тем, что MFC повторяет внутреннее устройство Windows, а не является библиотекой сверхвысокого уровня для быстрой разработки приложений.



    Управление полосой прокрутки



    Раньше для установки различных параметров полосы прокрутки использовались отдельные функции, которые были в Windows 3.1. С появлением Windows 95 появилась возможность управления полосами прокрутки с помощью одной функции SetScrollInfo(). Только эта функция позволяет сделать полосу прокрутки пропорциональной (в этом случае, чем меньше диапазон полосы прокрутки, тем длиннее будет ее ползунок). Функция GetScrollInfo() предназначена для чтения параметров полосы прокрутки. В отличие от старых функций, эти функции работают с 32-разрядными данными. Поэтому мы будем использовать только новые функции.

    Для стандартных полос прокрутки используется функция:

    BOOL CWnd::SetScrollInfo(int Which, LPSCROLLINFO pSI,

    BOOL Redraw = TRUE);

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

    Для независимых полос прокрутки используется функция:

    BOOL CScrollBar::SetScrollInfo(LPSCROLLINFO pSI, BOOL Redraw = TRUE);

    Оба параметра имеют такой же смысл.

    Для чтения параметров стандартных полос прокрутки используется функция:



    BOOL CWnd::GetScrollInfo(int Which, LPSCROLLINFO pSI,

    UINT Mask = SIF_ALL);

    Информация, получаемая от полосы прокрутки, записывается в структуру по адресу pSI. Значение параметра Mask определяет, какая информация записывается в структуру. По умолчанию заполняются все поля.

    Для независимых полос прокрутки вариант функции таков:

    BOOL CScrollBar::SetScrollInfo(LPSCROLLINFO pSI, UINT Mask = SIF_ALL);

    Значение параметров аналогично предыдущему случаю.

    Во всех вариантах функций используется следующая структура типа SCROLLINFO:

    typedef struct tagSCROLLINFO {

    UINT cbSize; // размер самой структуры

    UINT fMask; // маска для параметров

    int nMin; // нижняя граница диапазона

    int nMax; // верхняя граница диапазона

    UINT nPage; // размер страницы

    int nPos; // позиция ползунка

    int nTrackPos; // позиция ползунка во время перемещения

    } SCROLLINFO;

    Поле fMask определяет, какое из полей структуры содержит требуемую информацию. Используются константы с префиксом SIF_, которые можно объединять операцией "|". Поле nPos содержит статическую позицию ползунка. Поле nPage содержит размер страницы для пропорциональных полос прокрутки. Для получения обычной пропорциональной полосы прокрутки в этом поле нужно задать значение 1. Поля nMin и

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

    Пример программы с пропорциональными полосами прокрутки



    Scrollbars.hpp



    #include "stdafx.h"

    class CMainWin: public CFrameWnd {

    public:

    CMainWin();

    afx_msg void OnDialog();

    afx_msg void OnExit();

    // Обработчики сообщений стандартных полос прокрутки

    afx_msg void OnVScroll(UINT SBCode, UINT Pos, CScrollBar *SB);

    afx_msg void OnHScroll(UINT SBCode, UINT Pos, CScrollBar *SB);

    DECLARE_MESSAGE_MAP()

    };

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };

    class CSampleDialog: public CDialog {

    public:

    CSampleDialog(char *DialogName, CWnd *Owner = 0)



    :CDialog(DialogName, Owner) { }

    BOOL OnInitDialog();

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

    // в диалоге

    afx_msg void OnVScroll(UINT SBCode, UINT Pos, CScrollBar *SB);

    DECLARE_MESSAGE_MAP()

    };



    Scrollbars.cpp



    #include "stdafx.h"

    #include "Scrollbars.hpp"

    #include "resource.h"

    // Диапазон значений для полос прокрутки

    const int RANGEMAX = 10;

    int ctlsbpos = 0; // Текущее положение независимой полосы прокрутки диалога

    int vsbpos = 0; // То же, для вертикальной полосы прокрутки окна

    int hsbpos = 0; // То же, для горизонтальной полосы прокрутки окна

    CMainWin::CMainWin()

    {

    Create(0, "Полосы прокрутки (32-битные функции)",

    WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,

    rectDefault, 0, "MENU");

    LoadAccelTable("MENU");

    // Устанавливаем свойства пропорциональной полосы прокрутки

    SCROLLINFO si;

    si.cbSize = sizeof(si);

    si.fMask = SIF_RANGE | SIF_PAGE;

    si.nMin = 0;

    si.nMax = RANGEMAX;

    si.nPage = 1;

    this->SetScrollInfo(SB_VERT, &si);

    this->SetScrollInfo(SB_HORZ, &si);

    }

    BOOL CApp::InitInstance()

    {

    m_pMainWnd = new CMainWin;

    m_pMainWnd->ShowWindow(m_nCmdShow);

    m_pMainWnd->UpdateWindow();

    return TRUE;

    }

    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd)

    ON_COMMAND(IDM_DIALOG, OnDialog)

    ON_COMMAND(IDM_EXIT, OnExit)

    ON_WM_VSCROLL()

    ON_WM_HSCROLL()

    END_MESSAGE_MAP()

    afx_msg void CMainWin::OnDialog()

    {

    CSampleDialog dlg("SAMPLEDIALOG");

    dlg.DoModal();

    }

    afx_msg void CMainWin::OnExit()

    {

    SendMessage(WM_CLOSE);

    }

    // Оработчик сообщений вертикальной полосы прокрутки окна

    afx_msg void CMainWin::OnVScroll(UINT SBCode, UINT Pos, CScrollBar *SB)

    {

    // Анализируем коды полосы прокрутки

    if(SBCode == SB_LINEDOWN) {

    ++vsbpos;

    if(vsbpos > RANGEMAX)

    vsbpos = RANGEMAX;

    }

    if(SBCode == SB_LINEUP) {

    --vsbpos;

    if(vsbpos < 0)

    vsbpos = 0;

    }

    if(SBCode == SB_THUMBPOSITION) {

    vsbpos = Pos;

    }

    if(SBCode == SB_THUMBTRACK) {

    vsbpos = Pos;



    }

    if(SBCode == SB_PAGEDOWN) {

    vsbpos += 5;

    if(vsbpos > RANGEMAX)

    vsbpos = RANGEMAX;

    }

    if(SBCode == SB_PAGEUP) {

    vsbpos -= 5;

    if(vsbpos < 0)

    vsbpos = 0;

    }

    // Устанавливаем новое состояние

    SCROLLINFO si;

    si.cbSize = sizeof(si);

    si.fMask = SIF_POS;

    si.nPos = vsbpos;

    this->SetScrollInfo(SB_VERT, &si);

    // Выводим состояние полосы прокрутки в окно.

    // Это только пример, так не следует делать в реальных программах!

    char str[255];

    CClientDC dc(this);

    wsprintf(str, "Vertical Scroll Bar: %i ", vsbpos);

    dc.TextOut(2, 2, str);

    }

    // Оработчик сообщений горизонтальной полосы прокрутки окна

    afx_msg void CMainWin::OnHScroll(UINT SBCode, UINT Pos, CScrollBar *SB)

    {

    // Анализируем коды полосы прокрутки

    if(SBCode == SB_LINERIGHT) {

    ++hsbpos;

    if(hsbpos > RANGEMAX)

    hsbpos = RANGEMAX;

    }

    if(SBCode == SB_LINELEFT) {

    --hsbpos;

    if(hsbpos < 0)

    hsbpos = 0;

    }

    if(SBCode == SB_THUMBPOSITION) {

    hsbpos = Pos;

    }

    if(SBCode == SB_THUMBTRACK) {

    hsbpos = Pos;

    }

    if(SBCode == SB_PAGERIGHT) {

    hsbpos += 5;

    if(hsbpos > RANGEMAX)

    hsbpos = RANGEMAX;

    }

    if(SBCode == SB_PAGELEFT) {

    hsbpos -= 5;

    if(hsbpos < 0)

    hsbpos = 0;

    }

    // Устанавливаем новое состояние

    SCROLLINFO si;

    si.cbSize = sizeof(si);

    si.fMask = SIF_POS;

    si.nPos = hsbpos;

    this->SetScrollInfo(SB_HORZ, &si);

    // Выводим состояние полосы прокрутки в окно.

    // Это только пример, так не следует делать в реальных программах!

    char str[255];

    CClientDC dc(this);

    wsprintf(str, "Horizontal Scroll Bar: %i ", hsbpos);

    dc.TextOut(200, 2, str);

    }

    BEGIN_MESSAGE_MAP(CSampleDialog, CDialog)

    ON_WM_VSCROLL()

    END_MESSAGE_MAP()

    BOOL CSampleDialog::OnInitDialog()

    {

    CDialog::OnInitDialog();

    // Получаем адрес объекта элемента управления

    CScrollBar *SB = (CScrollBar *) GetDlgItem(IDC_SCROLLBAR);

    // Устанавливаем свойства полосы прокрутки

    SCROLLINFO si;

    si.cbSize = sizeof(si);

    si.fMask = SIF_RANGE | SIF_PAGE;

    si.nMin = 0;

    si.nMax = RANGEMAX;

    si.nPage = 1;



    SB->SetScrollInfo(&si);

    ctlsbpos = 0;

    // Меняем текст в элементе управления "статический текст"

    CStatic *staticText = (CStatic *) (this->GetDlgItem(IDC_ST_SCRLINF));

    char s[255];

    wsprintf(s, "%d", SB->GetScrollPos());

    staticText->SetWindowText(s);

    return TRUE;

    }

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

    afx_msg void CSampleDialog::OnVScroll(UINT SBCode, UINT Pos, CScrollBar *SB)

    {

    // Анализируем коды полосы прокрутки

    if(SBCode == SB_LINEDOWN) {

    ++ctlsbpos;

    if(ctlsbpos > RANGEMAX)

    ctlsbpos = RANGEMAX;

    }

    if(SBCode == SB_LINEUP) {

    --ctlsbpos;

    if(ctlsbpos < 0)

    ctlsbpos = 0;

    }

    if(SBCode == SB_THUMBPOSITION) {

    ctlsbpos = Pos;

    }

    if(SBCode == SB_THUMBTRACK) {

    ctlsbpos = Pos;

    }

    if(SBCode == SB_PAGEDOWN) {

    ctlsbpos += 5;

    if(ctlsbpos > RANGEMAX)

    ctlsbpos = RANGEMAX;

    }

    if(SBCode == SB_PAGEUP) {

    ctlsbpos -= 5;

    if(ctlsbpos < 0)

    ctlsbpos = 0;

    }

    // Устанавливаем новое состояние

    SCROLLINFO si;

    si.cbSize = sizeof(si);

    si.fMask = SIF_POS;

    si.nPos = ctlsbpos;

    SB->SetScrollInfo(&si);

    // Выводим значение текущей позиции

    // в элемент статического текста

    char str[255];

    wsprintf(str, "%i", SB->GetScrollPos());

    CStatic *staticText = (CStatic *) (this->GetDlgItem(IDC_ST_SCRLINF));

    staticText->SetWindowText(str);

    }

    CApp App;

    Рис. 12. Приложение с использованием пропорциональных полос прокрутки

    Обратите внимание, как в программе используются новые 32-разрядные функции работы с полосами прокрутки. Помните, что эта программа не будет работать под Windows NT 3.51. Также посмотрите, как изменяется текст в элементе управления "статический текст", который имеет уникальный идентификатор.



    Иконки и курсоры



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


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



    Создание иконки и курсора



    Для создания иконки и курсора нужно использовать ресурсный редактор. Иконки сохраняются в файлах с расширением ico, а курсоры - в файлах с расширением cur.

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

    Иконки могут иметь размер 16х16, 32х32 и 48х48. Последний размер обычно не используется. Иконки могут иметь 16 или 256 цветов. При использовании ресурсного редактора среды Visual C++ 4.0 и выше иконки всех размеров можно хранить в одном файле, что обычно и делается. Для современных приложений обязательно наличие иконок размером как 16х16, так и 32х32.



    Загрузка иконки и курсора из ресурсов



    К сожалению, на текущий момент MFC плохо поддерживает этот процесс. Поэтому иногда удобнее пользоваться функциями Windows API. Перед рассмотрением функций следует рассмотреть понятие дескриптора. Дескриптор - это 32-разрядное беззнаковое целое значение, которое идентифицирует объект в Windows. При традиционном SDK-программировании дескрипторы используются очень широко. Например, свои дескрипторы имеют иконки, курсоры и само приложение. Для дескрипторов каждого объекта существует свой тип, например, HICON, HCURSOR, HINSTANCE.

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


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

    Будем предполагать, что в декларации класса основного окна (теперь уже идет речь о классе С++) объявлены переменные m_hIconSmall, m_hIconBig, m_hCursor - соответственно дескрипторы иконки 16х16, иконки 32х32 и курсора. Тогда для загрузки иконок и курсора нужно выполнить следующий код:

    // Получить дескриптор модуля (приложения)

    HINSTANCE hInst = AfxGetInstanceHandle();

    // Загрузить иконку 16х16

    m_hIconSmall = (HICON) ::LoadImage(hInst, MAKEINTRESOURCE(IDI_ICON),

    IMAGE_ICON, 16, 16,

    LR_DEFAULTCOLOR);

    // Загрузить иконку 32x32

    m_hIconBig = (HICON) ::LoadImage(hInst, MAKEINTRESOURCE(IDI_ICON),

    IMAGE_ICON, 32, 32,

    LR_DEFAULTCOLOR);

    // Загрузить курсор

    m_hCursor = AfxGetApp()->LoadCursor(IDC_CURSOR);

    Сначала мы получаем дескриптор модуля с помощью глобальной функции MFC AfxGetInstanceHandle(). Затем мы загружаем маленькую и большую иконки с помощью функции Windows API LoadImage(), и получаем их дескрипторы. Эта функция очень мощная, и позволяет загружать изображения из ресурсов и из файлов. И наконец, мы загружаем курсор уже с помощью функции MFC LoadCursor(), члена класса CWinApp. Функция AfxGetApp() возвращает адрес объекта приложения.



    Изменение иконки и курсора окна



    После того, как мы получили дескрипторы иконок и курсора, нужно изменить класс окна. (Класс окна нужно изменять для установки курсора, а для установки иконок подойдут и функции MFC.) Для этого используется API-функция SetClassLong(), которая изменяет атрибут класса окна (здесь опять имеется в виду структура данных). Первый параметр этой функции - дескриптор окна. Дескриптор окна хранится в члене класса MFC CWnd под названием m_hWnd. Если главным окном приложения является диалог, то для изменения иконки и курсора потребуется следующий код (он будет работоспособен и для обычных окон):

    // Устанавливаем курсор для диалогового окна.



    // Так как MFC не поддерживает динамическое изменение курсоров окна,

    // делаем это с помощью API, модифицируя оконный класс.

    SetClassLong(m_hWnd, GCL_HCURSOR, (long) m_hCursor);

    // Делаем то же самое для всех элементов управления

    for(int i = 0; i < 0xDFFF; ++i) {

    CWnd *pCtrl = this->GetDlgItem(i);

    if(pCtrl != 0)

    SetClassLong(pCtrl->m_hWnd, GCL_HCURSOR, (long) m_hCursor);

    }

    // Устанавливаем иконки. Здесь уже можно использовать MFC.

    this->SetIcon(m_hIconBig, TRUE);

    this->SetIcon(m_hIconSmall, FALSE);

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

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

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

    CMainFrame::CMainFrame()

    :CDialog(IDD_MYDIALOG)

    {

    ... здесь тело конструктора

    }

    И в-третьих, в функции CApp::InitInstance()

    должен присутствовать следующий код:

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

    CMainFrame dlgWnd;

    // Cообщаем MFC адрес окна

    m_pMainWnd = &dlgWnd;

    // Отображаем модальный диалог

    dlgWnd.DoModal();

    // Возвратим FALSE, чтобы MFC не пыталась инициировать

    // очередь сообщений главного окна.

    return FALSE;

    Мы отображаем модальный диалог. Так как при завершении функции DoModal() нам уже не нужна очередь сообщений, мы "обманываем" MFC, делая вид, что инициализация прошла неудачно. На самом же деле просто уже пора завершать приложение.



    Пример программы



    CUR_ICO.hpp



    #include "STDAFX.H"

    // Класс диалогового окна, главного окна приложения

    class CMainFrame: public CDialog {

    public:

    CMainFrame();

    private:

    enum {IDD = IDD_MAINFRAME};

    BOOL OnInitDialog();

    // Внутренние дескрипторы иконки и курсора

    HICON m_hIconSmall, m_hIconBig;

    HCURSOR m_hCursor;

    DECLARE_MESSAGE_MAP()

    };

    // Класс приложения

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };



    CUR_ICO.cpp



    #include "STDAFX.H"

    #include <string.h>

    #include "resource.h"

    #include "CUR_ICO.HPP"

    CApp App;

    BOOL CApp::InitInstance()

    {

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

    CMainFrame dlgWnd;

    // Cообщаем MFC адрес окна

    m_pMainWnd = &dlgWnd;

    // Отображаем модальный диалог

    int responce = dlgWnd.DoModal();

    // Возвратим FALSE, чтобы MFC не пыталась инициировать

    // очередь сообщений главного окна.

    return FALSE;

    }

    BEGIN_MESSAGE_MAP(CMainFrame, CDialog)

    ON_WM_DESTROY()

    END_MESSAGE_MAP()

    CMainFrame::CMainFrame()

    :CDialog(CMainFrame::IDD)

    {

    // Загружаем из ресурсов иконку и курсор

    // MFC плохо поддерживает процесс динамического изменения иконок,

    // поэтому используем функции Windows API

    HINSTANCE hInst = AfxGetInstanceHandle(); // Получить дескриптор модуля

    m_hIconSmall = (HICON) ::LoadImage(hInst, MAKEINTRESOURCE(IDI_ICON),

    IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR); //Иконка 16x16

    m_hIconBig = (HICON) ::LoadImage(hInst, MAKEINTRESOURCE(IDI_ICON),

    IMAGE_ICON, 32, 32, LR_DEFAULTCOLOR); //Иконка 32x32

    m_hCursor = AfxGetApp()->LoadCursor(IDC_CURSOR);

    }

    BOOL CMainFrame::OnInitDialog()

    {

    // Устанавливаем курсор для диалогового окна.

    // Так как MFC не поддерживает динамическое изменение курсоров окна,

    // делаем это с помощью API, модифицируя оконный класс.

    SetClassLong(m_hWnd, GCL_HCURSOR, (long) m_hCursor);

    // Делаем то же самое для всех элементов управления

    for(int i = 0; i < 0xDFFF; ++i) {

    CWnd *pCtrl = this->GetDlgItem(i);

    if(pCtrl != 0)

    SetClassLong(pCtrl->m_hWnd, GCL_HCURSOR, (long) m_hCursor);



    }

    this->SetIcon(m_hIconBig, TRUE);

    this->SetIcon(m_hIconSmall, FALSE);

    return TRUE;

    }

    Рис. 13. Использование собственных иконки и курсора в диалоге

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



    Стандартные иконки и курсоры



    Часто в программах нужно использовать курсоры и иконки, уже предопределенные в Windows. Для этого можно использовать или функцию API LoadImage(), или функции MFC LoadStandardIcon() и LoadStandardCursor(). Работа со стандартными курсорами и иконками почти ничем не отличается от работы с пользовательскими. Для стандартных иконок есть предопределенные идентификаторы с префиксом IDI_, а для стандартных курсоров - с префиксом IDC_. Например, стандартный курсор в виде "песочных часов" имеет идентификатор IDC_WAIT.



    Битовые образы



    Битовые образы - очень важная часть Windows. При хранении битовых образов в отдельном файле обычно используется расширение BMP (это единственный растровый формат, который напрямую поддерживается Windows). Часто они хранятся и в ресурсах. Битовые образы используются шире, чем все остальные ресурсы. Это объясняется наличием для них чрезвычайно мощной поддержки. Если Вы имеете большой опыт программирования только под DOS, то будете удивлены, узнав, что в Windows многие вещи, которые можно легко нарисовать программно, отображаются с помощью готовых битовых образов. Например, кнопки в нажатом и отпущенном состоянии, каркасы для целых окон. Так как компьютеры теперь имеют большие жесткие диски, то выбор между программным рисованием объекта и готовой картинкой часто однозначно решается в пользу последней.



    Создание битовых образов



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


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



    Вывод битового образа на экран



    Когда битовый образ помещен в ресурсы, его можно выводить на экран. Но этот процесс не такой уж и простой.

    Сначала необходимо создать объект типа CBitmap и с помощью функции LoadBitmap() загрузить в него битовый образ из ресурсов. Прототип функции таков:

    BOOL CBitmap::LoadBitmap(LPCSTR ResourceName);

    Параметр определяет строковый идентификатор ресурса.

    После загрузки битового образа его нужно вывести в клиентскую область окна. Для этого обработчик WM_PAINT должен содержать приблизительно такой код (предполагается, что битовый образ загружен в объект backgroundBitmap):

    CPaintDC clientDC(this);

    CDC memDC; // Контекст памяти

    // Создать совместимый контекст памяти

    memDC.CreateCompatibleDC(&clientDC);

    // Выбрать битовый образ в контекст устройства

    memDC.SelectObject(&backgroundBitmap);

    // Получить характеристики битового образа в структуру BITMAP

    BITMAP bmp;

    backgroundBitmap.GetBitmap(&bmp);

    // Скопировать битовый образ из контекста памяти в контекст

    // клиентской области окна

    clientDC.BitBlt(0, 0, bmp.bmWidth, bmp.bmHeight, &memDC,

    0, 0, SRCCOPY);

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

    virtual BOOL CDC::CreateCompatibleDC(CDC *pDC);

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

    CBitmap *CDC::SelectObject(CBitmap *pBmp);



    Параметр - это указатель на объект битового образа.

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

    BOOL CDC::BitBlt(int x, int y, int Width, int Height,

    CDC *pSourceDC, int SourceX, int SourceY,

    DWORD RasterOpCode);

    Первые два параметра задают координаты начальной точки изображения. Размеры изображения задают следующие два параметра. Параметр pSourceDC является указателем на исходный контекст устройства. Координаты SourceX и SourceY задают левый верхний угол изображения и обычно равны 0. Последний параметр задает код операции, которая будет проделана при передаче изображения из одного контекста в другой. Мы будем использовать только значение SRCCOPY, в этом случае изображение просто копируется. Существует также много других констант.

    Следует отметить, что указанным методом нельзя корректно выводить битовые образы более чем с 16 цветами в видеорежимах с 256 цветами. В режимах же HiColor и TrueColor без всяких проблем этим методом выводятся любые битовые образы. Так как на всех современных компьютерах используются по крайней мере HiColor режимы, мы не будем рассматривать ограничения худших режимов и манипуляции с палитрой. На сегодняшний день этот материал устарел.



    Получение системных метрик



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

    int GetSystemMetrics(int Index);

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

    SM_CXDLGFRAME Ширина бордюра окна без выпуклой рамки (плоского диалогового окна)
    SM_CYDLGFRAME Высота бордюра окна без выпуклой рамки (плоского диалогового окна)
    SM_CXEDGE Ширина трехмерной выпуклой рамки окна
    SM_CYEDGE Высота трехмерной выпуклой рамки окна
    SM_CYCAPTION Высота заголовка окна
    <


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

    Пример программы



    bmpshow.hpp



    #include "STDAFX.H"

    class CMainWin: public CFrameWnd {

    public:

    CMainWin(CString Title, int HowShow = SW_RESTORE);

    afx_msg void OnPaint();

    private:

    // Объект битового образа

    CBitmap backgroundBitmap;

    DECLARE_MESSAGE_MAP()

    };

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };



    bmpshow.cpp



    #include "STDAFX.H"

    #include <string.h>

    #include "resource.h"

    #include "BMPShow.HPP"

    CApp App;

    BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd)

    ON_WM_PAINT()

    END_MESSAGE_MAP()

    CMainWin::CMainWin(CString Title, int HowShow)

    {

    backgroundBitmap.LoadBitmap("BACKGROUND_BITMAP");

    // Прямоугольник, в котором должно разместиться окно.

    RECT rect;

    rect.top = 0;

    rect.left = 0;

    BITMAP bmp;

    backgroundBitmap.GetBitmap(&bmp);

    // Определяем, чему должны быть равны размеры окна,

    // если размеры клиентской области должены быть равны размерам картинки.

    rect.right = rect.left + bmp.bmWidth

    + (GetSystemMetrics(SM_CXDLGFRAME) + GetSystemMetrics(SM_CXEDGE)) * 2;

    rect.bottom = rect.top + bmp.bmHeight + GetSystemMetrics(SM_CYCAPTION) +

    + (GetSystemMetrics(SM_CYDLGFRAME) + GetSystemMetrics(SM_CYEDGE)) * 2;

    this->Create(0, Title,

    WS_OVERLAPPED | WS_CAPTION | WS_DLGFRAME

    | WS_SYSMENU | WS_MINIMIZEBOX, rect, 0, 0);

    // Если не задана позиция окна, то центрируем его

    if(x0 < 0 || y0 < 0)

    this->CenterWindow();

    this->ShowWindow(HowShow);

    this->UpdateWindow();

    }

    afx_msg void CMainWin::OnPaint()

    {

    CPaintDC clientDC(this);

    CDC memDC; // Контекст памяти

    // Создать совместимый контекст памяти

    memDC.CreateCompatibleDC(&clientDC);

    // Выбрать битовый образ в контекст устройства

    memDC.SelectObject(&backgroundBitmap);

    // Получить характеристики битового образа в структуру BITMAP

    BITMAP bmp;

    backgroundBitmap.GetBitmap(&bmp);

    // Скопировать битовый образ из контекста памяти в контекст



    // клиентской области окна

    clientDC.BitBlt(0, 0, bmp.bmWidth, bmp.bmHeight, &memDC, 0, 0, SRCCOPY);

    }

    BOOL CApp::InitInstance()

    {

    m_pMainWnd = new CMainWin("Вывод битового образа", SW_RESTORE);

    return TRUE;

    }

    Рис. 14. Пример программы, выводящей битовый образ из ресурсов

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



    Вывод текста и шрифты



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



    Небольшое введение



    Любой шрифт, с которым мы имеем дело в Windows, характеризуется несколькими параметрами. Гарнитура (typeface) - это совокупность нескольких начертаний шрифта, объединенных стилевыми и другими признаками. Пример гарнитур: Arial, Times New Roman, MS Sans Serif. Размер шрифта - это высота прямоугольника, в который помещаются все символы шрифта, выражается в специальных единицах - пунктах. Пункт равен 1/72 части дюйма. Эта единица пришла из полиграфии. Начертание - это специфические характеристики шрифта. В Windows доступны четыре начертания: нормальное (normal), курсивное (italic), жирное (bold) и жирное курсивное (bold italic). Кроме того, шрифты могут быть моноширинные (fixed pitch), пример - Courier New, и пропорциональные (variable pitch), пример - Times New Roman.

    Сейчас в Windows в основном используются шрифты двух групп: растровые

    (примеры - MS Sans Serif, Fixedsys) и контурные TrueType (примеры - Arial, Courier New).


    Первые представляют собой жестко определенные битовые матрицы для каждого символа и предназначены для отображения не очень крупного текста на экране. Вторые представляют собой очень сложные объекты. В них заданы контуры символов, которые закрашиваются по определенным правилам. Каждый шрифт TrueType - это программа на специальном языке, которая выполняется интерпретатором под названием растеризатор. Программа шрифта полностью определяет способ расчета конкретных битовых матриц символов на основе контуров. При использовании символов маленького размера (высотой приблизительно до 30 точек) модель контуров становится некорректной и символы сильно искажаются. Для борьбы с этим в качественных шрифтах используется разметка (хинты). Разметка шрифта - чрезвычайно сложный и долгий процесс, поэтому на рынке встречается немного качественно размеченных шрифтов. Поэтому использовать TrueType шрифты для вывода текста на экран нежелательно, за исключением стандартных шрифтов Windows (Times New Roman, Arial и Courier New), которые очень качественно размечены.



    Координаты при выводе текста



    За вывод текста в окно в MFC отвечает функция CDC::TextOut(). Ей нужно указать координаты для вывода строки. Эти координаты являются логическими

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



    Задание цвета текста и фона



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

    virtual COLORREF CDC::SetTextColor(COLORREF Color);

    virtual COLORREF CDC::SetBkColor(COLORREF Color);

    Функции возвращают значение предыдущего цвета. Тип COLORREF представляет собой 32-разрядное беззнаковое целое число - представление цвета в виде красной, зеленой и синей компонент, каждая размером в 8 бит.


    Для формирования этого значения существует макрос RGB().



    Задание режима отображения фона



    C помощью функции SetBkMode() можно задать режим отображения фона. Прототип функции такой:

    int CDC::SetBkMode(int Mode);

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



    Получение метрик текста



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

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

    BOOL CDC::GetTextMetrics(LPTEXTMETRICS TextAtttrib) const;

    Параметр является указателем на структуру TEXTMETRIC, в которую будут записаны установки текущего шрифта контекста устройства. Структура имеет достаточно много полей. Наиболее важные поля следующие:

    LONG tmHeight Полная высота шрифта
    LONG tmAscent Высота над базовой линией
    LONG tmDescent Высота подстрочных элементов
    LONG tmInternalLeading Пустое пространство над символами
    LONG tmExternalLeading Пустой интервал между строками
    LONG tmMaxCharWidth Максимальная ширина символов
    Для получения числа логических единиц по вертикали между строками нужно сложить значения tmHeight и tmExternalLeading. Это не то же самое, что и высота символов.



    Изменение шрифтов



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


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



    Инициализация объекта шрифта: выбор шрифта



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

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

    BOOL CFont::CreateFont(int nHeight, int nWidth,

    int nEscapement,

    int nOrientation,

    int nWeight,

    BYTE bItalic,

    BYTE bUnderline,

    BYTE cStrikeOut,

    BYTE nCharSet,

    BYTE nOutPrecision,

    BYTE nClipPrecision,

    BYTE nQuality,

    BYTE nPitchAndFamily,

    LPCTSTR lpszFacename);

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

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


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

    Учитывая вышесказанное, мы не будем подробно рассматривать параметры функции CreateFont(). Вместо этого мы рассмотрим пример кода, который позволяет задать размер шрифта, начертание и гарнитуру, минимизировав возможные трансформации шрифта и синтеза при отсутствии заданного. Такие же параметры для функции CreateFont() Вы можете использовать в своих программах. Желательно также проверять наличие шрифта, если он не является стандартным. Вот код, который используется в примере программы (указатель на объект шрифта хранится в переменной m_pFont):

    void CMainFrame::SetClientFont(CString Typeface, // Гарнитура

    int Size, // размер в пунктах

    BOOL Bold, // Признак жирного начертания

    BOOL Italic // Признак наклонного

    // начертания

    )

    {

    // Получим контекст окна

    CWindowDC winDC(this);

    // Узнаем, сколько пикселей в одном логическом дюйме

    int pixelsPerInch = winDC.GetDeviceCaps(LOGPIXELSY);

    // Узнаем высоту в пикселях шрифта размером Size пунктов

    int fontHeight = -MulDiv(Size, pixelsPerInch, 72);

    // Устанавливаем параметр жирности для функции CreateFont()

    int Weight = FW_NORMAL;

    if(Bold)

    Weight = FW_BOLD;

    // Удаляем предыдущий экземпляр шрифта -- нельзя дважды

    // инициализировать шрифт вызовом CreateFont().

    delete m_pFont;

    m_pFont = new CFont;

    // Создание шрифта. Большинство параметров не используются.

    m_pFont->CreateFont(fontHeight, 0, 0, 0, Weight, Italic, 0, 0,

    DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,

    CLIP_DEFAULT_PRECIS, PROOF_QUALITY,

    DEFAULT_PITCH | FF_DONTCARE, Typeface);

    }

    Пример программы

    В этом примере мы поместим декларации и описания объектов приложения и главного окна в разные файлы.


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



    MainFrame.hpp



    #include "stdafx.h"

    class CMainFrame: public CFrameWnd {

    public:

    CMainFrame();

    ~CMainFrame();

    afx_msg void OnLButtonDown(UINT Flags, CPoint Loc);

    afx_msg void OnPaint();

    virtual void SetClientFont(CString Typeface, int Size, BOOL Bold, BOOL Italic);

    private:

    CWinApp *pApp;

    CFont *m_pFont;

    COLORREF m_textColor;

    DECLARE_MESSAGE_MAP();

    };



    MainFrame.cpp



    #include "stdafx.h"

    #include "MainFrame.hpp"

    BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)

    ON_WM_PAINT()

    ON_WM_LBUTTONDOWN()

    END_MESSAGE_MAP()

    CMainFrame::CMainFrame()

    {

    pApp = AfxGetApp();

    this->Create(0, "Вывод текста и шрифты",

    WS_OVERLAPPEDWINDOW, rectDefault, 0, 0);

    m_pFont = new CFont;

    this->SetClientFont("Arial", 20, FALSE, FALSE);

    this->ShowWindow(SW_RESTORE);

    this->UpdateWindow();

    }

    CMainFrame::~CMainFrame()

    {

    delete m_pFont;

    }

    afx_msg void CMainFrame::OnLButtonDown(UINT Flags, CPoint Loc)

    {

    // Получим структуру LOGFONT текущего шрифта, чтобы узнать имя гарнитуры

    LOGFONT lf;

    m_pFont->GetLogFont(&lf);

    // Циклически меняем шрифт

    if(CString(lf.lfFaceName) == "Arial"

    && lf.lfWeight == FW_NORMAL && lf.lfItalic == 0) {

    this->SetClientFont("Arial", 20, FALSE, TRUE); // Arial Italic

    }

    else if(CString(lf.lfFaceName) == "Arial"

    && lf.lfWeight == FW_NORMAL && lf.lfItalic == 1) {

    this->SetClientFont("Arial", 20, TRUE, FALSE); // Arial Bold

    }

    else if(CString(lf.lfFaceName) == "Arial" && lf.lfWeight > FW_NORMAL) {

    // Times New Roman

    this->SetClientFont("Times New Roman", 20, FALSE, FALSE);

    }

    else if(CString(lf.lfFaceName) == "Times New Roman"

    && lf.lfWeight == FW_NORMAL && lf.lfItalic == 0) {

    // Times New Roman Italic

    this->SetClientFont("Times New Roman", 20, FALSE, TRUE);



    }

    else if(CString(lf.lfFaceName) == "Times New Roman"

    && lf.lfWeight == FW_NORMAL && lf.lfItalic == 1) {

    // Times New Roman Bold

    this->SetClientFont("Times New Roman", 20, TRUE, FALSE);

    }

    else if(CString(lf.lfFaceName) == "Times New Roman"

    && lf.lfWeight > FW_NORMAL) {

    this->SetClientFont("Arial", 20, FALSE, FALSE); // Arial

    }

    // Установим случайный RGB-цвет текста

    m_textColor = RGB(MulDiv(rand(), 255, RAND_MAX),

    MulDiv(rand(), 255, RAND_MAX), MulDiv(rand(), 255, RAND_MAX));

    // Выведем информацию в окно новым шрифтом (пошлем сообщение WM_PAINT)

    this->InvalidateRect(0);

    }

    void CMainFrame::SetClientFont( CString Typeface, int Size, BOOL Bold, BOOL Italic)

    {

    // Получим контекст окна

    CWindowDC winDC(this);

    // Узнаем, сколько пикселей в одном логическом дюйме

    int pixelsPerInch = winDC.GetDeviceCaps(LOGPIXELSY);

    // Узнаем высоту в пикселях шрифта размером Size пунктов

    int fontHeight = -MulDiv(Size, pixelsPerInch, 72);

    int Weight = FW_NORMAL;

    if(Bold)

    Weight = FW_BOLD;

    // Удаляем предыдущий экземпляр шрифта -- нельзя дважды

    // инициализировать шрифт вызовом CreateFont().

    delete m_pFont;

    m_pFont = new CFont;

    m_pFont->CreateFont(fontHeight, 0, 0, 0, Weight, Italic, 0, 0,

    DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,

    CLIP_DEFAULT_PRECIS, PROOF_QUALITY,

    DEFAULT_PITCH | FF_DONTCARE, Typeface);

    }

    afx_msg void CMainFrame::OnPaint()

    {

    CPaintDC paintDC(this);

    COLORREF oldColor = paintDC.SetTextColor(m_textColor);

    LOGFONT lf;

    m_pFont->GetLogFont(&lf);

    paintDC.SelectObject(m_pFont);

    TEXTMETRIC tm;

    paintDC.GetTextMetrics(&tm);

    int currY = 2;

    CString s = CString("Font: ") + lf.lfFaceName;

    paintDC.TextOut(2, currY, s);

    currY += (tm.tmHeight + tm.tmExternalLeading);

    CSize size = paintDC.GetTextExtent(s);

    s.Format("Previous string is %i units long", size.cx);

    paintDC.TextOut(2, currY, s);

    paintDC.SetTextColor(oldColor);

    }



    text_and_fonts.hpp





    #include "stdafx.h"

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };



    text_and_fonts.cpp



    #include "stdafx.h"

    #include "Text_And_Fonts.hpp"

    #include "MainFrame.hpp"

    CApp App;

    BOOL CApp::InitInstance()

    {

    m_pMainWnd = new CMainFrame();

    return TRUE;

    }

    Рис. 15. Программа, отображающая в окне стандартные TrueType шрифты

    При нажатии левой кнопки мыши в клиентской области программа циклически меняет шрифт из набора всех начертаний двух стандартных TrueType-шрифтов, Arial и Times New Roman. При этом случайным образом устанавливается цвет символов. Выводится длина первой строки в логических единицах. Попробуйте изменить программу так, чтобы вместо TrueType шрифтов использовались растровые шрифты, например, MS Serif и MS Sans Serif. Посмотрите, что произойдет, если задать несуществующий в системе размер шрифта, или несуществующий шрифт.



    Введение в графические функции. Организация вывода в виртуальное окно



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

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



    Система графических координат



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

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





    Перья



    Графические объекты рисуются с помощью текущего пера. В MFC перья описываются с помощью класса CPen. По умолчанию установлено черное перо толщиной в 1 пиксель. Всего имеется три стандартных пера: черное, белое и нулевое (прозрачное). Конечно, этого недостаточно. Поэтому обычно создаются собственные перья. Для этого нужно вызвать функцию :

    BOOL CPen::CreatePen(int Style, int Width, COLORREF Color);

    Параметр Style определяет стиль созданного пера. Толщина пера в логических единицах задается параметром Width. Цвет пера задается с помощью параметра Color. Класс CPen также имеет перегруженный конструктор с такими же параметрами. Иногда легче использовать его.



    Кисти



    Кисти создаются подобно перьям. Только они описываются классом CBrush. Существуют различные стили кистей. Наиболее распространенным является стиль сплошной кисти. Такие кисти создаются с помощью функции:

    BOOL CBrush::CreateSolidBrush(COLORREF Сolor);

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

    Следующие функции позволяют создать также кисти со штриховкой и кисти по шаблону битового образа:

    BOOL Сbrush::CreateHatchBrush(int Index, COLORREF Color);

    BOOL Сbrush::CreatePatternBrush(CBitmap *pBitmap);

    Параметр Index указывает стиль кисти. Параметр pBitmap - это указатель на объект битового образа. Предварительно он может быть загружен из ресурсов. Битовый образ должен иметь размер 8х8.

    Мы будем использовать все эти функции в примере программы.

    Отображение точки

    Отобразить в контексте устройства точку определенного цвета можно с помощью следующей функции:

    СOLORREF CDC::SetPixel(int X, int Y, COLORREF Color);

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

    Рисование прямоугольников

    Для рисования прямоугольников текущим пером используются функции:



    BOOL CDC::Rectangle( int upX, int upY, int lowX, int lowY);

    BOOL CDC::RoundRect(int upX, int upY, int lowX, int lowY,

    int curveX, int curveY);

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

    Рисование эллипсов

    Для рисования эллипсов текущим пером используется функция:

    BOOL CDC::Ellipse(int upX, int upY, int lowX, int lowY);

    Координаты (upX, upY) и (lowX, lowY) задают левый верхний и правый нижний углы прямоугольника, в который будет вписан эллипс. Если нужно нарисовать окружность, то задается описанный квадрат. Специальной функции для рисования окружностей нет.

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



    Организация виртуального окна



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

    Дополнительные функции

    Для реализации виртуального окна нужно использовать некоторые еще не рассмотренные нами функции MFC.

    Функция с прототипом:

    BOOL CBitmap::CreateCompatibleBitmap(CDC *pDC, int Width, int Height);

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

    Следующая нужная нам функция:

    BOOL CDC::PatBlt(int x, int y, int Width, int Height, DWORD RasterOp);

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


    Последний параметр определяет растровую операцию, аналогично функции BitBlt(). Нам будет нужно значение PATCOPY.

    Создание виртуального окна

    Для этого в примере программы используется следующий код:

    // Поддержка виртуального окна

    // Получим размеры экрана

    maxX = ::GetSystemMetrics(SM_CXSCREEN);

    maxY = ::GetSystemMetrics(SM_CYSCREEN);

    CClientDC dc(this);

    // Создание совместимого контекста устройства и битового образа

    m_memDC.CreateCompatibleDC(&dc);

    m_bmp.CreateCompatibleBitmap(&dc, maxX, maxY);

    m_memDC.SelectObject(&m_bmp);

    // Выбор черной кисти для стирания фона окна и стирание фона

    m_bkbrush.CreateStockObject(BLACK_BRUSH);

    m_memDC.SelectObject(&m_bkbrush);

    m_memDC.PatBlt(0, 0, maxX, maxY, PATCOPY);

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

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

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

    в обработчике выполняется следующий код:

    CPaintDC paintDC(this);

    RECT clientRect;

    this->GetClientRect(&clientRect);

    paintDC.BitBlt(0, 0, clientRect.right, clientRect.bottom,

    &m_memDC, 0, 0, SRCCOPY);

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

    Пример программы, демонстрирующей некоторые графические функции и использование виртуального окна



    MainFrame.hpp



    #include "stdafx.h"

    class CMainFrame: public CFrameWnd {



    public:

    CMainFrame();

    ~CMainFrame();

    // Фигуры рисуются по сообщениям от таймера

    afx_msg void OnTimer(UINT id);

    // Обработчик сообщения WM_ERASEBKGND. Обычно по этому сообщению

    // стирается фон окна. Наш же обработчик ничего не делает, так как

    // в окнах с виртуальным окном перерисовывать фон не нужно.

    afx_msg BOOL OnEraseBkgnd(CDC* pDC);

    afx_msg void OnPaint();

    afx_msg void OnDestroy();

    private:

    CWinApp *pApp;

    CDC m_memDC;

    CBitmap m_bmp;

    CBrush m_bkbrush;

    int maxX, maxY;

    COLORREF m_textColor;

    DECLARE_MESSAGE_MAP();

    };



    MainFrame.cpp



    #include "stdafx.h"

    #include "resource.h"

    #include "MainFrame.hpp"

    BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)

    ON_WM_ERASEBKGND()

    ON_WM_PAINT()

    ON_WM_TIMER()

    ON_WM_DESTROY()

    END_MESSAGE_MAP()

    CMainFrame::CMainFrame()

    {

    pApp = AfxGetApp();

    this->Create(0, "Графика GDI и использование виртуального окна",

    WS_OVERLAPPEDWINDOW, rectDefault, 0, 0);

    // Поддержка виртуального окна

    // Получим размеры экрана

    maxX = ::GetSystemMetrics(SM_CXSCREEN);

    maxY = ::GetSystemMetrics(SM_CYSCREEN);

    CClientDC dc(this);

    // Создание совместимого контекста устройства и битового образа

    m_memDC.CreateCompatibleDC(&dc);

    m_bmp.CreateCompatibleBitmap(&dc, maxX, maxY);

    m_memDC.SelectObject(&m_bmp);

    // Выбор черной кисти для стирания фона окна и стирание фона

    m_bkbrush.CreateStockObject(BLACK_BRUSH);

    m_memDC.SelectObject(&m_bkbrush);

    m_memDC.PatBlt(0, 0, maxX, maxY, PATCOPY);

    this->ShowWindow(SW_RESTORE);

    this->UpdateWindow();

    // Установим таймер с интервалом 0.1 с

    this->SetTimer(1, 100, 0);

    }

    CMainFrame::~CMainFrame()

    {

    }

    afx_msg void CMainFrame::OnTimer(UINT id)

    {

    // Получим прямоугольник клиентской области окна

    RECT clientRect;

    this->GetClientRect(&clientRect);

    //

    // Выбрать в контекст перо по случайному критерию

    //

    // Выбор стиля пера

    int penStyle;

    int random = rand();

    if(random < RAND_MAX / 3) {

    penStyle = PS_SOLID;

    }

    else if(random < RAND_MAX * 2 / 3) {



    penStyle = PS_DOT;

    }

    else {

    penStyle = PS_DASH;

    }

    // Толщина (от 1 до 3);

    int penWidth = MulDiv(rand(), 3, RAND_MAX);

    // Цвет (исключая черный)

    COLORREF penColor = 0;

    while(penColor == 0) {

    penColor = RGB(MulDiv(rand(), 255, RAND_MAX),

    MulDiv(rand(), 255, RAND_MAX),

    MulDiv(rand(), 255, RAND_MAX));

    }

    CPen pen(penStyle, penWidth, penColor);

    m_memDC.SelectObject(pen);

    //

    // Выбрать в контекст кисть по случайному критерию

    //

    CBrush brush;

    // Цвет

    COLORREF brushColor = RGB(MulDiv(rand(), 255, RAND_MAX),

    MulDiv(rand(), 255, RAND_MAX),

    MulDiv(rand(), 255, RAND_MAX));

    // Выбор стиля кисти

    CBitmap brushBitmap;

    brushBitmap.LoadBitmap(IDB_BRUSH);

    random = rand();

    if(random < RAND_MAX / 3) {

    // Сплошная кисть

    brush.CreateSolidBrush(brushColor);

    }

    else if(random < RAND_MAX * 2 / 3) {

    // Кисть с крестообразной штриховкой

    brush.CreateHatchBrush(HS_DIAGCROSS, brushColor);

    }

    else {

    // Кисть по шаблону

    brush.CreatePatternBrush(&brushBitmap);

    }

    m_memDC.SelectObject(brush);

    //

    // Рисуем фигуру

    //

    int x = MulDiv(rand(), clientRect.right, RAND_MAX);

    int y = MulDiv(rand(), clientRect.bottom, RAND_MAX);

    int r1 = MulDiv(rand(), 90, RAND_MAX) + 10;

    int r2 = MulDiv(rand(), 90, RAND_MAX) + 10;

    if(rand() < RAND_MAX / 2) {

    // Рисуем эллипс

    m_memDC.Ellipse(x - r1 / 2, y - r2 / 2, x + r1 / 2, y + r2 / 2);

    }

    else {

    // Рисуем прямоугольник со скругленными углами

    m_memDC.RoundRect(x - r1 / 2, y - r2 / 2, x + r1 / 2, y + r2 / 2,

    r1 / 5, r2 / 5);

    }

    // пошлем сообщение WM_PAINT

    this->InvalidateRect(0, FALSE);

    }

    afx_msg BOOL CMainFrame::OnEraseBkgnd(CDC* pDC)

    {

    // Ничего не делаем, так как фон окна прорисовывать не нужно -

    // он будет прорисован при копировании контекста памяти в контекст окна.

    return TRUE; // Якобы фон прорисован

    }

    afx_msg void CMainFrame::OnPaint()

    {

    CPaintDC paintDC(this);

    RECT clientRect;

    this->GetClientRect(&clientRect);

    paintDC.BitBlt(0, 0, clientRect.right, clientRect.bottom,

    &m_memDC, 0, 0, SRCCOPY);



    }

    afx_msg void CMainFrame::OnDestroy()

    {

    this->KillTimer(1);

    }



    Graphics_And_VirtWin.hpp



    #include "stdafx.h"

    class CApp: public CWinApp {

    public:

    BOOL InitInstance();

    };



    Graphics_And_VirtWin.cpp



    #include "stdafx.h"

    #include "Graphics_and_VirtWin.hpp"

    #include "MainFrame.hpp"

    CApp App;

    BOOL CApp::InitInstance()

    {

    m_pMainWnd = new CMainFrame();

    return TRUE;

    }

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

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



    Заключение



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

    Возможно, первое, что Вы захотите подробно изучить, - это использование элементов управления OCX, а также создание собственных элементов управления. Это даст возможность использовать готовые высокоуровневые компоненты. Комбинируя технологию визуальной разработки интерфейса пользователя с использованием эффективного языка С++, Вы непременно добьетесь больших успехов в создании Windows-приложений.



    РЕКОМЕНДУЕМАЯ Литература



    1.  Герберт Шилдт. MFC: Основы программирования: Пер. с англ. - Киев, издательская группа "BHV", 1997.

    2.  Питер Нортон, Роб Макгрегор. Программирование для Windows 95/NT 4 с помощью MFC: Пер. с англ. В 2-х книгах. - М.: "СК Пресс", 1998.

    3. Microsoft Visual C++ Books Online.


    Содержание раздела