![]() |
![]() |
![]() |
Класс
CPolygon
В соответствии
с архитектурой «документ — представление» мы должны ввести в класс документа
некоторые новые структуры данных для хранения информации о файлах документов,
обнаруженных в выбранной пайке или логическом диске. Файловые пути хранятся
в контейнере текстовых строк типа vector<cstring>. Пришлось отказаться
от использования класса string из библиотеки STL, так как многие используемые
нами методы классов и API-функции требуют в качестве параметров переменные типа
CString из библиотеки MFC. Преобразование типов из CString в string и обратно
потребует дополнительных усилий, поэтому проще взять CString в качестве аргумента
шаблона vector. Для изображения мини-чертежей найденных документов в правом
представлении (CRightview) расщепленного окна (CTreeFrame) удобно ввести в рассмотрение
класс CDPoint и тип данных VECPTS:
typedef
vector<CDPoint,
allocator<CDPoint> > VECPTS;
Эти типы данных
мы разработали во втором уроке для обозначения множества реальных (World) координат
точек изображаемых объектов. Перенесите указанные объявления из проекта My (см.
урок 2) и вставьте их в начало файла TreeDoc.h до объявления класса CTreeDoc,
но после директивы #pragma once. Вставляя объявление новых классов в тот же
файл, мы экономим свои силы в процессе отладки приложения, потому что нам не
надо так часто переключать окна и заботиться о видимости новых типов данных.
Однако довольно часто при этом становятся невидимыми для новых классов старые
типы, которые декларированы в этом же файле, но чуть ниже. Такие проблемы легко
решаются с помощью упреждающих объявлений класса. Вставьте сразу за директивой
#pragma once такое объявление:
class
CTreeDoc; // Упреждающее
объявление
В конец файла
StdAfx.h вставьте строки, которые обеспечивают видимость некоторых ресурсов
библиотеки STL:
#include
<vector>
using
namespace
std;
Кроме того,
нам понадобится новый полноценный класс, который инкапсулирует функциональность
изображаемого объекта. Объекты этого класса должны быть устойчивы, то есть должны
уметь сохранять и восстанавливать свое состояние, также они должны уметь правильно
изображать себя в любом контексте устройства, который будет подан им в качестве
параметра. Все перечисленные свойства «почти бесплатно» получают классы, произведенные
от класса библиотеки MFC cobject. Вставьте в файл TreeDoc.h после строки с определением
типа VECPTS, но до объявления класса CTreeDoc, объявление класса CPolygon:
class CPolygon: public CObject
{
DECLARE_SERIAL(CPolygon)
public:
CTreeDoc
*m_pDoc; // Обратный указатель
VECPTS
m_Points; // Контейнер вещественных точек
UINT
m_nPenWidth; // Толщина пера
COLORREF
m PenColor; // Цвет пера
COLORREF
m_BrushColor; // Цвет кисти
CDPoint
m_ptLT; // Координата левого верхнего угла
CDPoint
m_ptRB; // Координата правого нижнего угла
//======
Конструктор по умолчанию
CPolygon
() ;
//======
Конструктор копирования
CPolygon(const
CPolygons poly);
//======
Операция присвоения
CPolygons operator= (const CPolygons poly);
//======
Операция выбора i-той точки
CDPointS
operator!] (UINT i);
//======
Вычисление обрамляющего прямоугольника
void GetRect(CDPointS ptLT, CDPointS ptRB);
//======
Установка обратного указателя
void
Set (CTreeDoc *p); //====== Изменение атрибутов
void
SettCTreeDoc *p,COLORREF bCl,COLORREF pCl,UINT pen);
//======
Создание трех простых заготовок
void MakeStar();
// Звезда
void MakeTria();
// Треугольник
void
MakePent(); // Пятиугольник
//======
Изображение в контексте устройства
virtual
void
Draw (CDC *pDC,
bool
bContour);
//======
Сохранение и восстановление данных
virtual void Serialize(CArchiveS ar);
virtual
~CPolygon(); // Деструктор
//======
Новый тип данных: контейнер полигонов
typedef
vector<CPolygon, allocator<CPolygon> > VECPOLY;
Каждый объект
класса CPolygon должен иметь связь с данными документа. Это осуществляется путем
запоминания адреса документа в переменной m_pDoc, которая играет роль обратного
указателя. Такой прием, когда вложенный объект помнит адрес объемлющей его структуры
данных, очень распространен в объектно-ориентированном программировании. Он
существенно упрощает обмен данными между двумя объектами.
Примечание
Здесь трудно обойтись без специального метода установки обратного указа-теля, в нашем случае метода Set. Дело в том, что при создании документа надо сначала создать вложенные в него объекты других классов (вспомните правило: «C++ уважает гостей»). Но в этот момент им нельзя передать адрес документа, так как он еще не создан. В таких случаях поступают следующим образом. В заголовке конструктора документа создают пустые объекты (вызывают default-конструкторы вложенных объектов), а затем в теле конструктора документа, когда он уже существует, для вложенных объектов вызывают метод, устанавливающий обратный указатель. При этом объекту передают указатель на документ (на объект собственного класса). Например: m_Poly.Set(this);
Обилие методов
класса CPolygon сделано «на вырост». Сейчас каждый документ для простоты представлен
одним полигоном. Реальные конструкции можно задать в виде множества полигонов.
При этом каждый из них должен знать свои габариты. Метод GetRect позволяет вычислять
и корректировать габариты полигона. Если вы будете применять эти идеи в более
сложном проекте, то вам понадобится множество других методов. Например, методы,
определяющие факт самопересечения полигона или взаимного их пересечения.
Главными методами,
которые реализуют концепцию архитектуры «документ — представление», являются
Serialize и Draw. Метод Serialize позволяет общаться с файлами. Его особенность
состоит в том, что он позволяет как записывать все данные объекта в файл, точнее
в архив, так и читать их из файла. Здесь опять проявятся преимущества наследования
от cobject, так как объекты классов, имеющих такого авторитетного родителя,
обычно сами умеют себя сериализовывать.
Примечание
Термин «сериализация» приходится брать на вооружение, так как он довольно емкий, и чтобы его заменить, надо произнести довольно много слов о последовательном (in series) помещении данных объекта в архив, который связан с файлом. Кроме того, надо сказать о том, что в классе CArchive переопределены операции « и ». Просмотрите почти пустое тело функции Serialize в классе документа. Оно, тем не менее, намекает нам, как разделяются две разновидности общения с архивом. Вызов функции CArchive::IsStoring() возвращает ненулевое значение в случае, если архив используется для записи данных.
Новый класс CPolygon должен иметь родителя CObject, с тем чтобы он мог воспользоваться его мощным оружием — сериализацией. При этом в объявлении класса должен присутствовать макрос:
DECLARE_SERIAL(CPolygon)
который влечет
свое продолжение — другой макрос
IMPLEMENT_SERIAL(CPolygon,
CObject, 1)
Последний должен
быть расположен в файле реализации класса. Третий параметр (wSchema) этой макроподстановки
задает номер версии приложения. Номер схемы кодируется и помещается в архив
вместе с другими сохраняемыми данными. Это позволяет корректно обойтись в такой
ситуации.
Предположим,
что имеются файлы с расширением mgn, в которых хранятся данные о магнитах, созданных
нашим приложением. Затем допустим, что мы внесли изменения в коды приложения
и добавили в класс CPolygon еще одно какое-то поле данных. Теперь, записывая
данные в архив (файл), также получим файл с расширением mgn, но другого формата.
После этого мы не сможем правильно читать старые файлы. Если не предпринять
никаких мер, то данные будут прочитаны неверно, а это часто приводит к непредсказуемому
поведению программы. Механизм версий справляется с этой проблемой, но вы не
должны забывать вовремя менять номер версии. При каждом изменении в структуре
сохраняемых данных следует изменять номер версии. При попытке прочитать файл,
соответствующий другой версии, каркас приложения просто выдаст сообщение о несовпадении
версий и закроет файл данных.
С учетом сказанного
рассмотрим, как должна выглядеть реализация нового класса. Следующие функции
и макросы необходимо поместить в начало файла TreeDoc.cpp, после директив препроцессора:
IMPLEMENT_SERIAL(CPolygon,
CObject, 1)
//======
Конструктор по умолчанию
CPolygon::CPolygon()
{
m_pDoc
= 0; // Пока не знаем обратного адреса
MakeStar(); // Зададим полигон в виде звезды
}
![]() |
![]() |
![]() |