Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Microsoft Visual C++ и MFC

© Александр Фролов, Григорий Фролов
Том 24, М.: Диалог-МИФИ, 1993.

[Назад] [Содеожание] [Дальше]

Простейший графический редактор

В предыдущем разделе мы создали при помощи средств 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. Режим предварительного просмотра документа перед печатью

Создание нового документа

Документ, который вы можете создать в приложении 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();
}

Сохранение и восстановление документа на диске

Построенное вами приложение можно использовать для рисования и печати документов, но оно не позволяет сохранять и загружать документ из файла на диске. Вы можете выбрать строку 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, постройте проект. Запустите полученное приложение. Теперь вы сможете записать документ в файл на диске, а затем загрузить его снова.

Для забывчивых пользователей

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

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

void SetModifiedFlag(BOOL bModified = TRUE);

Если документ изменен, установите флаг модификации, вызвав метод SetModifiedFlag с параметром bModified, равным TRUE или без параметра. В случае необходимости вы можете убрать установленный флаг. Для этого надо вызвать метод SetModifiedFlag с параметром bModified, равным FALSE.

Мы должны добавить вызов метода SetModifiedFlag в методах OnLButtonDown и OnRButtonDown, выполняющих модификацию документа. Вызов метода можно разместить в любом месте, например, сразу после добавления к массиву arrayFig, представляющему документ, нового элемента.


//////////////////////////////////////////////////////////////
// Метод OnLButtonDown класса CSingleView 
void CSingleView::OnLButtonDown(UINT nFlags, CPoint point) 
{
	// ...

	// Добавляем к массиву, определяющему документ, новый 
	// элемент
	pDoc->arrayFig.Add(OneFigure);

	// Устанавливаем флаг изменения документа
	pDoc->SetModifiedFlag();

	CView::OnLButtonDown(nFlags, point);
}

//////////////////////////////////////////////////////////////
// Метод OnRButtonDown класса CSingleView 
void CSingleView::OnRButtonDown(UINT nFlags, CPoint point) 
{
	// ...

	// Добавляем к массиву, определяющему документ, новый 
	// элемент
	pDoc->arrayFig.Add(OneFigure);

	// Устанавливаем флаг изменения документа
	pDoc->SetModifiedFlag();
	
	CView::OnRButtonDown(nFlags, point);
}
[Назад] [Содеожание] [Дальше]