Простейший графический редактор
В предыдущем разделе мы создали при помощи средств MFC AppWizard работающий шаблон приложения. Теперь мы усовершенствуем его, чтобы пользователь смог рисовать в окне приложения и сохранять свои рисунки в файле на диске.
За основу нашего нового приложения мы возьмем проект Single и внесем в него все необходимые исправления и добавления. Доработаем приложение Single так, что когда пользователь нажимает левую клавишу мыши в окне отображается окружность, а когда пользователь нажимает правую кнопку - то отображается квадрат.
В момент нажатия на клавиши мыши создаются соответствующие сообщения, которые передаются классу окна просмотра. Нажатие левой клавиши мыши вызывает сообщение WM_LBUTTONDOWN, а нажатие правой - сообщение WM_RBUTTONDOWN.
Чтобы класс окна просмотра CSingleView мог отреагировать на это сообщение, вы должны создать метод для его обработки. Лучше всего для этого воспользоваться средствами ClassWizard.
Откройте страницу Message Maps на панели ClassWizard. Выберите из списков Class name и Object IDs класс CSingleView. В списке Messages появится названия виртуальных методов, которые вы можете переопределить и сообщений, для которых можно создать методы обработки.
Выберите из списка Messages сообщение WM_LBUTTONDOWN и нажмите кнопку Add Function. ClassWizard добавляет новую строку в таблицу сообщений класса CSingleView, вставляет в класс описание нового метода обработчика сообщения и создает шаблон этого метода.
Нажмите кнопку Edit Code. В окне редактирования появится шаблон метода, предназначенного для обработки сообщения WM_LBUTTONDOWN.
void CSingleView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Здесь вы можете разместить код метода
CView::OnLButtonDown(nFlags, point);
}
Название этого метода ClassWizard выбирает автоматически на основе сообщения WM_LBUTTONDOWN. Для этого префикс WM_ в названии сообщения заменяется префиксом On и происходит замена некоторых прописных букв строчными.
Шаблон метода OnLButtonDown содержит вызов метода OnLButtonDown базового класса CView. Вы должны добавить свой код перед вызовом этого метода, сразу после коментария // TODO:.
void CSingleView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Здесь вы можете разместить код метода
CClientDC dc(this);
dc.Ellipse(point.x-10, point.y-10, point.x+10,point.y+10);
CView::OnLButtonDown(nFlags, point);
}
Чтобы нарисовать в окне просмотра окружность, сначала необходимо получить контекст отображения. Для этого создается объект dc класса CClientDC. Конструктору передается указатель this, который указывает на объект класса CSingleView.
Затем вызывается метод Ellipse, который и отображает на экране небольшую окружность с центром, совпадающим с координатами указателя мыши.
Повторите только что проделанную процедуру для сообщения WM_RBUTTONDOWN. Создайте метод обработчик этого сообщения и добавьте в него команды отображения квадрата.
void CSingleView::OnRButtonDown(UINT nFlags, CPoint point)
{
// TODO: Здесь вы можете разместить код метода
CClientDC dc(this);
dc.Rectangle(point.x-10, point.y-10,
point.x+10,point.y+10);
CView::OnRButtonDown(nFlags, point);
}
Постройте проект и запустите полученное приложение. Нажимайте правую и левую кнопку мыши. Вы увидите, что на экране появляются окружности и квадраты (рис. 5.13). Поэкспериментируйте с приложением. Вы заметите, что изображение на экране пропадает, если оно перекрывается другими окнами, а также в некоторых других случаях.
Рис. 5.13. Отображение фигур в окне приложения Single
Это происходит потому, что наше приложение не обрабатывает одно из самых важных сообщений операционной системы Windows - сообщение WM_PAINT. Когда в окно приложения поступает сообщение WM_PAINT, приложение должно обновить информацию, отображаемую в данном окне.
Мы должны сохранить координаты и размеры нарисованных окружностей и квадратов, чтобы приложение могло воспроизвести их на экране, когда придет сообщение WM_PAINT.
Так как эти координаты в конечном счете представляют документ с которым работает приложение, то хранить их надо в классе документа приложения - CSingleDoc. Пользователь может нарисовать в документе любое количество фигур. Поэтому для хранения их координат лучшее всего подходит список или массив. Так как мы изучаем объектно-ориентированное программирование, мы воспользуемся для этого специальными классами. В состав библиотеки входят шаблоны для создания таких классов и несколько готовых классов. В нашей программе мы используем шаблон класса CArray.
Создадим новый класс CFigure, который будет представлять геометрические фигуры - окружности и квадраты. Координаты этих фигур мы будем определять по координатам их центра. Для этого в состав класса включим элемент xyFigCenter класса CPoint. Класс CPoint определяет координаты точки и содержит два элемента x и y, соответствующие координатам точки по оси ординат и абсцисс. Краткое описание класса CPoint представлено в разделе “Класс CPoint - точка на плоскости” главы “Некоторые классы MFC”.
Второй элемент cType типа char определяет форму геометрической фигуры. Если cType содержит значение 'E' значит данный объект представляет окружность, а если 'R' - квадрат.
Вы можете создать для класса CFigure отдельный файл, но сейчас мы просто добавим его в самое начало файла SingleDoc.h. Вот определение класса CFigure.
//////////////////////////////////////////////////////////////
// Класс определяет геометрическую фигуру
class CFigure
{
public:
// Координаты центра фигуры
CPoint xyFigCenter;
// Тип фигуры: 'E' - оокружность, 'R' - кволрат
char cType;
};
Один объект класса CFigure представляет одну геометрическую фигуру. Так как документ нашего приложения может содержать несколько фигур, мы воспользуемся шаблоном CArray, чтобы определить массив объектов класса CFigure. Вы можете получить дополнительную информацию о шаблоне CArray в разделе “Коллекции” главы “Некоторые классы MFC”.
Определение этого массива, который получил название arrayFig, помещаем в класс документа CSingleDoc, в атрибутах класса.
//////////////////////////////////////////////////////////////
// Класс CSingleDoc
class CSingleDoc : public CDocument
{
protected:
CSingleDoc();
DECLARE_DYNCREATE(CSingleDoc)
// Attributes
public:
CArray<CFigure, CFigure&> arrayFig;
Если вы используете шаблоны классов CArray, CMap или CList, вы должны включить в исходный текст приложения файл afxtempl.h. В данном файле содержатся определения этих шаблонов.
Так как мы работаем с объектами класса CArray в различных файлах, удобнее всего включить его в самом конце файла stdafx.h.
// Включаемый файл stdafx.h
// ...
// Включаемый файл для шаблона CArray
#include <afxtempl.h>
Теперь у нас есть структура для хранения геометрических фигур, нарисованных в окне. Мы должны ее заполнить. Так как за взаимодействие с пользователем отвечает класс окна просмотра, мы изменяем определенные нами ранее методы OnLButtonDown и OnRButtonDown таким образом, чтобы одновременно с выводом на экран они сохраняли параметры новой фигуры в массиве arrayFig.
//////////////////////////////////////////////////////////////
// Метод OnLButtonDown класса CSingleView
// Обрабатывает сообщения левой кнопки мыши
void CSingleView::OnLButtonDown(UINT nFlags, CPoint point)
{
// Получаем указатель на документ (объект класса CSingleDoc)
CSingleDoc* pDoc = GetDocument();
// Проверяем указатель pDoc
ASSERT_VALID(pDoc);
// Отображаем на экране окружность
CClientDC dc(this);
dc.Ellipse(point.x-10, point.y-10,
point.x+10,point.y+10);
// Сохраняем характеристики окружности
CFigure OneFigure;
OneFigure.xyFigCenter = point;
OneFigure.cType = 'E';
// Добавляем к массиву, определяющему документ, новый
// элемент
pDoc->arrayFig.Add(OneFigure);
// Вызываем метод OnLButtonDown базового класса CView
CView::OnLButtonDown(nFlags, point);
}
//////////////////////////////////////////////////////////////
// Метод OnRButtonDown класса CSingleView
// Обрабатывает сообщения правой кнопки мыши
void CSingleView::OnRButtonDown(UINT nFlags, CPoint point)
{
// Получаем указатель на документ (объект класса CSingleDoc)
CSingleDoc* pDoc = GetDocument();
// Проверяем указатель pDoc
ASSERT_VALID(pDoc);
// Отображаем на экране квадрат
CClientDC dc(this);
dc.Rectangle(point.x-10, point.y-10,
point.x+10,point.y+10);
// Сохраняем характеристики квадрата
CFigure OneFigure;
OneFigure.xyFigCenter = point;
OneFigure.cType = 'R';
// Добавляем к массиву, определяющему документ, новый
// элемент
pDoc->arrayFig.Add(OneFigure);
// Вызываем метод OnRButtonDown базового класса CView
CView::OnRButtonDown(nFlags, point);
}
Теперь координаты и форма всех нарисованных фигур запоминаются в классе документа. Следующим шагом надо определить, как отображать эти фигуры на экране. Для этого следует внести изменения в метод OnDraw класса окна просмотра CSingleView.
//////////////////////////////////////////////////////////////
// Метод OnDraw класса окна просмотра
void CSingleView::OnDraw(CDC* pDC)
{
CSingleDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO:
int i;
for (i=0; i<pDoc->arrayFig.GetSize(); i++)
{
if(pDoc->arrayFig[i].cType == 'E')
pDC->Ellipse(pDoc->arrayFig[i].xyFigCenter.x-10,
pDoc->arrayFig[i].xyFigCenter.y-10,
pDoc->arrayFig[i].xyFigCenter.x+10,
pDoc->arrayFig[i].xyFigCenter.y+10);
else if (pDoc->arrayFig[i].cType == 'R')
pDC->Rectangle(pDoc->arrayFig[i].xyFigCenter.x-10,
pDoc->arrayFig[i].xyFigCenter.y-10,
pDoc->arrayFig[i].xyFigCenter.x+10,
pDoc->arrayFig[i].xyFigCenter.y+10);
}
}
Постройте проект и запустите полученное приложение. Вы можете свободно изменять размеры окна приложения, перекрывать его окно окнами других приложений, минимизировать и восстанавливать размеры окна. Изображение документа, которое вы нарисуете, не пропадет.
Вы даже можете распечатать нарисованный документ на принтере. А ведь вы не написали для этого не единой строки кода. Перед печатью документа его можно проверить в режиме предварительного просмотра (рис. 5.14). Для этого выберите из меню File строку Print Preview
Рис. 5.14. Режим предварительного просмотра документа перед печатью
Простейший текстовый редактор
Когда вы создаете приложение с однооконным или многооконным интерфейсом при помощи средств MFC AppWizard, в последней диалоговой панели вы можете выбрать базовый класс для окна просмотра приложения. По умолчанию используется класс CView.
От класса CView наследуется целая группа классов, которые можно использовать для управление окном просмотра документа. Каждый из этих классов предоставляет дополнительные возможности для отображения документа.
CEditView <- |<- CCtrlView <- | CView <- CWnd
CRichEditView <- | |
CListView <- | |
CTreeView <- | |
|
CFormView <- CScrollView <- |
Опишем основные характеристики этих классов.
Класс | Описание | ||
CView | Наиболее общий класс, обеспечивающий отображение документа и взаимодействие с пользователем | ||
CScrollView | Класс CScrollView наследован от базового класса CView. В этом классе добавлены полосы просмотра, позволяющие перемещать документ в окне | ||
CEditView | Класс наследован от класса CView. Класс CEditView предоставляет возможности простого текстового редактора | ||
CRichEditView | Класс наследован от класса CView. Класс предоставляет возможности текстового редактора. В отличие от CEditView, он позволяет работать с текстом в формате RTF | ||
CFormView | Класс обеспечивает форматированное отображение документа на основе диалоговой панели | ||
CListView | Класс обеспечивает отображение документа с использование спискового органа управления | ||
CTreeView | Класс обеспечивает отображение документа с использование древовидного органа управления |
Чтобы создать простейший текстовый редактор создайте новое приложение с однооконным (или многооконным) интерфейсом. В качестве базового класса для класса окна просмотра приложения выберите класс CEditView. Завершите создание шаблона приложения.
Постройте полученный проект и запустите приложение. Текстовый редактор готов! Вы можете открыть в нем любой текстовый файл, отредактировать его и сохранить, внесенные изменения.
Более того, ваше приложение может работать с обменным буфером clipboard. Оно может записывать данные в clipboard и вставлять их из clipboard в редактируемый документ.
Фактически в приложении Editor полностью работают все строки меню Edit. Командные сообщения этого меню обрабатываются классом CEditView, поэтому их не надо реализовывать вручную. Вот краткое описание строк меню Edit.
Строка меню Edit |
Описание |
Undo |
Отменить последнюю операцию |
Cut |
Удалить выделенный текст и записать его в clipboard |
Copy |
Скопировать выделенный текст в clipboard |
Paste |
Вставить в документ содержимое clipboard |
Простые классы
MFC содержит классы, соответствующие объектам типа простых геометрических фигур, текстовых строк и объектам, определяющим дату и время. В следующей таблице перечислены названия этих классов и их краткие описания.
Класс | Описание | ||
CPoint | Объекты класса описывают точку | ||
CRect | Объекты класса описывают прямоугольник | ||
CSize | Объекты класса определяют размер прямоугольника | ||
CSrting | Объекты класса представляют собой текстовые строки переменной длинны | ||
CTime | Объекты класса служат для хранения даты и времени. Большое количество методов класса позволяют выполнять над объектами класса различные преобразования | ||
CTimeSpan | Объекты класса определяют период времени |
Проверка целостности объектов класса
Метод AssertValid выполняет проверку целостности объекта класса. Он проверяет состояние элементов данных класса на недопустимые значения. Если вы работаете с отладочной версией приложения и метод AssertValid обнаружит ошибку во внутреннем представлении объекта класса, выполнение приложения прерывается с выдачей соответствующего сообщения.
virtual void AssertValid() const;
Если вы наследуете класс от базового класса CObject и желаете использовать возможности метода AssertValid, вы должны переопределить его. Переопределенный метод AssertValid должен вызывать метод AssertValid базового класса, чтобы проверить целостность соответствующей части объекта. Затем необходимо выполнить проверку элементов порожденного класса. Для этого используйте макрокоманду ASSERT:
ASSERT(booleanExpression)
Макрокоманда ASSERT проверяет свой параметр booleanExpression. Если параметр макрокоманды имеет нулевое значение (FALSE), она отображает диагностическое сообщение и прерывает работу приложения. Если параметр booleanExpression не равен нулю (TRUE) работа приложения продолжается и макрокоманда не выполняет никаких действий.
Макрокоманда ASSERT выполняет проверку только в отладочной версии приложения. В окончательной версии приложения, построенной без отладочной информации, макрокоманда ASSERT не работает.
Если проверку параметра макрокоманды необходимо выполнять и в окончательной версии приложения, вы можете использовать вместо макрокоманды ASSERT макрокоманду VERIFY. Но при обнаружении ошибки работа приложения не будет прервана.
Вот пример переопределения метода AssertValid для класса CFigure, наследованного от базового класса CObject:
// Класс CFigure наследуется от базового класса CObject
class CFigure : public CObject
{
// Переопределяем виртуальный метод базового класса
int m_area = 0;
// Остальные элементы класса...
}
// Переопределяем виртуальный метод AssertValid класса CObject
void CFigure::AssertValid() const
{
// Сначала проверяем целостность элементов базового класса
CObject::AssertValid();
// Проверяем элемент m_area.
// Он должен быть больше или равен нулю
ASSERT(m_area >= 0);
}
Распределение памяти
Стандартная библиотека компиляторов содержит специальные функции управления памятью - malloc, free, а также другие разновидности этих функций. Они позволяют получить для использования блок оперативной памяти, и затем отдать его обратно операционной системе.
В Си++ встроены специальные операторы для управления памятью - оператор new и оператор delete. Эти операторы очень удобны для динамического создания переменных, массивов и объектов классов, поэтому мы остановимся на них более подробно.
Разграничение доступа к элементам базового класса
Мы уже рассказывали, что можно управлять доступом к элементам класса, указывая спецификаторы доступа для элементов класса. Элементы класса, объявленные с спецификаторами protected и private доступны только из методов самого класса. Элементы с спецификаторами public доступны не только из методов класса, но и извне.
При создании порожденного класса встает вопрос о доступе к элементам базового класса. Оказывается, когда вы наследуете класс из базового класса, вы можете управлять разграничением доступа к элементам базового класса. При этом имеет значение то, как объявлены элементы базового класса и какой спецификатор доступа указан для базового класса.
По следующей таблице вы можете определить как будут доступны элементы базового класса в зависимости от спецификатора доступа базового класса и спецификаторов доступа элементов базового класса.
Спецификатор доступа базового класса | |||||||
Спецификатор доступа элемента базового класса | public | protected | private | ||||
public | Доступны как public | Доступны как protected | Доступны как private | ||||
protected | Доступны как protected | Доступны как protected | Доступны как private | ||||
private | Недоступны | Недоступны | Недоступны |
Разграничение доступа к элементам класса
Определив класс, вы можете создавать объекты этого класса и манипулировать ими, используя методы. Некоторые данные и методы, объединенные одним классом, можно сделать недоступными вне реализации класса, к другим можно будет обращаться из программы.
Для управления доступом к элементам класса предусмотрены ключевые слова public, private и protect (спецификаторы доступа). Методы и данные, определенные или описанные после ключевого слова public представляют собой интерфейс класса - они доступны для использования вне определения класса. Остальные члены класса относятся к его внутренней реализации и обычно недоступны вне класса. Различия между членами класса, описанными после ключевых слов private и protect сказываются только при наследовании от данного класса новых классов. Процедуру наследования мы рассмотрим позже.
Ключевые слова public, private и protect указываются в определении класса перед элементами класса, доступом к которым они управляют. Ключевые слова, управляющие доступом, могут быть указаны несколько раз в одном классе, порядок их расположения значения не имеет. По умолчанию элементы класса являются private. Рекомендуется всегда явно определять права доступа к членам класса.
Ниже представлено определение класса Sample:
class Sample
{
int iX;
void Load();
public:
void SetStr();
void GetStr();
char sDataText[80];
private:
char sNameText[80];
int iIndex;
public:
void ConvertStr();
int iLevel;
};
В классе описаны элементы данных iX, sDataText, sNameText, iIndex, iLevel и методы Load, SetStr, GetStr, ConvertStr.
Элементы данных и методы SetStr, GetStr, sDataText, ConvertStr, iLevel объявлены public. К ним можно обращаться как из методов класса Sample, так и из программы. Остальные элементы класса объявлены как private. Доступ к ним открыт только для методов самого класса, а также дружественных функций и дружественных методов других классов. Дружественные функции и дружественные классы описаны в следующем разделе.
Редактирование элементов данных класса
Временное меню данных класса отличается от временного меню метода. Если выбрать из списка элементов класса данные и нажать правую кнопку мыши, на экране появится меню, представленное на рисунке 2.19.
Рис. 2.19. Временное меню метода
Редактирование методов класса
Выберите из списка элементов класса интересующий вас метод и нажмите правую кнопку мыши. На экране появится временное меню, показанное нами на рисунке 2.18. Это меню позволяет перейти к редактированию объявления или определения метода, просмотреть те строки исходного текста приложения, в которых вызывается метод, получить список функций и методов, вызываемых выбранным методом.
Рис. 2.18. Временное меню метода
Чтобы открыть в редакторе файл, в котором объявляется метод и перейти к его редактированию, выберите из временного меню метода строку Go to Definition. После того как вы добавите в класс новый метод, ClassWizard создаст шаблон метода. Строка Go to Declaration из временного меню метода позволяет изменить этот шаблон по вашему усмотрению.
Возможности ClassView можно использовать даже на этапе отладки приложения. Так, из временного меню метода можно установить точку прерывания непосредственно на начало метода. Для этого выберите из меню строку Set Breakpoint.
Ресурсы приложения
Добавить в проект новый ресурс очень просто. Вы можете для этого воспользоваться кнопками панели управления Progect или установить указатель мыши на название типа ресурса и нажать правую кнопку мыши. На экране появится временное меню свойств данного ресурса.
Если у вас уже есть ресурсы, которые разработаны ранее и записаны в отдельных файлах на диске, вы можете подключить их к своему проекту. Для этого надо выбрать из временного меню свойств ресурса строку Import. На экране появится диалоговая панель Import Resource с приглашением ввести имя файла подключаемого вами ресурса. Новый ресурс проекта будет записан в подкаталог RES, расположенный в каталоге проекта.
Сам по себе новый ресурс, подключенный к приложению, не принесет никакой пользы. Ваше приложение должно обращаться к нему - загружать в память, обрабатывать сообщения, связанные с этим ресурсом
Среда Visual C++ версии 4.0 не только позволяет просматривать и редактировать ресурсы приложения, она позволяет сразу после создания ресурса вызвать ClassWizard и подключить к ресурсу управляющий программный код. Пока мы не будем использовать ClassWizard, а код, управляющий ресурсами приложения, создадим вручную.
Большую часть пользовательского интерфейса любого приложения составляют ресурсы - меню, диалоговые панели, пиктограммы, курсоры. Создавая приложение, MFC AppWizard подготавливает для него базовый набор ресурсов. Вы можете редактировать подготовленные для вас ресурсы по своему усмотрению, а также добавлять в проект новые ресурсы.
Все ресурсы приложения хранятся в отдельном каталоге. Этот каталог называется RES и располагается в главном каталоге проекта. Однако нет необходимости каждый раз вручную открывать файлы с ресурсами. Для этого надо использовать средства, предоставляемые средой Visual C++.
Просмотреть ресурсы приложения можно в окне Project Workspace на странице ResourceView. Ресурсы приложения представлены в виде дерева и разделяются по типам. Чтобы просмотреть ресурсы определенного типа, надо установить курсор на символ
и нажать левую кнопку мыши. Символ изменится на и вам откроется список ресурсов данного типа, включенных в проект. Около имени каждого ресурса размещается небольшая пиктограмма. Чтобы загрузить выбранный ресурс в редактор, сделайте двойной щелчок левой кнопкой мыши по имени ресурса или по его пиктограмме. Вот список ресурсов, доступных для использования в приложении:Ресурс | Описание | ||||
Accelerators | Акселераторы | ||||
Bitmaps | Растровые изображения в формате BMP | ||||
Cursors | Курсоры | ||||
Dialogs | Диалоговые панели | ||||
Icons | Пиктограммы | ||||
Menus | Меню | ||||
String tables | Таблицы текстовых строк | ||||
Toolbars | Панели управления | ||||
Version information | Сведения о версии приложения | ||||
Имя определяется программистом | Ресурс, определяемый самим пользователем |
MFC AppWizard автоматически создает несколько различных ресурсов для приложения Dialog. Вы можете просмотреть эти ресурсы в окне Project Workspace, выбрав страницу ResourceView. Как видите ресурсы приложения включают две диалоговые панели, пиктограмму, таблицу строк и информацию о версии приложения. Рассмотрим эти ресурсы подробнее.
Приложение с однооконным интерфейсом, созданное средствами MFC AppWizard, имеет гораздо больше ресурсов, чем приложение, использующее в качестве интерфейса обыкновенную диалоговую панель. В нем определены не только диалоговые панели, таблица текстовых строк, пиктограмма и ресурс описания версии приложения, но также меню, панель управления и таблица акселераторов.
Самый базовый класс MFC (класс CObject)
Подавляющее большинство классов библиотеки MFC наследовано от базового класса CObject, лежащего в основе всей иерархии классов этой библиотеки. Методы и элементы данных класса CObject представляют наиболее общие свойства наследованных из него классов MFC.
Класс CObject, а также все классы, наследованные от него, обеспечивают возможность сохранения объектов класса в файлах на диске с их последующим восстановлением.
Для объектов классов, наследованных от базового класса CObject уже во время работы приложения можно получить разнообразную информацию о классе объекта.
Ряд методов класса CObject предназначен для получения дампа объектов класса во время отладки приложения. Эта особенность класса может ускорить процесс поиска ошибок в приложении.
Шаблон документов (классы CDocTemplate, CSingleDocTemplate и CMultiDocTemplate)
Еще один важный класс, наследуемый от CCmdTarget, называется CDocTemplate. От этого класса наследуются два класса CSingleDocTemplate и CMultiDocTemplate. Все эти классы предназначены для синхронизации и управления основными объектами представляющими приложение - окнами, документами и используемыми ими ресурсами.
Шаблон меню
Большой интерес для нас представляет ресурс, описывающий меню приложения. В ресурсах приложения определен только один шаблон меню, имеющий идентификатор IDR_MAINFRAME.
Когда пользователь выбирает строки меню, операционная система передает командное сообщение главному окну приложения.
//////////////////////////////////////////////////////////////
// Меню
IDR_MAINFRAME MENU PRELOAD DISCARDABLE
BEGIN
POPUP "&File"
BEGIN
MENUITEM "&New\tCtrl+N", ID_FILE_NEW
MENUITEM "&Open...\tCtrl+O", ID_FILE_OPEN
MENUITEM "&Save\tCtrl+S", ID_FILE_SAVE
MENUITEM "Save &As...", ID_FILE_SAVE_AS
MENUITEM SEPARATOR
MENUITEM "&Print...\tCtrl+P",ID_FILE_PRINT
MENUITEM "Print Pre&view", ID_FILE_PRINT_PREVIEW
MENUITEM "P&rint Setup...", ID_FILE_PRINT_SETUP
MENUITEM SEPARATOR
MENUITEM "Recent File", ID_FILE_MRU_FILE1,GRAYED
MENUITEM SEPARATOR
MENUITEM "E&xit", ID_APP_EXIT
END
POPUP "&Edit"
BEGIN
MENUITEM "&Undo\tCtrl+Z", ID_EDIT_UNDO
MENUITEM SEPARATOR
MENUITEM "Cu&t\tCtrl+X", ID_EDIT_CUT
MENUITEM "&Copy\tCtrl+C", ID_EDIT_COPY
MENUITEM "&Paste\tCtrl+V", ID_EDIT_PASTE
END
POPUP "&View"
BEGIN
MENUITEM "&Toolbar", ID_VIEW_TOOLBAR
MENUITEM "&Status Bar", ID_VIEW_STATUS_BAR
END
POPUP "&Help"
BEGIN
MENUITEM "&About Single...", ID_APP_ABOUT
END
END
Большая часть строк меню IDR_MAINFRAME имеет стандартные идентификаторы, описанные в библиотеке MFC. Некоторые из команд, соответствующих этим идентификаторам полностью обрабатываются MFC. Список стандартных команд с их описанием представлен в разделе “Стандартные команды”.
Шаблоны
Языки программирования С и Си++ обеспечивают строгую проверку типов данных. Некоторые языки не обеспечивают такой проверки и она полностью ложится на плечи программиста. Например в языке PL1 вы можете сравнивать значение строковой и числовой переменных. Это не будет ошибкой с точки зрения компилятора. Если вы случайно допустите ошибку, то обнаружить ее будет достаточно сложно.
Однако строгая типизация, присущая некоторым языкам, не всегда удобна. Если вам надо выполнять одинаковые вычисления над переменными различных типов, придется создавать две фактически одинаковые функции, которые оперируют с различными типами данных.
Аналогичная ситуация возникает, если вам надо создать класс, для работы с различными типами данных. Допустим вам надо создать список из элементов. Удобнее всего это сделать при помощи классов. Но если вам надо, чтобы в качестве элементов списка фигурировали различные типы данных, то вам наверняка придется написать несколько одинаковых классов, различающихся только типом элементов.
Естественно такая ситуация усложняет работу программиста, увеличивают объем исходных текстов программы, и наконец просто может стать причиной ошибок и несоответствий. Так, если в вашей программе содержится несколько функций или классов, отличающихся только типом данных, которыми они оперируют, то когда вы вносите исправления в одну функцию, надо будет точно также исправлять и все остальные.
Чтобы облегчить программисту работу, в стандарт языка Си++ было включено понятие шаблона. В Visual C++ шаблоны реализованы начиная с версии 2.0. Ранние версии Visual C++ с ними работать не могли.
Шаблоны предназначены для создания ряда классов и функций, отличающихся только типом обрабатываемых ими данных. Для определения шаблона предназначено ключевое слово template (шаблон). Общий синтаксис определения шаблона имеет следующий вид:
template <template-argument-list> declaration;
Аргумент template-argument-list представляет собой список условных имен для определения типов, по которым будут различаться различные реализации классов или функций данного шаблона.
Шаблоны в MFC
В библиотеке классов MFC определен ряд шаблонов для создания таких структур хранения информации как массив, список, словарь. Более подробно об этих шаблонах вы можете прочитать в разделе “Коллекции” главы “Некоторые классы MFC”.
Синхронизация задач приложения (класс CSyncObject)
Библиотека MFC позволяет создавать многозадачные приложения. Для синхронизации отдельных задач приложения предусмотрен ряд специальных классов. Все они наследуются от класса CSyncObject, представляющего собой абстрактный класс (рис. 2.8).
Рис. 2.8. Класс CSyncObject
В некоторых случаях требуется, чтобы участок программного кода мог выполняться только одной задачей. Такой участок называют критической секцией кода. Для создания и управления критическими секциями предназначены объекты класса CCriticalSection.
Объекты класса CEvent представляют событие. При помощи событий одна задача приложения может передать сообщение другой.
Объекты класса CMutex позволяют в данный момент времени представить ресурс в пользование только одной задачи. Остальным задачам доступ к ресурсу запрещается.
Объекты класса CSemaphore представляют собой семафоры. Семафоры позволяют ограничить количество задач, которые имеют доступ к какому-либо ресурсу.
Следующие версии Microsoft Visual C++
Весной 1996 года вышла новая версия Microsoft Visual C++ 4.1. В состав этой версии продукта включены дополнительные средства для разработки приложений, поддерживающих глобальную сеть Internet, язык моделирования виртуальной реальности (Virtual Reality Modeling Language - VRML), игровое SDK (Windows 95 Games SDK) и большое количество OCX объектов, которые вы можете включать в свои приложения.
Поддержка сети Internet
Последние версии Microsoft Internet Information Server, используемого в качестве серверов WWW и FTP, имеют программный интерфейс (Internet Server application programming interface - ISAPI). Используя этот интерфейс, вы можете разрабатывать собственные модули, взаимодействующие с сервером.
Чтобы облегчить программистам создание таких модулей, Microsoft Visual C++ версии 4.1 содержит “волшебник” ISAPI Extention Wizard, работающий на тех же принципах, что и MFC AppWizard. ISAPI Extention Wizard использует новые классы библиотеки MFC - CHttpServer, CHttpFilter, CHttpServerContext, CHttpFilterContext и CHtmlStream.
Язык моделирования виртуальной реальности
Язык моделирования виртуальной реальности позволяет помещать на WEB страницах серверов WWW трехмерные изображения. Подключившись к такой странице, пользователь сможет не просто просматривать статические изображения, он сможет перемещаться по трехмерному виртуальному пространству, выбирать различные объекты и т. д.
Игровое SDK
В настоящее время существует очень мало компьютерных игр, созданных специально для работы в операционной системе Windows. Обычно эти игры достаточно примитивны и не далеко ушли от таких игр как пасьянс и минер, поставляемых вместе с Windows. В то же время игры, которые не нуждаются в средствах Windows, значительно сложнее и имеют несравненно более сложный и реалистичный интерфейс.
Отсутствие сложных игр для Windows связано в первую очередь с тем, что в этой операционной системе нет средств для быстрого отображения графической информации на экране. Существующие методы не позволяют приложениям Windows отображать информацию с достаточной для компьютерных игр скоростью.
С появлением операционной системы Windows 95 ситуация начала изменяться к лучшему. В Windows 95 встроен специальный набор функций, обеспечивающий приложениям высокоэффективный доступ к видеоадаптеру. Вышли первые игры для Windows 95. Так фирма idSoftware выпустила специальную версию популярной игры Doom 2, предназначенную для работы исключительно в среде Windows 95.
Для создания программ, поддерживающих этот набор функций, необходимо использовать специальной средство разработки Games SDK. Ранее оно поставлялось отдельно, а начиная с версии 4.1 компилятора Microsoft Visual C++, вошло в состав этого пакета.
Набор OCX объектов
По сравнению с Microsoft Visual C++ версии 4.0 в версии 4.1 значительно расширен набор органов управления OLE (OCX). В него Microsoft включила органы управления OCX, разработанные другими фирмами.
Словари - шаблон CMap
Словарь, это таблица переменной длины, состоящая из двух колонок. Первая колонка содержит ключевые поля, а вторая - соответствующие им значения. Пользуясь объектами этого класса, вы можете по ключевому полю получить связанное с ним значение. Для поиска по ключу используется алгоритм хеширования, реализация которого вручную заняла бы слишком много времени.
В MFC включены классы для создания словарей отображающих значения слов, указателей, указателей на объекты типа CObject и объектов CString. Библиотека MFC версии 4.0 содержит шаблоны для организации словарей. Шаблоны позволяют создать класс словарей, основанных на объектах любых типов.
Ниже представлен прототип шаблона CMap.
template <class KEY, class ARG_KEY, class VALUE,
class ARG_VALUE> class CMap : public CObject
Параметр шаблона KEY определяет тип ключевого поля словаря. Параметр ARG_KEY является типом данных, используемых в качестве аргумента KEY. Параметр VALUE соответствует типу элементов, хранящихся в словаре, а параметр ARG_VALUE используется в качестве аргумента VALUE.
Сохранение и восстановление документа на диске
Построенное вами приложение можно использовать для рисования и печати документов, но оно не позволяет сохранять и загружать документ из файла на диске. Вы можете выбрать строку Save As (сохранить под именем) из меню File. На экране появится диалоговая панель Save As. В этой панели вы можете ввести имя файла, в котором надо сохранить документ. Однако несмотря на то, что файл создается, документ в него не записывается - файл остается пустым.
Вы можете попытаться его открыть, выбрав из меню File строку Open. Однако единственным результатом будет изменение заголовка окна. Чтобы приложение обрело возможность сохранения документов в файле и последующего чтения, надо изменить метод Serialize класса документа CSingleDoc.
Метод Serialize вызывается всякий раз когда надо сохранить документ в файле на диске или загрузить его из существующего файла. В частности, метод Serialize вызывается, когда пользователь выбирает из меню File строки Save, Save As и Open. Основные принципы работы метода Serialize были рассмотрены нами в разделе “Запись и восстановление объектов”.
MFC AppWizard подготавливает шаблон метода Serialize для класса CSingleDoc, представляющего документ приложения.
//////////////////////////////////////////////////////////////
// Метод Serialize класса CSingleDoc отвечает за сохранение и
// последующее восстановление документов приложения
void CSingleDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
// TODO: Здесь выполняется сохранение документа
}
else
{
// TODO: Здесь выполняется загрузка документа
}
}
Вы должны определить в методе Serialize, как он должен сохранять и восстанавливать документы приложения. Так как документ, с которым работает наше приложение представлен классом CSingleDoc, то все что должен делать метод Serialize - это сохранять все элементы массива arrayFig.
//////////////////////////////////////////////////////////////
// Метод Serialize класса CSingleDoc
void CSingleDoc::Serialize(CArchive& ar)
{
int i; // временная переменная
int num; // количество фигур в документе
// Сохранение документа
if(ar.IsStoring())
{
// Определяем количество элементов массива arrayFig
num = arrayFig.GetSize();
// Записываем полученное число в файл
ar << num;
// Записываем в файл координаты и тип фигур
for(i=0; i<num; i++)
{
// Сохраняем координаты центра фигуры
ar << arrayFig[i].xyFigCenter;
// Сохраняем тип фигуры
ar << arrayFig[i].cType;
}
}
// Загрузка документа
else
{
// Считываем количество элементов, составляющих документ
ar >> num;
// Восстанавливаем документ
for(i=0; i<num; i++)
{
CFigure OneFigure; // описание одной фигуры
// Считываем координаты центра фигуры
ar >> OneFigure.xyFigCenter;
// Считываем тип фигуры
ar >> OneFigure.cType;
// Добавляем описание очередной фигуры в документ
arrayFig.Add(OneFigure);
}
}
}
Метод Serialize имеет единственный параметр ar, представляющий ссылку на объект класса CArchive. Этот объект, называемый архивом, представляет файл документа, расположенный на диске. Кроме того, архив несет в себе информацию о том, что делать с документом - записать его в файл или загрузить из файла.
После вызова, метод Serialize определяет, какую операцию надо выполнить - сохранить документ в файле или загрузить его из файла. Для этого используется метод IsStoring, определенный в классе CArchive. Если метод IsStoring возвращает ненулевое значение для объекта ar, переданного методу Serialize, значит надо сохранить документ в файле.
Чтобы сохранить все элементы массива, мы определяем количество элементов в нем с помощью метода GetSize. Этот метод определен в шаблоне CArray и возвращает количество элементов массива.
Мы сохраняем количество элементов массива в файле, представленном архивом ar. Это значение поможет нам при восстановлении документа с файла на диске. Затем в цикле в файл записываются все элементы массива arrayFig.
Загрузка документа из файла выполняется в том же порядке. Сначала из файла документа, представленного архивом ar считывается значение, определяющее количество фигур в документе. Потом из файла считываются по очереди все элементы документа. При этом они сразу заносятся в массив arrayFig, представляющий документ. Для этого используется метод Add шаблона CArray.
После того, как вы внесете изменения в метод Serialize, постройте проект. Запустите полученное приложение. Теперь вы сможете записать документ в файл на диске, а затем загрузить его снова.
Сохранение и восстановление состояния объекта
В классе CObject определены метод IsSerializable и виртуальный метод Serialize, которые используются для сохранения и восстановления состояния объектов в файлах на диске. Чтобы объекты класса можно было сохранять в файлах на диске с возможностью их последующего восстановления, объявление класса объекта должно содержать макрокоманду DECLARE_SERIAL, а реализация класса макрокоманду IMPLEMENT_SERIAL. Более подробно об сохранении и восстановлении объектов можно прочитать в разделе “Запись и восстановление объектов”.
Сокеты (классы CAsyncSocket и CSocket)
Для тех, кто занимается сетевыми коммуникациями, в состав библиотеки MFC включены классы CAsyncSocket и наследованный от него класс CSocket (рис. 2.9). В класс CAsyncSocket включен программный интерфейс Windows Socket.
Класс CSocket предоставляет программисту более высокий уровень для работы с сокетами. Это значительно облегчает задачу программирования сетевых приложений.
Программирование на уровне программного интерфейса Windows с использованием сокетов описано в двадцать третьем томе серии “Библиотека системного программиста”, который называется “Глобальные сети компьютеров”.
Рис. 2.9. Класс CAsyncSocket
Сообщения от органов управления
Эта группа включает в себя сообщения WM_COMMAND от дочерних окон (включая окна стандартных классов), передаваемые их родительскому окну. Сообщения от органов управления обрабатываются точно таким же образом, что и оконные сообщения.
Исключение составляет сообщение WM_COMMAND с кодом извещения BN_CLICKED. Это сообщение передается кнопкой, когда пользователь на нее нажимает. Обработка сообщений с кодом извещения BN_CLICKED от органов управления происходит аналогично обработке командных сообщений.
Создание меню для приложения MFMenu
Так как наше приложение будет содержать меню, мы приступим к созданию ресурсов приложения. Для этого создайте новый файл ресурсов. Выберите из меню File строку New, а затем из открывшейся диалоговой панели выберите строку Resource Script и нажмите кнопку OK.
Теперь надо создать меню. Выберите из меню Insert строку Resource. На экране появится диалоговая панель Insert Resource. Выберите из нее строку Menu и нажмите кнопку OK. Вы сможете в диалоговом режиме разработать меню. Чтобы быстрее перейти к редактированию меню, вы можете нажать кнопку New Menu (
) из панели управления Project или просто нажать комбинацию кнопок <Ctrl+2>.Создайте меню, содержащее единственную строку Test, при выборе которой открывается меню, содержащее три строки - Beep и Exit. Внешний вид меню во время разработки представлен на рисунке 2.22.
Рис. 2.22. Разработка меню приложения
Когда вы создаете новое меню верхнего уровня или определяете строки, входящие в это меню, на экране появляется диалоговое окно Menu Item Properties (рис. 2.23). В нем полностью описывается выбранный элемент меню. В поле Caption задается название меню или строки меню. В названии вы можете вставить символ &. Он будет означать, что символ, следующий за ним, можно будет использовать для быстрого выбора данного элемента меню. Например, если перед названием строки меню Beep поставить символ &, то во время работы приложения символ B будет отображаться с подчеркиванием, и строку Beep можно будет выбрать, нажав комбинацию клавиш <Alt+B>.
Рис. 2.23. Диалоговая панель Menu Item Properties
Как вы знаете, каждый элемент меню должен иметь уникальный идентификатор, однозначно его определяющий. Вы можете задать идентификатор в поле ID или выбрать его из списка предопределенных идентификаторов.
Редактор ресурсов сам предлагает вам название идентификатора, создавая его из названия главного меню и строки меню. Так например строке Beep меню Test по умолчанию будет присвоен идентификатор ID_TEST_BEEP.
В поле Prompt можно в нескольких словах описать данный пункт меню. Введенная вами строка будет записана в строковый ресурс. Ей будет присвоен тот же самый идентификатор, что и пункту меню. Приложение может использовать строку описания, чтобы предоставить пользователю краткую справку о меню.
Если вы разрабатываете приложение с помощью средств MFC AppWizard, то при выборе пользователем строк меню их описание будет появляться в панели состояния StatusBar. Что интересно, для этого вам не потребуется написать ни одной строки текста программы. Это будет сделано автоматически благодаря возможностям библиотеки классов MFC и MFC AppWizard.
Остальные переключатели диалоговой панели Menu Item Properties описаны в следующей таблице.
Переключатель |
Описание |
Break |
Этот переключатель может принимать три различных значения - None, Column и Bar. Он позволяет задать структуру меню. По умолчанию выбрано значение None оно соответствует нормальному виду меню без деления на колонки. Если выбрать значение Column, тогда пункт меню будет размещен в новом столбце. Значение Bar соответствует Column, за исключением меню верхнего уровня. Если указать Bar для меню верхнего уровня, то новая колонка будет отделена от старой вертикальной линией |
Checked |
Если установить переключатель, то строка меню будет выделена символом . Потом, обращаясь к специальным методам, вы сможете удалять или отображать этот символ |
Grayed |
Если включить переключатель Grayed, тогда пункт меню будет отображаться серым цветом и будет недоступен для выбора пользователем. Такая возможность удобна, если вам надо сделать недоступным какие-либо возможности приложения. Впоследствии приложение может сделать пункт меню доступным. Для этого надо вызвать соответствующий метод |
Help |
Если установить переключатель Help, тогда для него будет установлено выравнивание по правому краю. Обычно этот переключатель устанавливают для меню верхнего уровня, которое управляет справочной системной приложения |
Inactive |
Если включен переключатель Grayed, тогда переключатель недоступен. В противном случае вы можете установить переключатель Inactive. В этом случае пункт меню будет неактивен |
Pop-up |
Вы можете создавать многоуровневые меню. Если вы включите переключатель Pop-up, то данный пункт меню будет являться меню верхнего уровня, которое можно открыть. По умолчанию, все пункты главного меню имеют установленный переключатель Pop-up. Так как меню верхнего уровня служит только для объединения других пунктов меню, то оно не имеет идентификатора |
Separator |
Если переключатель установлен, тогда в меню вставляется разделитель. Для разделителя все остальные поля и переключатели диалоговой панели не используются |
Сохраните файл ресурсов в файле с именем MFMenu.rc. Редактор ресурсов создает кроме самого файла ресурсов еще включаемый файл, в котором определяются константы, используемые в файле ресурсов. В нашем случае в нем определяются идентификаторы меню приложения. По умолчанию этот файл сохраняется под именем resource.h. Вы можете изменить его, выбрав из меню View строку Resource Includes. Для нашего приложения мы изменили имя включаемого файла для ресурсов на MFMenuRes.h. Содержимое этого файла представлено листингом 2.8.
Листинг 2.8. Файл MFMenuRes.h
//{{NO_DEPENDENCIES}}
// Включаемый файл, созданный Microsoft Developer Studio
// Используется в файле ресурсов MFMenu.rc
//
#define IDR_MENU 101
#define ID_TEST_BEEP 40001
#define ID_TEST_EXIT 40002
// Следующие значения идентификаторов используются по
// умолчанию для новых объектов
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40003
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
В листинге 2.9 мы привели файл ресурсов MFMenuRes.rc приложения. Этот файл был подготовлен редактором ресурсов Visual C++. Одна из первых строк файла содержит директиву #include которой подключается файл MFMenuRes.h, содержащий описание идентификаторов ресурсов (листинг 2.8).
Среди прочих служебных строк, необходимых редактору ресурсов и компилятору Visual C++, вы можете обнаружить описание меню приложения IDR_MENU. Для первого приложения, использующего ресурсы мы привели файл ресурсов полностью. Впоследствии мы ограничимся словесным описанием ресурсов и будем приводить только выдержки из файла ресурсов.
Листинг 2.9. Файл MFMenuRes.rc
// Файл описания ресурсов приложения, созданный
// Microsoft Developer Studio
#include "MFMenuRes.h"
#define APSTUDIO_READONLY_SYMBOLS
//////////////////////////////////////////////////////////////
// Включаем файл afxres.h, содержащий определения стандартных
// идентификаторов
#include "afxres.h"
//////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
//////////////////////////////////////////////////////////////
// Руссификацированные ресурсы
#if !defined(AFX_RESOURCE_DLL) defined(AFX_TARG_RUS)
#ifdef _WIN32
LANGUAGE LANG_RUSSIAN, SUBLANG_DEFAULT
#pragma code_page(1251)
#endif //_WIN32
//////////////////////////////////////////////////////////// // Меню
//
IDR_MENU MENU DISCARDABLE
BEGIN
POPUP "Test"
BEGIN
MENUITEM "Beep", ID_TEST_BEEP
MENUITEM SEPARATOR
MENUITEM "Exit", ID_TEST_EXIT
END
END
#ifdef APSTUDIO_INVOKED
////////////////////////////////////////////////////////
// Ресурсы TEXTINCLUDE
//
1 TEXTINCLUDE DISCARDABLE
BEGIN
"MFMenuRes.h\0"
END
2 TEXTINCLUDE DISCARDABLE
BEGIN
"#include ""afxres.h""\r\n"
"\0"
END
3 TEXTINCLUDE DISCARDABLE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
#endif // Руссификацированные ресурсы
//////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
#endif
Когда вы создадите ресурсы приложения и включите файл ресурсов в проект обратите внимание на окно Project Workspace. В нем появится еще одна, четвертая страница ResourceView (рис. 2.24). Эта страница показывает все ресурсы, входящие в проект. В приложении MFMenu определен только один ресурс - меню, имеющее идентификатор IDR_MENU.
Вы можете быстро перейти к редактированию меню, если выберите его идентификатор и щелкните два раза левой кнопкой мыши.
Рис. 2.24. Страница ResourceView окна Project Workspace
Теперь проект готов. Вы можете построить его и запустить полученное приложение MFMenu. Внешний вид приложения представлен на рисунке 2.25. Как видите окно приложения имеет меню Test, состоящее из двух строк - Beep и Exit.
Если вы выберите строку Beep из меню Test, то услышите на внутреннем динамике компьютера звуковой сигнал. В случае если звуковой сигнал не слышен, проверьте подключен ли внутренний динамик, а если в компьютере установлена звуковая плата, правильно установите громкость сигнала.
Когда вы завершите работу с приложением, его можно закрыть. Для этого воспользуйтесь системным меню приложения или выберите из меню Test строку Exit.
Рис. 2.25. Приложение MFMenu
Чтобы объекты класса могли обрабатывать сообщения, в определении этого класса необходимо поместить макрокоманду DECLARE_MESSAGE_MAP. По принятым соглашениям эта макрокоманда должна записываться в конце определения класса в секции public.
//=====================================================
// Класс CMFMenuWindow - представляет главное окно
//=====================================================
class CMFMenuWindow : public CFrameWnd
{
public:
// Объявляем конструктор класса CMFMenuWindow
CMFMenuWindow();
// Объявляем методы для обработки команд меню
afx_msg void MenuCommand();
afx_msg void ExitApp();
// Макрокоманда необходима, так как класс
// CMFMenuWindow обрабатывает сообщения
DECLARE_MESSAGE_MAP()
};
Однако это еще не все. Необходимо также определить таблицу сообщений. Таблица начинается макрокомандой BEGIN_MESSAGE_MAP и заканчивается макрокомандой END_MESSAGE_MAP. Между этими макрокомандами расположены строки таблицы сообщений, определяющие сообщения, подлежащие обработке данным классом и методы, которые выполняют такую обработку.
Приложение может содержать несколько классов, обладающих собственными таблицами сообщений. В следующем разделе мы приведем пример такого приложения. Чтобы однозначно определить класс, к которому относится таблица сообщений, имя этого класса записывается в первый параметр макрокоманды BEGIN_MESSAGE_MAP.
Приложение MFMenu обрабатывает только две команды от меню приложения. Первая команда имеет идентификатор ID_TEST_BEEP и передается, когда пользователь выбирает из меню Test строку Beep. Для обработки этой команды вызывается метод MenuCommand. Вторая команда с идентификатором ID_TEST_EXIT передается приложению, когда пользователь выбирает из меню Test строку Exit. Обработка этого сообщения выполняется методом ExitApp.
//=====================================================
// Таблица сообщений класса CMFMenuWindow
//=====================================================
BEGIN_MESSAGE_MAP(CMFMenuWindow, CFrameWnd)
ON_COMMAND(ID_TEST_BEEP, MenuCommand)
ON_COMMAND(ID_TEST_EXIT, ExitApp)
END_MESSAGE_MAP()
Конечно, приложению MFMenu может поступать гораздо больше сообщений и команд, чем указано в таблице сообщений класса CMFMenuWindow. Необработанные сообщения передаются для обработки базовому классу CMFMenuWindow - классу CFrameWnd. Класс, который будет обрабатывать сообщения, не указанные в таблице сообщений, указывается во втором параметре макрокоманды BEGIN_MESSAGE_MAP.
Создание нового документа
Документ, который вы можете создать в приложении Single, можно убрать, только полностью закрыв приложение. Функция создания нового документа не работает. Когда вы выбираете из меню File строку New или нажимаете кнопку
, расположенную в панели управления приложения, внешний вид документа не изменяется.Оказывается, когда пользователь выбирает из меню File строку New, вызывается виртуальный метод OnNewDocument, определенный в классе CDocument. Если вы не переопределите этот метод, то по умолчанию он вызывает метод DeleteContents, и далее помечает его как чистый (пустой). Вы можете переопределить метод OnNewDocument в своем классе документа, чтобы выполнить его инициализацию. Требуется, чтобы вы вызывали из переопределенного метода OnNewDocument, метод OnNewDocument, определенный в базовом классе CDocument.
Когда пользователь создает новый документ в приложении, построенном на основе однооконного интерфейса, то на самом деле используется старый документ. Новый объект класса, представляющего документ, не создается. Метод OnNewDocument должен удалить содержимое документа и выполнить повторную инициализацию существующего объекта класса документ.
Из этого следует, что нельзя выполнять инициализацию документа в конструкторе класса документа, так как конструктор будет вызван только один раз за время работы приложения. Более правильно использовать для этой цели метод OnNewDocument.
Для приложений, использующих многооконный интерфейс, процедура создания нового документа отличается. Каждый раз, когда пользователь создает новый документ, создается новый объект класса документ. Процесс создания нового документа мы опишем в следующей главе.
//////////////////////////////////////////////////////////////
// Метод DeleteContents
void CSingleDoc::DeleteContents()
{
// TODO:
// Очищаем документ, удаляя все элементы массива arrayFig.
// Метод RemoveAll определен в классе CArray
arrayFig.RemoveAll();
// Вызываем метод DeleteContents базового класса CDocument
CDocument::DeleteContents();
}
Создание нового класса
Из любой страницы ClassWizard можно добавить в приложение новый класс, созданный на основе базовых классов. В качестве базового класса можно использовать классы, наследованные от класса CCmdTarget или класса CRecordset. Если требуется создать класс, не наследуемый от базовых классов CCmdTarget или CRecordset, использовать средства ClassWizard нельзя. Такие классы надо создавать вручную, непосредственно в текстовом редакторе.
Объекты порожденные от класса CCmdTarget могут обрабатывать сообщения Windows и команды, поступающие от меню, кнопок, акселераторов. Класс CCmdTarget и другие наследованные от него классы имеют таблицу сообщений (Message map) - набор макрокоманд, позволяющий сопоставить сообщениям Windows и командам методы класса
Чтобы создать класс, нажмите кнопку Add Class из любой страницы главной диалоговой панели ClassWizard. Откроется временное меню, содержащее три строки: New, From a file, From an OLE TypeLib. Для создания нового класса выберите из этого меню строку New. Если вы уже имеете исходные тексты класса и их просто требуется подключить к проекту, выберите из меню строку From a file. Последняя строка меню From an OLE TypeLib используется для подключения классов из библиотеки OLE.
Когда вы создаете новый класс, на экране появляется диалоговая панель Create New Class. В поле Name введите имя создаваемого класса. Рекомендуется начинать названия классов с символа “C”. Для создаваемого класса организуются два файла реализации класса, имеющие расширения CPP и H. В них будут помещаться объявления класса, а также определения его методов и данных. Имя файлов реализации отображается в левой части группы File. По умолчанию файлы реализации имеют имена, соответствующие имени класса. Однако их можно изменить, воспользовавшись кнопкой Change из группы File.
Теперь выберите из списка Base Class имя базового класса. Список Base Class достаточно велик. В нем содержатся не только основополагающие классы типа CCmdTarget, CDialog, CDocument, CFrameWnd, CView, CWinThread, CWnd. Список базовых классов включает классы большинства органов управления, например CAnimateCtrl, CButton, CColorDialog, CComboBox, CDragListBox, CEdit, CEditView, CFileDialog, CFontDialog, CHeaderCtrl, CHotKeyCtrl, CListBox, CListCtrl, CListView, CProgressCtrl, CStatic и многие многие другие. Доступны также базовые классы, предназначенные для работы с базами данных: CDaoRecordSet, CDaoRecordView, CRecordset, CRecordView, классы обеспечивающие технологию OLE: COleDocument, COleLinkingDoc, COleServerDoc.
Так, например, вы можете создать новый класс CNewClass, наследованный от базового класса окна просмотра CEditView. Определение класса помещается во включаемый файл NewClass.h (листинг 4.7).
Листинг 4.7. Файл NewClass.h
// Класс окна просмотра CNewClass
class CNewClass : public CEditView
{
protected:
CNewClass();
DECLARE_DYNCREATE(CNewClass)
// Attributes
public:
// Operations
public:
// Overrides
//{{AFX_VIRTUAL(CNewClass)
protected:
virtual void OnDraw(CDC* pDC);
//}}AFX_VIRTUAL
// Implementation
protected:
virtual ~CNewClass();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
// Методы, предназначенные для обработки сообщений
protected:
//{{AFX_MSG(CNewClass)
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
Определение методов класса размещается в другом файле, имеющем расширение CPP (листинг 4.8).
Листинг 4.8. Файл NewClass.cpp
#include "stdafx.h"
#include "Single.h"
#include "NewClass.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
//////////////////////////////////////////////////////////////
// Реализация класса CNewClass
IMPLEMENT_DYNCREATE(CNewClass, CEditView)
CNewClass::CNewClass()
{
}
CNewClass::~CNewClass()
{
}
BEGIN_MESSAGE_MAP(CNewClass, CEditView)
//{{AFX_MSG_MAP(CNewClass)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
//////////////////////////////////////////////////////////////
// Метод OnDraw класса CNewClass
void CNewClass::OnDraw(CDC* pDC)
{
CDocument* pDoc = GetDocument();
// TODO: здесь можно расположить код, выполняющий вывод в
// окно
}
//////////////////////////////////////////////////////////////
// Диагностические методы класса CNewClass
#ifdef _DEBUG
void CNewClass::AssertValid() const
{
CEditView::AssertValid();
}
void CNewClass::Dump(CDumpContext& dc) const
{
CEditView::Dump(dc);
}
#endif //_DEBUG
Полученная заготовка класса полностью работоспособна. Ее можно дополнить по своему усмотрению новыми методами и данными. Эту работу можно выполнить вручную, но гораздо лучше и проще воспользоваться услугами предоставляемыми ClassWizard. За счет использования ClassWizard процедура создания собственного класса значительно ускоряется и уменьшается вероятность совершить ошибку во время объявления методов.
Создание приложения средствами MFC AppWizard
Во второй главе книги мы рассматривали приложение MFDialog, которое не имеет главного окна. Вместо окна это приложение использует обыкновенную диалоговую панель. Сейчас мы расскажем вам как создать приложение, подобное MFDialog, не набрав ни одной строки текста программы. Для этого мы будем использовать средства MFC AppWizard и ClassWizard.
Выберите из меню File строку New. На экране появится диалоговая панель New, содержащая меню. Выберите из него тип объекта, который надо создать. Для создания нового проекта выберите из этого меню строку Project Workspace. Теперь на экране откроется диалоговая панель New Project Workspace, показанная нами на рисунке 4.1.
Рис. 4.1. Диалоговая панель New Project Workspace
Из списка Type выберите тип приложения, которое вы желаете создать. В следующей таблице перечислены типы приложений, которые вы можете выбрать.
Тип приложения | Описание | ||
MFC AppWizard (exe) | Приложение, создаваемое с использованием библиотеки классов MFC. С помощью AppWizard вы можете автоматически создать основные классы необходимые для приложения | ||
MFC AppWizard (dll) | Библиотека динамической компоновки - DLL, создаваемая с помощью библиотеки классов MFC. AppWizard позволяет автоматически создать все основные файлы, необходимые для DLL | ||
OLE ControlWizard | Органы управления OLE, созданные с использованием библиотеки классов MFC. Компилятор автоматически создает базовый набор файлов для проекта этого типа | ||
Application | Приложение, созданное на основе библиотеки классов MFC или с использованием только вызовов функций программного интерфейса Windows | ||
Dynamic-Link Library | Библиотека динамической компоновки, созданная с использованием только вызовов функций программного интерфейса Windows | ||
Console Application | Приложение, разработанное с использованием функций консольного ввода/вывода. Этот тип приложений можно использовать для создания небольших программ, работающих в пакетном режиме | ||
Static Library | Библиотека функций | ||
Makefile | Предоставляет дополнительные возможности для использования MAKE-файла | ||
Custom AppWizard | Позволяет создать собственный “волшебник” Custom AppWizard, который можно будет использовать для разработки шаблонов приложений с заданными вами свойствами |
Список типов приложений, которые может создавать Microsoft Visual C++ версии 4.1, расширен. В него включен “волшебник” ISAPI Extension Wizard, который облегчает создание приложений для Microsoft Internet Information Server.
В этой книге мы расскажем о создании собственных приложений с использованием средств AppWizard. Поэтому выберите из списка Type строку MFC AppWizard (exe).
Теперь определите расположение базового каталога, в котором будут размещены проекты. Путь каталога можно ввести непосредственно в поле Location или выбрать, из списка, нажав на кнопку Browse. Затем введите в поле Name имя создаваемого проекта. В базовом каталоге создается одноименный подкаталог и в него записываются все файлы проекта. Имена файлов, составляющих проект, и названия классов приложения также присваиваются AppWizard на основе имени проекта.
В группе переключателей Platforms надо выбрать, для какой платформы создается приложение. Если вы работаете в среде операционных систем Windows NT или Windows 95, установите переключатель Win32.
После того как вы заполнили все поля диалоговой панели, нажмите кнопку Create. На экране появится первая диалоговая панель MFC AppWizard. Внешний вид этой панели зависит от того, какой тип приложения вами создается. Если вы создаете выполнимое приложение, то на экране появится диалоговая панель, показанная на рисунке 4.2.
Рис. 4.2. Первый шаг MFC AppWizard
На первом шаге вам предлагается определить, какой тип пользовательского интерфейса должно иметь приложение. Вы можете выбирать между многооконным интерфейсом (Multiple documents), однооконным интерфейсом (Single document) и интерфейсом основанном на диалоговой панели без главного окна (Dialog based).
После того как вы определите тип пользовательского интерфейса приложения, в заголовке диалоговой панели MFC AppWizard будет указано, сколько еще шагов (диалоговых панелей AppWizard) надо будет заполнить, чтобы определить все свойства приложения. Для приложений, имеющих интерфейс на основе главной диалоговой панели, процесс создания приложения будет состоять из 4 шагов, а для приложений, имеющих однооконный и многооконный интерфейс - 6 шагов.
Вы также можете выбрать язык, на котором написан интерфейс приложения. К сожалению, в той версии компилятора, которая была у нас, русский язык отсутствовал. Поэтому мы использовали в наших приложениях английский язык.
Заполнив первую диалоговую панель MFC AppWizard, нажмите кнопку Next >. На экране появится следующая диалоговая панель MFC AppWizard. В зависимости от того, какой тип интерфейса пользователя вы выбрали для приложения, вид этой диалоговой панели может несколько отличаться.
Если вы выбрали интерфейс приложения, основанный на диалоговой панели, тогда диалоговая панель на втором шаге будет иметь вид, показанный на рисунке 4.3. В этой панели можно указать, будет ли у создаваемого приложения информационная диалоговая панель, справочная подсистема, трехмерные органы управления, возможности использования технологии OLE и коммуникации с помощью протокола TCP/IP. Вы также сможете определить заголовок главной диалоговой панели приложения.
Рис. 4.3. Второй шаг MFC AppWizard
Если включить переключатель About box, то приложение будет иметь небольшую информационную панель About. В ней обычно содержится краткая информация о приложении - его название, номер версии, авторские права, небольшая пиктограмма. Чтобы вызвать эту панель, пользователь должен будет выбрать из системного меню главной диалоговой панели приложения строку About App…
Операционная система Windows имеет хорошо развитую справочную систему. Обычно каждое приложение имеет собственный справочный файл данных, содержащий разнообразную информацию о приложении. MFC AppWizard позволяет легко создать заготовку такого файла и подключить ее к приложению. Для этого следует включить переключатель Context sensitive Help. Теперь главная диалоговая панель приложения будет иметь кнопку Help, с помощью которой можно запустить справочную систему приложения.
Современный дизайн интерфейса приложений предполагает, что все органы управления, например кнопки и переключатели, должны выглядеть объемными. Чтобы получить эффект трехмерных органов управления, включите переключатель 3D controls.
Средства автоматизированного создания приложений легко позволяют создавать приложения, использующие OLE технологию. Для приложений, интерфейс пользователя которых основан на диалоговой панели, вы можете использовать технологию OLE automation. Эта технология позволяет приложению работать с объектами, созданными в других приложениях.
Чтобы облегчить программистам создание приложений Windows, разработаны органы управления OLE. Если вы будете их использовать, включите переключатель OLE controls.
Библиотека классов MFC версии 4.0 позволяет создавать приложения, взаимодействующие друг с другом через сетевой протокол TCP/IP. Чтобы включить поддержку этого протокола, включите переключатель Windows Sockets.
По умолчанию название главной диалоговой панели приложения совпадает с именем проекта. Вы можете изменить это название в поле Please enter a title for your dialog.
После того, как вы заполнили диалоговую панель, нажмите кнопку Next >. На экране появится следующая диалоговая панель, предназначенная для определения основных свойств приложения. Мы представили ее на рисунке 4.4.
Рис. 4.4. Третий шаг MFC AppWizard
В этой диалоговой панели вы можете попросить MFC AppWizard немного приподнять завесу тайны над волшебством автоматического создания приложения. Если вы переместите переключатель Would you like to generate source file comments в положение Yes, please, то исходный текст приложения будет снабжен комментариями.
Приложение может использовать библиотеку классов MFC двумя способами - вызывая библиотеки DLL или включая код классов непосредственно в приложение.
В первом случае приложение будет иметь меньший размер, но вместе с ним вы должны будете распространять dll-библиотеки MFC. Описание dll-библиотек MFC вы можете найти в разделе “Первое приложение MFC” главы “Введение в MFC”.
Во втором случае выполнимый файл приложения будет иметь больший размер, но он будет полностью содержать весь код, необходимый для его работы.
Способ подключения библиотеки MFC определяется положением переключателя How would you like to use the MFC library. Если он находится в положении As a shared DLL, то используется dll-библиотека MFC, а если в положении As a statically linked library, то код классов MFC включается непосредственно в выполнимый файл приложения.
Теперь вы можете перейти к последнему этапу определения свойств приложения. Нажмите кнопку Next >. На экране появится диалоговая панель для выбора названий классов приложения. Внешний вид этой панели представлен на рисунке 4.5.
Рис. 4.5. Четвертый шаг MFC AppWizard
В списке AppWizard creates the following classes for you перечислены названия всех классов, которые создает MFC AppWizard для вашего приложения. Названия этих классов являются производными от названия проекта. Ниже этого списка расположены четыре поля Class name, Base class, Header file, Implementation file. Когда вы выбираете из списка AppWizard creates the following classes for you название класса приложения в этих полях отображаются следующая информация:
Имя поля |
Описание |
Class name |
Имя класса приложения, выбранное вами из списка. Вы можете изменить его по вашему усмотрению или оставить как есть |
Base class |
Имя базового класса MFC, из которого наследуется класс выбранный из списка Class name. Для ряда классов базовый класс можно изменить. Более подробно об этом мы расскажем позже |
Header file, Implementation file |
Эти два поля определяют названия включаемого файла, в котором описан класс, и файла, содержащего исходный код методов класса. Вы можете изменить их по своему усмотрению |
MFC AppWizard создаст проект, который сразу можно оттранслировать и получить приложение, полностью готовое к запуску. Запустите полученное приложение. На экране появится главная диалоговая панель приложения (рис. 4.6).
Рис. 4.6. Приложение Dialog
Полученное приложение имеет только две кнопки OK и Cancel. Нажав на любую из них, вы можете завершить приложение. Взяв за основу полученный проект, измените его в соответствии с вашими потребностями. Вы можете добавить в диалоговую панель новые органы управления, подключить к ним программный код, создать другие ресурсы, и т. д. Все эти задачи легко решаются в среде Microsoft Visual C++ версии 2.0 и 4.0.
Списки - шаблон CList
В состав MFC входят классы, предназначенные для организации двунаправленных списков указателей, строк, состоящих из объектов CString, указателей на объекты класса CObject. В MFC версии 4.0 добавлен шаблон класса списка CList. С помощью этого шаблона можно создавать списки, состоящие из любых объектов.
Списковый класс имеет методы для вставки и удаления элементов в начало, конец и середину списка. Вы можете получить предыдущий и следующий элемент из списка.
Ниже представлен прототип шаблона CList.
template <class TYPE, class ARG_TYPE>
class CList : public CObject
Средства ClassView
Конечно, вы можете продолжать набирать исходные тексты приложения вручную непосредственно в текстовом редакторе. Но во многих случаях среда VIsual C++ может оказать вам значительную помощь. Одним из средств, способных оказать вам такую помощь уже сейчас, является ClassView.
Используя ClassView можно быстро добавлять к классам новые элементы, просматривать структуру наследования классов и выполнять другие полезные операции. После того как вы разберетесь с основными принципами построения приложений Windows с использованием классов MFC, мы продолжим изучение вспомогательных средств VIsual C++. В следующих главах книги мы рассмотрим средства автоматизированного проектирования приложений MFC AppWizard и средство для разработки классов ClassWizard.
А сейчас приступим к описанию возможностей ClassView. Выберите из списка ClassView название класса, который вы желаете просмотреть или изменить, и нажмите правую клавишу мыши. На экране появится временное меню, представленное на рисунке 2.14.
Рис. 2.14. Временное меню класса
Строки этого меню позволяют выполнять над классами все основные действия. Строка Go to Definition позволяет просмотреть в окне редактирования исходный текст класса. Вы можете редактировать класс непосредственно, но для добавления в класс новых методов и данных удобнее пользоваться строками Add Function и Add Variable.
Средства ClassWizard
Разработка приложения не заканчивается, когда MFC AppWizard создаст для вас исходные файлы проекта. Теперь вы должны добавить к приложению собственный программный код, выполняющий основные функции приложения. Среда Microsoft Visual C++ версии 4.0 позволяет максимально облегчить дальнейшую разработку приложения. Для этого предназначены такие средства как ClassView и ClassWizard. Они позволяют с минимальными усилиями добавлять в классы новые методы и данные, быстро высвечивать в окне редактирования интересующие вас объявления и определения классов, методов, данных.
Мы уже изучили возможности ClassView, предназначенные для работы с классами в разделе “Средства ClassView” главы “Введение в MFC”. По сравнению с ClassView, “волшебник” ClassWizard предоставляет более полные услуги. Он позволяет не только добавлять к классу новые методы и данные. Вы можете использовать ClassWizard, чтобы добавить к классу новый метод, служащий для обработки сообщений, переменную, предназначенную для обмена информацией с полями диалоговой панели.
Запустить ClassWizard очень просто, для этого можно нажать кнопку
из стандартной панели управления (окно Standard) или выбрать из меню View строку ClassWizard. На экране появится главная диалоговая панель ClassWizard. Мы привели внешний вид этой панели на рисунке 4.10.Рис. 4.10. Главная панель ClassWizard
Главная панель ClassWizard содержит пять страниц - Message Maps, Member Variables, OLE Automation, OLE Events и Class Info. Страница Message Maps позволяет просмотреть сообщения, вырабатываемые объектами, и создать методы для их обработки. Вторая страница Member Variables позволяет управлять данными, записанными в классе. Следующие две страницы OLE Automation и OLE Events отвечают за поддержку вашим приложением технологии OLE. Эти страницы будут рассмотрены позже. Последняя страница Class Info позволяет получить различную информацию о классе.
Когда вы просматриваете исходный текст приложения, в верхней части окна редактора может отображаться панель WizardBar (рис. 4.11). Органы управления этой панели позволяют получить быстрый доступ к некоторым возможностям ClassWizard. WizardBar позволяет управлять методами, обрабатывающими сообщения от органов управления.
Рис. 4.11. Панель WizardBar
Ссылки
В языке Си++ вы можете определить ссылку на объект - переменную или объект класса. Ссылка содержит адрес объекта, но вы можете использовать ее, как будто она представляет сам объект. Для объявления ссылки используется оператор &.
В следующей программе мы определили переменную iVar типа int и ссылку iReferenceVar на нее. Затем мы отображаем и изменяем значение переменной iVar используя ее имя и ссылку.
// Включаемый файл для потокового ввода/вывода
#include <iostream.h>
void main(void)
{
// Определяем переменную iVar
int iVar = 10;
// Определяем ссылку iReferenceVar на переменную iVar
int& iReferenceVar = iVar;
// Отображаем значение переменной и ссылки
cout << "iVar = " << iVar << "; iReferenceVar = " <<
iReferenceVar << '\n';
// Изменяем значение переменной iVar пользуясь ссылкой
iReferenceVar = 20;
// Отображаем значение переменной и ссылки
cout << "iVar = " << iVar << "; iReferenceVar = " <<
iReferenceVar << '\n';
}
Вы можете использовать ссылки для передачи параметров функциям. При этом фактически вы передаете функции указатель на объект, представленный ссылкой. Внутри функции вы можете работать с ссылкой как с самим объектом, а не как с указателем.
Функция может не только принимать ссылки в качестве своих параметров, она также может возвращать ссылку. Такую функцию можно привести в левой части оператора присваивания.
Стандартные командные сообщения
Подавляющее большинство приложений, созданных на основе MFC, использует ряд стандартных командных сообщений, как правило соответствующих элементам меню или кнопкам панели управления. К ним относятся командные сообщения для завершения работы приложения, создания нового документа, открытия документа, записанного на диске, сохранения документа на диске, вызова справочной системы, управления текстовым редактором и т. д. За каждым таким командным сообщением зарезервирован отдельный идентификатор.
MFC обеспечивает различный уровень обработки стандартных командных сообщений, начиная от простого резервирования идентификатора и кончая полной его обработкой.
Элемент меню или кнопка панели управления приложения имеет тот же идентификатор, что и командное сообщение. Просмотрите список идентификаторов меню приложения Single. В нем вы найдете многие стандартные команды, описанные ниже.
Ниже коротко описаны наиболее важные стандартные командные сообщения.
Статические методы
Вы можете объявить некоторые методы класса статическими методами. Для этого вы должны воспользоваться ключевым словом static. Статические методы не принимают параметр this. На использование статических методов накладывается ряд ограничений.
Статические методы могут непосредственно обращаться только к статическим членам класса.
Статический метод не может быть объявлен как виртуальный метод.
Вы не можете определить нестатический метод с тем же именем и тем же набором параметров, что и статический метод класса.
Статические методы имеют одну интересную особенность - вы можете вызывать их даже без создания объектов класса. Чтобы вызвать из программы статический метод, вы должны указать его полное имя, включая имя класса.
Ниже представлен класс Circle, в котором определена статический метод GetPi. Он используется для получения значения статического элемента класса fPi.
class Circle
{
public:
static void GetPi()
{ return fPi; }
private:
static float fPi;
};
float Circle::fPi = 3.1415;
Вы можете вызвать метод GetPi следующим образом:
float fNumber;
fNumber = Circle::GetPi();
Обратите внимание, что объект класса Circle не создается.
Структуры
Понятие структуры в языке Си++ значительно расширено. Структура в Си++ обладает всеми возможностями классов. В структуры Си++ можно включать не только элементы данных, но и методы. Вы можете наследовать от структур новые структуры, точно также как вы наследуете новые классы от базовых классов.
Различие между структурами и обычными классами заключается только в управлении доступом к их элементам. Так, если элементы класса по умолчанию объявлены как private, то все элементы структуры по умолчанию объявлены как public.
Ниже мы привели пример объявления структуры StructData и класса ClassData, которые содержат одинаковые элементы с одинаковыми правами доступа к ним. Фактически, структура StructData и класс ClassData совершенно равнозначны.
//====================================================
// Класс ClassData
class ClassData
{
int iPrivateValue;
public:
int iPublicValue;
};
//====================================================
// Структура StructData
struct StructData
{
int iPublicValue;
private:
int iPrivateValue;
};
Еще одно различие между структурами и классами проявляется в разграничении доступа к элементам базового класса (см. раздел “Разграничение доступа к элементам базового класса”). Если вы наследуете новый класс от базового класса и не указываете спецификатор доступа, по умолчанию используется спецификатор private. Когда же вы наследуете от базового класса структуру, по умолчанию используется спецификатор public.
Таблица акселераторов
Чтобы ускорить доступ к строкам меню приложения, MFC AppWizard добавляет в файл ресурсов таблицу акселераторов. Когда пользователь нажимает комбинацию клавиш, представленную в таблице акселераторов, приложению поступает командное сообщение с соответствующим идентификатором.
//////////////////////////////////////////////////////////////
// Таблица акселераторов
IDR_MAINFRAME ACCELERATORS PRELOAD MOVEABLE PURE
BEGIN
"N", ID_FILE_NEW, VIRTKEY,CONTROL
"O", ID_FILE_OPEN, VIRTKEY,CONTROL
"S", ID_FILE_SAVE, VIRTKEY,CONTROL
"P", ID_FILE_PRINT, VIRTKEY,CONTROL
"Z", ID_EDIT_UNDO, VIRTKEY,CONTROL
"X", ID_EDIT_CUT, VIRTKEY,CONTROL
"C", ID_EDIT_COPY, VIRTKEY,CONTROL
"V", ID_EDIT_PASTE, VIRTKEY,CONTROL
VK_BACK, ID_EDIT_UNDO, VIRTKEY,ALT
VK_DELETE, ID_EDIT_CUT, VIRTKEY,SHIFT
VK_INSERT, ID_EDIT_COPY, VIRTKEY,CONTROL
VK_INSERT, ID_EDIT_PASTE, VIRTKEY,SHIFT
VK_F6, ID_NEXT_PANE, VIRTKEY
VK_F6, ID_PREV_PANE, VIRTKEY,SHIFT
END
Таблица сообщений
В библиотеке классов MFC для обработки сообщений используется специальный механизм, который получил название Message Map - таблица сообщений.
Таблица сообщений состоит из набора специальных макрокоманд, ограниченных макрокомандами BEGIN_MESSAGE_MAP и END_MESSAGE_MAP. Между ними расположены макрокоманды, отвечающие за обработку отдельных сообщений.
Макрокоманда BEGIN_MESSAGE_MAP представляет собой заголовок таблицы сообщений. Она имеет два параметра. Первый параметр содержит имя класса таблицы сообщений. Второй параметр указывает его базовый класс.
Если в таблице сообщений класса отсутствует обработчик для сообщения, оно передается для обработки базовому классу, указанному вторым параметром макрокоманды BEGIN_MESSAGE_MAP. Если таблица сообщений базового класса также не содержит обработчик этого сообщения, оно передается следующему базовому классу и т. д.
В том случае если ни один из базовых классов не может обработать сообщение, выполняется обработка по умолчанию, зависящая от типа сообщения.
Стандартные сообщения Windows обрабатываются функцией “default window procedure”
Командные сообщения передаются по цепочке следующему объекту, который может обработать командное сообщение. Более подробно мы расскажем об этой цепочке в главах “Однооконный интерфейс” и “Многооконный интерфейс”
В библиотеке MFC определены несколько макрокоманд, отвечающих за обработку сообщений. Их названия представлены в следующей таблице.
Макрокоманда | Устанавливает методы для обработки сообщений | ||
ON_WM_<name> | Стандартных сообщений операционной системы Windows | ||
ON_REGISTERED_MESSAGE | Зарегистрированные сообщения операционной системы Windows | ||
ON_MESSAGE | Сообщений, определенных пользователем | ||
ON_COMMAND, ON_COMMAND_RANGE | Командных сообщений | ||
ON_UPDATE_COMMAND_UI, ON_UPDATE_COMMAND_UI_RANGE | Сообщений, предназначенных для обновления пользовательского интерфейса | ||
ON_<name>, ON_CONTROL_RANGE | Сообщений от органов управления |
Перечисленные в таблице макрокоманды имеют различное количество параметров в зависимости от типа обрабатываемых ими сообщений.
Таблица сообщений класса CDialogApp
Таблица сообщений класса CDialogApp состоит из макрокоманд BEGIN_MESSAGE_MAP и END_MESSAGE_MAP. Между ними расположены макрокоманды, определяющие сообщения, обрабатываемые данным классом. В таблице определено только одно командное сообщение, имеющее идентификатор ID_HELP. Для его обработки вызывается метод OnHelp базового класса CWinApp.
Необработанные сообщения передаются базовому классу CWinApp, так как он указан во втором параметре макрокоманды BEGIN_MESSAGE_MAP.
//////////////////////////////////////////////////////////////
// Таблица сообщений класса CDialogApp
BEGIN_MESSAGE_MAP(CDialogApp, CWinApp)
//{{AFX_MSG_MAP(CDialogApp)
// ClassWizard размещает в данном блоке макрокоманды для
// обработки сообщений. Не изменяйте содержимое этого блока
//}}AFX_MSG
ON_COMMAND(ID_HELP, CWinApp::OnHelp)
END_MESSAGE_MAP()
Обратите внимание, что внутри таблицы сообщений расположены две макрокоманды AFX_MSG, помещенные за знаками комментария. Сразу после создания приложения между ними нет ни одной макрокоманды. Когда вы будете создавать обработчики сообщений при помощи ClassWizard, он будет располагать новые обработчики между этими комментариями. Не рекомендуется вручную вносить изменения в код, расположенный в блоке AFX_MSG. Используйте для этого средства ClassWizard.
Если вам требуется добавить новый обработчик в таблицу сообщений, без использования ClassWizard, расположите его после блока AFX_MSG и до макрокоманды END_MESSAGE_MAP.
Приложение Dialog содержит еще одну таблицу сообщений, принадлежащую классу диалоговой панели приложения. Мы рассмотрим эту таблицу позже.
Непосредственно после таблицы сообщений главного класса приложения расположено определение конструктора CDialogApp. Этот конструктор вызывается в момент создания объекта приложения. Конструктор, созданный MFC AppWizard, пустой и не выполняет никаких дополнительных действий. Для инициализации приложения используются методы InitInstance и InitApplication.
Таблица сообщений класса CDialogDlg
Файл DialogDlg.cpp содержит таблицу сообщений класса CDialogDlg. Таблица включает три макрокоманды. Как видите, они расположены в блоке AFX_MSG_MAP, поэтому для управления ими используется ClassWizard.
BEGIN_MESSAGE_MAP(CDialogDlg, CDialog)
//{{AFX_MSG_MAP(CDialogDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
При помощи ClassWizard вы можете обнаружить, что макрокоманды выполняют обработку сообщений WM_SYSCOMMAND, WM_PAINT, WM_QUERYDRAGICON, вызывая для этого методы OnSysCommand, OnPaint и OnQueryDragIcon.
Таблица сообщений класса CMainFrame
Класс CMainFrame может получать и обрабатывать сообщения, поэтому в определении класса указана макрокоманда DECLARE_MESSAGE_MAP, а в файле реализации класса MainFrm.cpp, расположена таблица сообщений.
// Таблица сообщений класса CMainFrame
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
//{{AFX_MSG_MAP(CMainFrame)
ON_WM_CREATE()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
Изначально в таблице сообщений расположена единственная макрокоманда ON_WM_CREATE. Эта макрокоманда устанавливает для обработки сообщения WM_CREATE метод OnCreate. Как вы знаете, сообщение WM_CREATE передается функции окна сразу после его создания, но до того, как окно появится на экране.
Таблица сообщений класса CSingleApp
Обратите внимание, что в последней строке определения класса CSingleApp расположена макрокоманда DECLARE_MESSAGE_MAP. Загадочная макрокоманда DECLARE_MESSAGE_MAP определена в файле afxwin.h следующим образом:
#define DECLARE_MESSAGE_MAP() \
private: \
static const AFX_MSGMAP_ENTRY _messageEntries[]; \
protected: \
static AFX_DATA const AFX_MSGMAP messageMap; \
virtual const AFX_MSGMAP* GetMessageMap() const; \
Таким образом, DECLARE_MESSAGE_MAP не является расширением языка Си++, а просто добавляет к вашему классу несколько новых элементов.
Так как в классе CSingleApp расположена макрокоманда DECLARE_MESSAGE_MAP, то он может обрабатывать сообщения и имеет таблицу сообщений. Таблица сообщений класса CSingleApp расположена в файле реализации Single.cpp.
//////////////////////////////////////////////////////////////
// Таблица сообщений класса CSingleApp
BEGIN_MESSAGE_MAP(CSingleApp, CWinApp)
//{{AFX_MSG_MAP(CSingleApp)
ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
//}}AFX_MSG_MAP
// Стандартные команды для работы с документами
ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
// Стандартная команда выбора принтера
ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
END_MESSAGE_MAP()
Кроме команды для обработки командного сообщения ID_APP_ABOUT, расположенного в блоке AFX_MSG_MAP, таблица сообщений содержит еще три макрокоманды, предназначенные для обработки командных сообщений с идентификаторами ID_FILE_NEW, ID_FILE_OPEN, ID_FILE_PRINT_SETUP.
Командные сообщения ID_FILE_NEW, ID_FILE_OPEN, ID_FILE_PRINT_SETUP поступают, когда пользователь выбирает из меню приложения строки с соответствующими идентификаторами. Для обработки этих командных сообщений вызываются методы класса CWinApp.
Таблица сообщений класса CSingleDoc
Макрокоманда IMPLEMENT_DYNCREATE размещается в файле реализации класса. Для класса CSingleDoc этот файл называется SingleDoc.cpp. Обычно MFC AppWizard размещает макрокоманду IMPLEMENT_DYNCREATE непосредственно перед таблицей сообщений класса (если конечно данные класс обрабатывает сообщения).
// Макрокоманда необходима для динамического создания объектов
// CSingleDoc
IMPLEMENT_DYNCREATE(CSingleDoc, CDocument)
// Таблица сообщений класса CSingleDoc
BEGIN_MESSAGE_MAP(CSingleDoc, CDocument)
//{{AFX_MSG_MAP(CSingleDoc)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
Сазу после создания проекта таблица сообщений класса CSingleDoc не содержит обработчиков сообщений. Когда вы продолжите разработку приложения, вы будете добавлять обработчики различных сообщений к классу CSingleDoc и другим классам приложения. Для добавления новых обработчикоов сообщений, а также для внесения других изменений в классы, следует использовать ClassWizard.
Таблица сообщений класса CSingleView
Таблица сообщений класса CSingleView располагается в файле SingleView.cpp. Непосредственно перед ней находится макрокоманда IMPLEMENT_DYNCREATE.
// Объекты класса CSingleView могут создаваться динамически
IMPLEMENT_DYNCREATE(CSingleView, CView)
// Таблица сообщений класса CSingleView
BEGIN_MESSAGE_MAP(CSingleView, CView)
//{{AFX_MSG_MAP(CSingleView)
//}}AFX_MSG_MAP
// Стандартные команды предназначенные для печати документа
ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
END_MESSAGE_MAP()
Таблица текстовых строк
В таблице текстовых строк проекта Dialog определена только одна текстовая строка &About Dialog..., имеющая идентификатор IDS_ABOUTBOX. Эта строка содержит текст нового элемента, который будет добавлен к системному меню главной диалоговой панели приложения. Если пользователь выберет эту строку меню, приложение выведет на экран небольшую диалоговую панель, с краткой информацией о приложении.
//////////////////////////////////////////////////////////////
// Таблица строк
STRINGTABLE DISCARDABLE
BEGIN
IDS_ABOUTBOX "&About Dialog..."
END
Для получения более полной информации вы можете изучить метод GetDocString, который позволяет определить отдельные фрагменты строки, описывающий документ. Описание метода GetDocString смотрите в справочной системе Visual C++.
Второй блок таблицы текстовых строк содержит две строки с идентификаторами AFX_IDS_APP_TITLE и AFX_IDS_IDLEMESSAGE. Строка, имеющая идентификатор AFX_IDS_IDLEMESSAGE, отображается в панели состояния, когда приложение находится в состоянии ожидания.
Когда пользователь создает объект главного класса приложения, он может указать имя приложения. Если это имя не указано, как в нашем приложении, тогда в качестве имени приложения используется строка, имеющая идентификатор AFX_IDS_APP_TITLE.
STRINGTABLE PRELOAD DISCARDABLE
BEGIN
AFX_IDS_APP_TITLE "Single"
AFX_IDS_IDLEMESSAGE "Ready"
END
В следующем блоке текстовых строк определены несколько текстовых строк, имеющих стандартные идентификаторы. Эти строки используются для отображения различной информации в панели состояния.
STRINGTABLE DISCARDABLE
BEGIN
ID_INDICATOR_EXT "EXT"
ID_INDICATOR_CAPS "CAP"
ID_INDICATOR_NUM "NUM"
ID_INDICATOR_SCRL "SCRL"
ID_INDICATOR_OVR "OVR"
ID_INDICATOR_REC "REC"
END
И наконец, последний, самый большой блок текстовых строк содержит краткие описания каждой строки меню приложения. Идентификаторы этих строк соответствуют идентификаторам строк меню, которые они описывают.
Строки, описывающие меню, состоят из двух частей, разделенных символом перевода строки \n. Первая часть строки отображаются в панели состояния, когда пользователь выбирает строки меню. Вторая часть строки содержит краткую подсказку, которая отображается, если поместить указатель мыши на кнопки и подождать несколько секунд. Если вы не нуждаетесь в короткой подсказке для кнопок управляющей панели, то вторую часть строки можно не приводить.
STRINGTABLE DISCARDABLE
BEGIN
ID_FILE_NEW "Create a new document\nNew"
ID_FILE_OPEN "Open an existing document\nOpen"
ID_FILE_CLOSE "Close the active document\nClose"
ID_FILE_SAVE "Save the active document\nSave"
ID_FILE_SAVE_AS " Save the active document with a new
name\nSave As"
ID_FILE_PAGE_SETUP "Change the printing options\nPage Setup"
ID_FILE_PRINT_SETUP "Change the printer and printing
options\nPrint Setup"
ID_FILE_PRINT "Print the active document\nPrint"
ID_FILE_PRINT_PREVIEW "Display full pages\nPrint Preview"
ID_APP_ABOUT " Display program information, version
number and copyright\nAbout"
ID_APP_EXIT "Quit the application; prompts to save
documents\nExit"
ID_FILE_MRU_FILE1 "Open this document"
ID_FILE_MRU_FILE2 "Open this document"
ID_FILE_MRU_FILE3 "Open this document"
ID_FILE_MRU_FILE4 "Open this document"
ID_FILE_MRU_FILE5 "Open this document"
ID_FILE_MRU_FILE6 "Open this document"
ID_FILE_MRU_FILE7 "Open this document"
ID_FILE_MRU_FILE8 "Open this document"
ID_FILE_MRU_FILE9 "Open this document"
ID_FILE_MRU_FILE10 "Open this document"
ID_FILE_MRU_FILE11 "Open this document"
ID_FILE_MRU_FILE12 "Open this document"
ID_FILE_MRU_FILE13 "Open this document"
ID_FILE_MRU_FILE14 "Open this document"
ID_FILE_MRU_FILE15 "Open this document"
ID_FILE_MRU_FILE16 "Open this document"
ID_NEXT_PANE " Switch to the next window pane\nNext Pane"
ID_PREV_PANE "Switch back to the previous window
pane\nPrevious Pane"
ID_WINDOW_SPLIT "Split the active window into panes\nSplit"
ID_EDIT_CLEAR "Erase the selection\nErase"
ID_EDIT_CLEAR_ALL "Erase everything\nErase All"
ID_EDIT_COPY "Copy the selection and put it on the
Clipboard\nCopy"
ID_EDIT_CUT "Cut the selection and put it on the
Clipboard\nCut"
ID_EDIT_FIND "Find the specified text\nFind"
ID_EDIT_PASTE "Insert Clipboard contents\nPaste"
ID_EDIT_REPEAT "Repeat the last action\nRepeat"
ID_EDIT_REPLACE "Replace specific text with different
text\nReplace"
ID_EDIT_SELECT_ALL "Select the entire document\nSelect All"
ID_EDIT_UNDO "Undo the last action\nUndo"
ID_EDIT_REDO "Redo the previously undone action\nRedo"
ID_VIEW_TOOLBAR "Show or hide the toolbar\nToggle ToolBar"
ID_VIEW_STATUS_BAR "Show or hide the status bar\nToggle
StatusBar"
END
Тип исключения
Если вызывается исключение, для которого отсутствует обработчик и не определен универсальный обработчик исключений всех типов, тогда вызывается функция terminate из стандартной библиотеки. Она вызывает функцию abort, завершающую работу программы.
Вы можете определить собственную функцию, которая будет вызываться перед аварийным завершением программы. Для этого вы должны вызвать функцию set_terminate, указав ей в качестве параметра имя вашей функции. Если вы воспользуетесь функцией set_terminate несколько раз, то будет вызываться только функция, указанная в последнем вызове set_terminate.
#include <eh.h>
#include <iostream.h>
#include <process.h>
void FastExit(void);
int main()
{
// Устанавливаем функцию term_func
set_terminate(FastExit);
try
{
// ...
// Вызываем исключение типа int
throw (int) 323;
// ...
}
// Определяем обработчик типа char. Обработчик исключений
// типа int и универсальный обработчик не определены
catch(char)
{
cout << "Exception " << endl;
}
return 0;
}
// Определение функции FastExit
void FastExit()
{
cout << "Exception handler not found" << endl;
exit(-1);
}
Среда Visual C++ версии 4.0 позволяет запретить или разрешить обработку исключений языка Си++. Для управления исключениями выберите из меню Build строку Settings. На экране появится диалоговая панель Project Settings, в которой определяются все режимы работы. Выберите страницу C/C++. Затем из списка Category выберите строку C++ Language. Чтобы включить обработку исключительных ситуаций установите переключатель Enable exception handling.
Универсальный обработчик исключений
В одном блоке try можно вызывать исключения разных типов. В этом случае после блока try должны следовать обработчики для исключений каждого типа. Вы можете определить обработчик, обслуживающий исключения всех типов. Для этого вместо типа в операторе catch надо указать три точки:
catch(...)
{
...
}
Исключения в языке Си++ могут быть различного типа, в том числе они могут быть объектами классов. Вы можете определить несколько обработчиков исключений различного типа. В этом случае исключение будет обрабатывать обработчик соответствующего типа.
Версия
Во всех приложениях, созданных с использованием MFC AppWizard, определен специальный ресурс, содержащий различные сведения о версии приложения (4.8). Приложение Dialog также содержит такой ресурс, который имеет идентификатор VS_VERSION_INFO.
Рис. 4.8. Информация о версии приложения
Вы можете внести изменения в этот ресурс, однако имеет смысл делать это только на конечной стадии разработки приложения. Поэтому мы не станем сейчас подробно останавливаться на описании этого ресурса.
Как и каждое приложение, созданное средствами MFC AppWizard, приложение Single включает ресурс, описывающий версию приложения. В этом ресурсе содержится информация о приложении и ее версии, данные о фирме-разработчике, авторские права.
Приложения, как правило, имеют только один ресурс, содержащий данные о версии, и который имеет имя VS_VERSION_INFO. Приложение может получить данные из ресурса, описывающего версию приложения. Для этого можно воспользоваться функциями GetFileVersionInfo и VerQueryValue.
Сейчас мы не станем подробно останавливаться на этом ресурсе. Большинство приложений не нуждается в определении данного ресурса, тем более на начальном этапе разработки. Поэтому мы продолжим изучения исходного текста самого приложения.
Виртуальные методы
Методы базового класса могут быть переопределены в порожденных классах. Если вы создадите объект порожденного класса и вызовете для него переопределенный метод, то будет вызван именно метод порожденного класса, а не соответствующий метод базового класса. Однако, если вы вызовете переопределенный метод для объекта порожденного класса, используя указатель или ссылку на объект базового класса, будет вызван именно метод базового класса. Иными словами метод вызывается в соответствии с классом указателя на объект, а не с классом самого объекта.
В Си++ вы можете указать, что некоторые методы базового класса, которые будут переопределены в порожденных классах, являются виртуальными. Для этого достаточно указать перед описанием метода ключевое слово virtual. Статический метод не может быть виртуальным. Методы, объявленные в базовом классе виртуальными считаются виртуальными и в порожденных классах.
Если вы переопределите в порожденном классе виртуальный метод, и создадите объект этого класса, то переопределенный метод будет использоваться вне зависимости от того, как он был вызван. При вызове переопределенного метода играет роль только класс объекта для которого вызывается метод.
Все сказанное нами не означает, что никак нельзя вызвать виртуальный метод базового класса, если он был переопределен. Виртуальный метод базового класса можно вызвать, если указать его полное имя, включая имя базового класса.
Виртуальный метод базового класса можно оставить без изменения и не переопределять в порожденном классе. В этом случае, он будет работать как обычный не виртуальный метод.
Следующая программа демонстрирует разницу между виртуальными и невиртуальными методами класса. В базовом классе Figure определены два метода PrintName и PrintDimention, причем метод PrintName определен как виртуальный. От класса Figure наследуется класс Rectangle, в котором методы PrintName и PrintDimention переопределяются.
В программе создается объект класса Rectangle, а затем несколько раз вызываются методы PrintName и PrintDimention. В зависимости от того, как вызывается метод, будет работать метод, определенный в классе Figure или Rectangle.
#include <iostream.h>
// Базовый класс Figure
class Figure
{
public:
// Виртуальный метод
virtual void PrintName(void)
{cout << Figure PrintName << ‘\n’};
// Невиртуальный метод
void PrintDimention(void)
{cout << Figure PrintDimention << ‘\n’};
};
// Порожденный класс Rectangle
class Rectangle : public Figure
{
// Переопределяем виртуальный метод базового класса
virtual void PrintName(void)
{cout << Rectangle PrintName << ‘\n’};
// Переопределяем невиртуальный метод базового класса
void PrintDimention(void);
{cout << Rectangle PrintDimention << ‘\n’};
};
// Главная функция
void main(void)
{
// Определяем объект порожденного класса
Rectangle rectObject;
// Определяем указатель на объект порожденного класса
// и инициализируем его
*Rectangle ptrRectObject = &rectObject;
// Определяем указатель на объект базового класса Figure
// и записываем в него адрес объекта порожденного класса.
*Figure ptrFigObject = &rectObject;
// Вызываем методы класса Rectangle, используя имя объекта
rectObject.PrintName;
rectObject.PrintDimention;
cout << ‘\n’;
// Вызываем методы класса базового класса Figure
rectObject.Figure::PrintName;
rectObject.Figure::PrintDimention;
cout << ‘\n’;
// Вызываем методы класса Rectangle, используя указатель на
// объекты класса Rectangle
ptrRectObject->PrintName;
ptrRectObject->PrintDimention;
cout << ‘\n’;
// Вызываем методы класса Rectangle, используя указатель на
// объекты класса Figure
ptrFigObject->PrintName;
ptrFigObject->PrintDimention;
}
Если вы запустите приведенную выше программу, она выведет на экран следующую информацию:
Rectangle PrintName
Rectangle PrintDimention
Figure PrintName
Figure PrintDimention
Rectangle PrintName
Rectangle PrintDimention
Figure PrintName
Figure PrintDimention
Виртуальный метод GetRuntimeClass
Виртуальный метод GetRuntimeClass возвращает указатель на структуру CRuntimeClass, описывающую класс объекта, для которого метод был вызван:
virtual CRuntimeClass* GetRuntimeClass() const;
Для каждого класса, наследованного от CObject поддерживается своя структура CRuntimeClass. Если вы желаете использовать метод GetRuntimeClass в своем классе, наследованном от CObject, вы должны поместить в реализации класса макрокоманду IMPLEMENT_DYNAMIC или IMPLEMENT_SERIAL.
Структура CRuntimeClass содержит различную информацию о классе. Ниже перечислены несколько основные полей этой структуры.
Поле структуры CRuntimeClass | Описание | ||
const char* m_pszClassName | Указатель на строку, закрытую двоичным нулем, в которой расположено имя класса | ||
int m_nObjectSize | Размер объектов класса | ||
WORD m_wSchema | Номер схемы (schema number) класса. Используется при автоматическом сохранении и восстановлении объектов класса в файле. Если объекты класса не могут быть сохранены и восстановлены (в объявлении класса отсутствует макрокоманда IMPLEMENT_SERIAL), m_wSchema содержит значение -1 | ||
void (*m_pfnConstruct) (void* p) | Указатель на конструктор класса, используемый по умолчанию. Этот конструктор не имеет параметров | ||
CRuntimeClass* m_pBaseClass | Указатель на структуру CRuntimeClass, содержащую аналогичную информацию о базовом классе |
Кроме перечисленных элементов структуры, она содержит метод CreateObject. Этот метод позволяет динамически создать объект соответствующего класса уже во время работы приложения. Если объект класса не создан, метод возвращает значение NULL.
CObject* CreateObject();
Виртуальный метод Serialize
Виртуальный метод Serialize вызывается, когда надо сохранить или восстановить объект класса из файла на диске. Вы должны переопределить этот метод в своем классе, чтобы сохранить или восстановить его элементы. Переопределенный метод Serialize должен вызывать метод Serialize базового класса:
virtual void Serialize(CArchive& ar);
throw(CMemoryException);
throw(CArchiveException);
throw(CFileException);
Ниже прототипа метода Serialize мы указали исключения, которые могут быть им вызваны. Более подробно об исключениях вы можете прочитать в разделе “Исключения - класс CException” данной главы.
Включение в класс новых элементов данных
ClassWizard позволяет включать в класс не только новые методы, но также и элементы данных, связанные с полями диалоговых панелей, форм просмотра и форм для просмотра записей баз данных и полей наборов записей. ClassWizard, использует специальные процедуры, чтобы привязать созданные им элементы данных класса к полям диалоговых панелей. Эти процедуры носят название обмен данными диалоговой панели и проверка данных диалоговой панели (Dialog Data Exchange and Dialog Data Validation - DDX/DDV). Чтобы привязать поля из наборов записей к переменным, используются процедуры обмена данными с полями записей - (Record Field Exchange - RFX).
Процедуры DDX/DDV и RFX значительно упрощают программисту работу с диалоговыми панелями. Они позволяют связать поля диалоговых панелей и переменные. Когда пользователь редактирует поля диалоговых панелей, процедуры DDV проверяют введенные значение и блокируют ввод запрещенных значений. Затем процедуры DDX автоматически копируют содержимое полей диалоговых панелей в привязанные к ним элементы данных класса. И наоборот, когда приложение изменяет элементы данных класса, привязанные к полям диалоговой панели, процедуры DDX могут сразу отобразить новые значения полей на экране компьютера.
Базовые сведения о процедурах обмена данных и проверке введенных значений мы привели в разделе “Диалоговая панель” главы “Введение в MFC”. А теперь мы рассмотрим средства, предоставляемые ClassWizard.
Откройте ClassWizard и выберите из списка Class name имя класса, к которому надо добавить новый элемент данных. Теперь из списка Control IDs выберите идентификатор органа управления диалоговой панели, к которому надо привязать элемент данных класса. Если из списка Class name выбрать имя класса, не соответствующего диалоговой панели, форме просмотра, форме просмотра записей или набору записей, то список Control IDs остается пустым.
В столбце Type отображается тип элемента данных, а в столбце Member имя этого элемента данных. Если к органу управления не подключен элемент данных класса, то соответствующие позиции в столбцах Type и Member остаются незаполненными.
Включение в класс новых методов
Очень удобно использовать ClassWizard для включения в состав класса новых методов. Вы можете добавлять к классу методы, служащие для обработки сообщений Windows и команд от объектов, а также методы, переопределяющие виртуальные методы базовых классов.
Выберите из списка Class name имя класса, к которому надо добавить новый метод. Теперь из списка Object IDs выберите идентификатор объекта, для которого надо создать метод. Если вам надо переопределить виртуальный метод базового класса, выберите из этого списка имя самого класса.
Обратите внимание на список Messages. Если вы выбрали в списке Object IDs идентификатор объекта, то в списке Messages отображаются сообщения, которые этот объект может вырабатывать. Например, если в качестве объекта фигурирует строка меню, то в списке сообщений отображаются два сообщения - COMMAND и UPDATE_COMMAND_UI. Сообщение COMMAND передается, когда данная строка выбирается из меню, а сообщение UPDATE_COMMAND_UI - когда открывается меню, содержащее эту строку.
Выберите из списка Messages то сообщение, для которого надо создать обрабатывающий его метод и нажмите кнопку Add Function. Откроется диалоговая панель Add Member Function. В ней надо определить название нового метода. По умолчанию вам будет предложено название, построенное на основе имени объекта и имени самого сообщения. Нажмите кнопку OK. ClassWizard добавит описание нового метода в класс, а также сформирует шаблон для этого метода, учитывающий все особенности объекта и сообщения.
Название сообщения, для которого создан метод, или имя переопределенного метода из списка Messages выделяется жирным шрифтом. Название нового метода появится в списке методов класса - Member Function, расположенном в нижней части диалоговой панели ClassWizard. Перед названием методов размещаются символы
или . Символ располагается перед виртуальными методами, а символ перед методами, обрабатывающими сообщения Windows.Чтобы перейти к редактированию метода класса, установите на его имя указатель мыши и сделайте двойной щелчок левой кнопкой мыши или нажмите кнопку Edit Code. Откроется окно редактора и курсор встанет непосредственно в начало определения метода.
ClassWizard не только позволяет добавить в класс новые методы, но и удалить их. ClassWizard самостоятельно удалит объявление метода из класса. Чтобы удалить метод, выберите его название из списка Member Function и нажмите кнопку Delete Function.
Встраивание
В некоторых случаях более удобно и эффективно выполнять подстановку тела функции вместо ее вызова. Непосредственная подстановка тела функции позволит сэкономить время процессора на вызове функции. В языке Си этого можно достичь при помощи директивы препроцессора #define. Однако неправильное использование директивы может стать причиной ошибок.
Си++ предусматривает специальный механизм для встраивания функций. Чтобы указать компилятору, что данную функцию необходимо встраивать, перед ее объявлением или определением надо указать ключевое слово inline:
inline unsigned int Invert(unsigned int number) {
return (~number);
}
Библиотеки системного программиста” мы ориентировались
В предыдущих томах серии “ Библиотеки системного программиста” мы ориентировались в первую очередь на язык программирования Си. Даже если некоторые программы были написаны на Си++, то богатые возможности этого языка практически не использовались.
Сегодня уровень сложности программного обеспечения настолько высок, что разработка коммерческих приложений Windows с использованием средств одного только языка Си значительно затрудняется. Программист должен будет затратить массу времени на решение стандартных задач по созданию многооконного интерфейса. Реализация технологии связывания и встраивания объектов - OLE потребует от программиста еще более тяжелой работы.
Чтобы облегчить работу программиста практически все современные компиляторы с языка Си++ содержат специальные библиотеки классов. Такие библиотеки включают в себя практически весь программный интерфейс Windows и позволяют пользоваться при программировании средствами более высокого уровня, чем обычные вызовы функций. За счет этого значительно упрощается разработка приложений, имеющих сложный интерфейс пользователя, облегчается поддержка технологии OLE и взаимодействие с базами данных.
Современные интегрированные средства разработки приложений Windows позволяют автоматизировать процесс создания приложения. Для этого используются генераторы приложений. Вы отвечаете на вопросы генератора приложений и определяете свойства приложения - поддерживает ли оно многооконный режим, технологию OLE, трехмерные органы управления, справочную систему. Генератор приложений создаст приложение, отвечающее вашим требованиям и предоставит вам его исходные тексты. Пользуясь ими как шаблоном, вы сможете быстро разрабатывать свои приложения.
Подобные средства автоматизированного создания приложений включены в компилятор Microsoft Visual C++ и называются MFC AppWizard - волшебник. Действительно, то что делает MFC AppWizard сродни волшебству. Заполнив несколько диалоговых панелей, можно указать характеристики приложения и получить его исходные тексты с обширными комментариями. MFC AppWizard позволяет создавать однооконные и многооконные приложения, а также приложения, не имеющих главного окна - вместо него используется диалоговая панель. Вы можете включить поддержку технологии OLE, баз данных, справочной системы.
Возможности MFC AppWizard позволяют всего за несколько минут создать собственный многооконный редактор текста с возможностью сервера и клиента OLE. При этом вы не напишите ни единой строчки текста, а исходный текст приложения, созданный MFC AppWizard, можно сразу оттранслировать и получить выполнимый модуль приложения, полностью готовый к использованию.
Конечно волшебство MFC AppWizard не всесильно. Прикладную часть приложения вам придется разрабатывать самостоятельно. Исходный текст приложения, созданный MFC AppWizard станет только основой, к которой надо подключить остальное. Но не стоит разочаровываться - работающий шаблон приложения это уже половина всей работы. Исходные тексты приложений автоматически полученных от MFC AppWizard могут составлять сотни строк текста. Набор его вручную был бы очень утомителен.
Надо отметить, что MFC AppWizard создает исходные тексты приложений только с использованием библиотеки классов MFC. Поэтому только изучив MFC вы сможете пользоваться средствами автоматизированной разработки и создавать свои приложения в кратчайшие сроки.
Введение в MFC
На сегодня существует более десятка версий библиотеки MFC. Практически каждая новая версия среды Microsoft Visual C++ (MSVC) поставляется с обновленной версией библиотеки MFC, в которой исправлены обнаруженные ошибки и добавлены новые классы.
Все версии библиотеки MFC можно разделить на две группы. К первой относятся 16-разрядные версии MFC, предназначенные для операционных систем Windows 3.1 и 3.11. Вторая группа включает версии MFC, предназначенные для 32-разрядных операционных систем Windows NT и Windows 95. В следующей таблице перечислены все основные версии Microsoft Visual C++ и соответствующие им версии MFC.
Среда разработки | Версия MFC | Разрядность | |||
Microsoft C/C++ версии 7.0 | 1.0 | 16 | |||
MSVC 1.0 | 2.0 | 16 | |||
MSVC 1.1 | 2.1 | 32 | |||
MSVC 1.5 | 2.5 | 16 | |||
MSVC 2.0 | 2.51 | 16 | |||
MSVC 2.1 | 2.52 | 16 | |||
MSVC 2.2 | 2.52b | 16 | |||
MSVC 4.0 | 2.5c | 16 | |||
MSVC 2.0 | 3.0 | 32 | |||
MSVC 2.1 | 3.1 | 32 | |||
MSVC 2.2 | 3.2 | 32 | |||
MSVC 4.0 | 4.0 | 32 | |||
MSVC 4.1 | 4.1 | 32 |
Вы легко можете определить версию библиотеки MFC, установленной на вашем компьютере. Для этого достаточно просмотреть включаемый файл afxver_.h, расположенный в каталоге include библиотеки MFC. В одной из первых строк этого файла определена константа _MFC_VER, содержащая версию MFC:
// Microsoft Foundation Classes версии 4.00
#define _MFC_VER 0x0400
Мы будем рассматривать библиотеку MFC версий 3.0, 4.0 и 4.1, однако приведенная информация верна и для других версий MFC. В тех случаях, когда эти версии имеют существенные отличия мы будем на это специально указывать.
Ввод/вывод
Как вы знаете, операторы << и >> выполняют сдвиг числового значения влево и вправо на опеределенное число бит. В программах, приведенных в нашей книге, эти операторы также используются для ввода информации с клавиатуры и вывода на экран.
Если с левой стороны от оператора << расположен символ cout, то этот оператор осуществляет вывод на экран информации, указанной справа от оператора. Форма, в которой выполняется вывод на экран, зависит от типа выводимого значения. Используя оператор <<, вы можете отображать на экране текстовые строки, а также значения переменных различных типов. В качестве левого параметра оператора << можно использовать не только cout, но также результат работы предыдущего оператора <<. Это позволяет строить цепочки из операторов <<. Чтобы перейти к отображению следующей строки, вы можете передать cout значение \n.
Так, например, следующий фрагмент кода отображает на экране значения переменных iInt, cChar и szString с соответствующими комментариями:
cout << “Значение переменной iInt = ”;
cout << iInt;
cout << “\n”;
cout << “Значение переменной cChar = ” << cChar << “\n”;
cout << “Строка szString = ” << szString << “\n”;
Оператор >> и символ inp предназначены для ввода данных. Они позволяют пользователю ввести с клавиатуры значение какой-либо переменной. Ниже мы привели пример, в котором для ввода целочисленного значения используется inp и оператор >>:
int iNum;
cout << “Введите целочисленное значение:”;
cin >> iNum;
Чтобы воспользоваться возможностями потокового ввода/вывода, необходимо включить в программу файл iostream.h.
Забегая вперед, скажем, что символы inp и outp, которые иногда называют потоками, представляют собой объекты специального класса, предназначенного для ввода и вывода информации. Операторы << и >> переопределены в этом классе и выполняют новые функции. О переопределении операторов вы можете прочитать в разделе “Перегрузка операторов”.
Забегая вперед
Вы, конечно, можете определить таблицу сообщений класса вручную, однако наиболее удобно воспользоваться для этой цели средствами ClassWizard. ClassWizard не только позволит в удобной форме выбрать сообщения, которые должен обрабатывать ваш класс. Он включит в состав класса соответствующие методы-обработчики. Вам останется только вставить в них необходимый код. К сожалению использовать все возможности ClassWizard можно только в том случае, если приложение создано с использованием средств автоматизированного программирования MFC AppWizard.
Однако ClassWizard не всесилен. Так он не позволяет определить один метод для обработки нескольких сообщений. Как вы уже знаете, для этих целей предназначены макрокоманды ON_COMMAND_RANGE и ON_CONTROL_RANGE. Если вы решите воспользоваться этими макрокомандами, вам придется редактировать таблицу сообщений непосредственно, без использования ClassWizard.
Более подробно об использовании ClassWizard для создания обработчиков сообщений вы можете прочитать в разделе “Средства ClassWizard” главы “Приложение с главной диалоговой панелью”. А сейчас мы рассмотрим механизм обработки сообщений, используемый MFC, на примере нескольких приложений.
Задание параметров функции по умолчанию
Еще одна интересная возможность, которая появляется у вас после перехода от Си к Си++, позволяет при определении функций задавать некоторые ее параметры по умолчанию. Вызывая такую функцию, можно не указывать параметры, заданные по умолчанию.
Если большинство вызовов функции выполняется с одинаковыми параметрами, это позволяет сократить текст программы, а главное, уменьшить возможность совершения ошибок во время набора параметров функции.
Параметры по умолчанию можно задать во время объявления функции или во время ее определения. По умолчанию задают только последние параметры функций:
int Summa(int first, int second, int third=0, int fourth=0) {
return(first + second + third + fourth);
}
Функцию Summa можно использовать для сложения четырех, трех или двух чисел. Если складываются два числа, то третий и четвертый параметр можно опустить:
void main() {
int value1 = 10, value2 = 20, value3 = 30, value4 = 40;
int result;
// Вызываем функцию с четырьмя параметрами
result = Summa(value1, value2, value3, value4);
print(“Сумма четырех чисел равна %d”, result);
// Вызываем функцию с тремя параметрами
result = Summa(value1, value2, value3);
print(“Сумма трех чисел равна %d”, result);
// Вызываем функцию с двумя параметрами,
// последний параметр задается по умолчанию
result = Summa(value1, value2);
print(“Сумма первых двух чисел равна %d”, result);
}
одной книги такого объема явно
Конечно, одной книги такого объема явно недостаточно, чтобы полно изучить все средства Visual C++ и библиотеки MFC. Поэтому мы предполагаем продолжить изучение MFC в следующих книгах серии “Библиотека системного программиста”.
В следующей книге из серии “Библиотека системного программиста” мы более подробно рассмотрим классы, составляющие основу приложений MFC, в том числе мы расскажем об элементах классов CWinApp, CView, CDocument и СDialog, для описания которых нам не хватило места в этой книге. Вы узнаете больше о классах, представляющих органы управления диалоговых панелей - CAnimateCtrl, CBitmapButton, CComboBox, CEdit, CListBox, CListCrtl, CRichEditCtrl, CTreeCtrl и некоторых других.
Отдельная глава книги будет посвящена программированию приложений, имеющих многооконный интерфейс. Мы представим пример приложения, которое может одновременно работать с документами различных типов.
Мы также не оставим без внимания возможности библиотеки MFC по работе с базами данных. Нами будут рассмотрены классы, позволяющие приложению обращаться с базами данных через ODBС и DAO. В качестве примера мы приведем несколько приложений, работающих с базами данных.
Закрытие файлов
После того, как вы поработали с файлом, его надо закрыть. Класс CFile имеет для этого специальный метод Close:
virtual void Close();
throw(CFileException);
Метод закрывает файл. Если вы создали объект класса CFile и открыли файл, а затем объект удаляется, то связанный с ним файл закрывается автоматически с помощью деструктора.
Запись и восстановление объектов
Одна из задач, решаемых программистом при разработке приложений, которые могут создавать и редактировать документы различных типов, например текстовые или графические, заключается в том, чтобы предоставить пользователю возможность записать внутреннее представление документа в файл и восстановить его.
Чтобы облегчить программисту решение этой задачи, библиотека классов MFC определяет механизм записи и восстановления объектов (serialization). Поддержка механизма записи и восстановления объектов осуществляется средствами класса CObject.
Классы, наследованные от CObject также могут обеспечивать работу механизма записи и восстановления объектов. Для этого при объявлении класса надо указать макрокоманду DECLARE_SERIAL, а при определении - макрокоманду IMPLEMENT_SERIAL.
Макрокоманду DECLARE_SERIAL надо поместить перед описанием вашего класса во включаемом файле. Непосредственно после имени макрокоманды надо указать имя класса - class_name:
DECLARE_SERIAL(class_name)
Макрокоманду IMPLEMENT_SERIAL надо указать перед определением класса в файле исходного текста приложения, имеющего расширение CPP. Прототип макрокоманды IMPLEMENT_SERIAL представлен ниже:
IMPLEMENT_SERIAL(class_name, base_class_name, wSchema)
Параметр class_name определяет имя вашего класса, base_class_name - имя базового класса из которого непосредственно наследуется ваш класс. Последний параметр wSchema - это число типа UINT, определяющее версию программы. Если вы разработаете новую версию своего приложения и измените набор данных, которые необходимо записать в файл, измените параметр wSchema.
В классе должны быть определены специальные методы для записи и восстановления состояния объектов этого класса. Обычно эти методы сохраняют и восстанавливают элементы данных из класса. Таким образом, объекты класса сами отвечают за то, как они сохраняют и восстанавливают свое состояние.
Методы, сохраняющие и восстанавливающие состояние класса, взаимодействуют с объектом класса CArchive, который осуществляет непосредственную запись и чтение информации из файла на диске.
Класс CObject содержит виртуальный метод Serialize, отвечающий за запись и чтение объектов классов, наследованных от класса CObject:
virtual void Serialize(CArchive& ar);
throw(CMemoryException);
throw(CArchiveException);
throw(CFileException);
В качестве параметра ar, методу передается указатель на объект класса CArchive, используемый для записи и восстановления его состояния из файла. Чтобы узнать, какую операцию должен выполнить метод Serialize, воспользуйтесь методами CArchive::IsLoading или CArchive::IsStoring.
Новая реализация метода Serialize должна первым делом вызвать метод Serialize базового класса. Это гарантирует, что при сохранении и восстановлении объекта будут обработаны все элементы всех базовых классов.
Метод Serialize вызывается объектами класса CArchive когда приложение читает или записывает этот объект, вызывая методы CArchive::ReadObject или CArchive::WriteObject. Сразу отметим, что с методами CArchive::ReadObject и CArchive::WriteObject непосредственно связаны операторы записи << и чтения >>.
Перед тем как создать объект класса CArchive, необходимо создать объект класса CFile. Связывая с объектом CFile файл на диске, имейте в виду, что если вы желаете записать объект файл, то файл надо открыть на запись, а если надо считать файл с диска и загрузить из него данные в объект, открыть файл надо для чтения.
Конструктор класса CArchive имеет следующий вид:
CArchive(CFile* pFile, UINT nMode, int nBufSize = 512,
void* lpBuf = NULL);
throw(CMemoryException, CArchiveException, CFileException);
Параметр pFile должен содержать указатель на объект класса CFile, из которого будут считываются или записываться данные. Перед вызовом конструктора файл, связанные с объектом pFile должен быть уже открыт.
Параметр nMode определяет, будут данные записываться в файл или считываться из него. Параметр nMode может принимать одно из трех значений CArchive::load, CArchive::store или CArchive::bNoFlushOnDelete, описанных в следующей таблице.
Константа |
Описание |
CArchive::load |
Данные считываются из файла на диске. Впоследствии они будут записаны в восстанавливаемый объект. В этом случае файл, определенный параметром pFile, должен быть открыт для чтения. |
CArchive::store |
Данные будут записываются в файл на диске. Файл, определенный параметром pFile должен быть открыт для записи. |
CArchive:: bNoFlushOnDelete |
Когда вызывается деструктор класса CArchive, он автоматически вызывает метод Flush для файла pFile. Если вы укажите этот флаг, то метод Flush вызван не будет. Чтобы предотвратить потерю данных, вы должны будете перед вызовом деструктора закрыть данный файл. |
Конструктор класса CArchive сам получает у операционной системы блок оперативной памяти для буфера. Однако с помощью необязательного параметра lpBuf вы можете предоставить собственный буфер. В этом случае параметр nBufSize должен указывать размер этого буфера.
Созданный вами объект класса CArchive можно будет использовать только для записи или только для чтения. Если вам надо сначала считать данные, а потом записать, следует создать для этого два отдельных объекта класса CArchive.
Не используйте методы класса CFile для доступа к файлу во время его использования совместно с объектами класса CArchive. Запись, чтение или перемещение указателя файла может вызвать нарушения во внутренней структуре файла архива.
Когда созданный объект класса CArchive передается функции Serialize, вы можете определить, предназначен он для записи или для чтения, вызвав метод CArchive::IsLoading или CArchive::IsStoring.
Метод IsStoring возвращает ненулевое значение, если данный объект предназначен для записи в файл и нуль в противном случае:
BOOL IsStoring() const;
Метод IsLoading является полной противоположностью метода IsStoring. Он возвращает ненулевое значение, если данный объект предназначен для чтения из файла и нуль в противном случае. Вы можете использовать любой метод IsLoading или IsStoring.
Основное предназначение объекта класса CArchive заключается в том, что объекты вашего класса могут посредством него записать свое состояние в файл, а затем при необходимости восстановить его. Для этого в классе CArchive определены операторы записи в файл << и чтения из файла >>. Вы также можете использовать методы WriteString, Write, ReadString и Read. Опишем эти операторы и методы более подробно.
Запись в архивный файл
Когда приложение желает сохранить состояние объекта данного класса в файле, оно вызывает для него метод Serialize. В качестве параметра этому методу передается указатель на объект класса CArchive, связанные с файлом, открытым на запись.
В этом случае реализация метода Serialize должна сохранить в файле все элементы данных, которые потом потребуется восстановить. Для этого необходимо воспользоваться оператором << или методами WriteString и Write, определенными в классе CArchive.
Оператор << можно использовать для записи в архивный файл переменных простых типов, например long, int, char и объектов других классов, которые наследованы от класса CObject.
В одной строке программы можно использовать оператор << несколько раз. Такая запись сокращает исходный код программы и делает его более легким для понимания, так как все операторы << будут сгруппированы вместе.
Для записи в архивный файл массивов удобнее использовать метод Write класса CArchive:
void Write(const void* lpBuf, UINT nMax);
throw(CFileException);
Он позволяет записать в файл определенное количество байт из указанного буфера памяти.
Параметр lpBuf является указателем на сохраняемую область данных, а параметр nMax определяет количество байт из этой области, которое надо записать в файл.
Если требуется сохранить строку символов, закрытую нулем, то гораздо удобнее вместо метода Write использовать метод WriteString. Метод WriteString записывает в архивный файл строку lpsz:
void WriteString(LPCTSTR lpsz);
throw(CFileException);