Пакет Vstudio7

       

Класс CPolygon



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

Примечание 1
Примечание 1

Здесь трудно обойтись без специального метода установки обратного указа-теля, в нашем случае метода Set. Дело в том, что при создании документа надо сначала создать вложенные в него объекты других классов (вспомните правило: «C++ уважает гостей»). Но в этот момент им нельзя передать адрес документа, так как он еще не создан. В таких случаях поступают следующим образом. В заголовке конструктора документа создают пустые объекты (вызывают default-конструкторы вложенных объектов), а затем в теле конструктора документа, когда он уже существует, для вложенных объектов вызывают метод, устанавливающий обратный указатель. При этом объекту передают указатель на документ (на объект собственного класса). Например: m_Poly.Set(this);

Обилие методов класса CPolygon сделано «на вырост». Сейчас каждый документ для простоты представлен одним полигоном. Реальные конструкции можно задать в виде множества полигонов. При этом каждый из них должен знать свои габариты. Метод GetRect позволяет вычислять и корректировать габариты полигона. Если вы будете применять эти идеи в более сложном проекте, то вам понадобится множество других методов. Например, методы, определяющие факт самопересечения полигона или взаимного их пересечения.

Главными методами, которые реализуют концепцию архитектуры «документ — представление», являются Serialize и Draw. Метод Serialize позволяет общаться с файлами. Его особенность состоит в том, что он позволяет как записывать все данные объекта в файл, точнее в архив, так и читать их из файла. Здесь опять проявятся преимущества наследования от cobject, так как объекты классов, имеющих такого авторитетного родителя, обычно сами умеют себя сериализовывать.

Примечание 2
Примечание 2

Термин «сериализация» приходится брать на вооружение, так как он довольно емкий, и чтобы его заменить, надо произнести довольно много слов о последовательном (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(); // Зададим полигон в виде звезды

}



Содержание раздела