программирование, создание программ, учебник Delphi, учебник по программированию, бейсек, делфи, си, паскаль
 
 
 

 

Конструкторы и операции


Важными моментами в жизни объектов являются те, когда они копируются или создаются на основе уже существующих. Реализация конструктора копирования объектов просто обязательна, если вы пользуетесь контейнером объектов. В случае отсутствия или некорректного тела конструктора контейнеры откажутся работать с объектами класса. Обычным приемом при этом является реализация в классе операции присвоения operator= () и последующее ее воспроизведение в конструкторе копирования. Обратите внимание на тип возвращаемого значения операции присвоения. Это ссылка (CPolygon&) на активный или стоящий в левой части операции присвоения — *this, объект класса:
CPolygoni CPolygon::operator=(const CPolygonS poly){
//====== Копируем все данные
m_pDoc = poly.m_pDoc;
m_nPenWidth = poly.m_nPenWidth;
m_PenColor = poly.m_PenColor;
m_BrushColor = poly.m_BrushColor;
m_ptLT = poly.m_ptLT;
m_ptRB = poly.m_ptRB;
//===== Освобождаем контейнер точек
if (!m_Points.empty()) m_Points.clear();
//====== Копируем все точки. Возможно решение с помощью assign.
for (OINT i=0; i<poly.m_Points.size();
m_Points.push_back(poly.m_Points[i] )
//====== Возвращаем собственный объект
return *this;
//====== Конструктор копирования пользуется уже
//====== существующей реализацией операции присвоения
CPolygon::CPolygon(const CPolygoni poly)
{
*this = poly;
}
Довольно часто во вновь создаваемых классах переопределяют операцию выбора с помощью угловых скобок ( [ ] ). Смысл этой операции задает программист. Он часто бывает очевидным для классов объектов, содержащих внутри себя контейнеры, как в нашем случае. Так, если к полигону poly применить операцию выбора, например
CDPoint pt = poly[i];
то он возвратит свою i-ю точку, что, безусловно, имеет смысл. Если же операция [ ] возвращает ссылку на i-ю точку, то становится возможным использовать ее и в левой части операции = (присвоения). Например,
poly[i] = CDPoint (2.5, -20.);
Отметим, что в новом языке С#, который поддерживается Studio.Net 7.0, такой прием является встроенным средством языка под названием indexer. С учетом сказанного введите следующую реализацию операции [ ]:
CDPointS CPolygon::operator[](UINT i)
{
if (0 <= i && i < m_Points.size ())
return m_Points[i];
return m_ptLT;
}
Функция Set для установки обратного указателя может быть совмещена (overloaded) с одноименной функцией, позволяющей изменять атрибуты изображения полигона:
//====== Установка обратного указателя
void CPolygon::Set (CTreeDoc *p) { m_pDoc = p;
{
//====== Совмещенная версия для изменения атрибутов
void CPolygon::Set (CTreeDoc *p, COLORREF bCl, COLORREF pCl, UINT pen)
{
m_pDoc = p;
m_BrushColor= bCl;
m_PenColor = pCl;
m_nPenWidth = pen;
}
Деструктор класса должен освобождать память, занимаемую вложенным в объект контейнером точек:
CPolygon::~CPolygon()
{
m_Points.clear() ;
}
Метод GetRect получает на входе ссылки на две характерные точки прямоугольника, обрамляющего весь полигон, вычисляет координаты этих точек и возвращает их с помощью механизма передачи ссылкой:
void CPolygon::GetRect(CDPointS ptLT, CDPointi ptRB)
{
m_ptLT = m_ptRB = CDPoint(0., 0 .) ;
//====== Если полигон содержит точки контура
UINT n = ra_Points.size();
if (n > 0)
{
//====== Пробег по всем его точкам
for (UINT 1=0; i<n; i++)
{
//====== Поиск и запоминание экстремумов
double х = m_Points[i].x,
у = m_Points[i].у;
if (x < m_ptLT.x) m_ptLT.x = x;
else if (x > m_ptRB.x)
m_ptRB.x = m_Points[i].x; if (y > m_ptLT.y) ra_ptLT.y = y;
else if (y < m_ptRB.y)
m_ptRB.y = y;
}
}
//====== Возвращаем найденные координаты (ссылками)
ptLT = m_ptLT; ptRB = m_ptRB;
}
Метод сериализации данных полигона, приведенный ниже, мог бы быть более компактным, если бы для хранения точек полигона мы воспользовались бы одним из шаблонов семейства классов Collection библиотеки MFC. В эти классы уже встроена возможность сериализации. Но у нас на вооружении шаблон классов vector из другой библиотеки STL, так как он обладает рядом других привлекательных черт. За это приходится платить несколькими лишними строками кода, в котором все точки контейнера либо помещаются в архив, либо выбираются из него:
void CPolygon: :Serialize (CArchiveS ar) {
//====== Если идет запись в архив,
if (ar. IsStoring() }
{
//=== то последовательно переносим туда все данные
m « m_nPenWidth « m_PenColor « m_BrushColor « m_Points. size () « m_ptLT.x « m_ptLT.y « m_ptRB.x « m_ptRB.y;
for (UINT i=0; i <m_Points . size 0 ;
m « m_Points [i] .x « m_Points [i] . y;
}
else
{
//=== При чтении из архива меняем направление обмена
UINT size;
m » m_nPenWidth » m_PenColor » m_BrushColor
» size » m_ptLT.x » m_ptLT.y
» m_ptRB.x » m_ptRB.y;
//====== Заново создаем контейнер точек полигона
m_Points . clear ( ) ;
while (size--)
{
double x, y;
m » x » y;
m_Points. oush back (CDPoint (x, v) ) ;
}
}
}
Ниже приведена функция рисования полигона в переданный ей в качестве параметра контекст устройства. Второй параметр является флагом, который задает способ заливки полигона. В операциях визуального редактирования, которое мы введем позже, полигон должен временно терять свой цвет, для того чтобы не было мелькания при частых перерисовках.
Напомним, что полигон хранит World-координаты всех своих точек в контейнере m_Points. Переход к Page-координатам производится с помощью функции MapToLogPt, которую мы еще должны разработать и поместить в класс документа. Двигаясь далее по коду функции Draw, мы видим, как объект настраивает контекст устройства с помощью своих личных атрибутов и изображает себя в этом контексте:
void CPolygon::Draw (CDC *pDC, bool bContour)
{
//====== Размер контейнера World-координат точек
UINT nPoints = m_Points.size();
if (!nPoints) return;
//====== Временный массив логических координат точек
CPoint *pts = new CPoint[nPoints];
//====== Преобразование координат
for (UINT i=0; KnPoints; i++)
pts[i] = m_pDoc->MapToLogPt(m_Points[i]);
pDC->SaveDC();
CPen pen (PS_SOLID,m_nPenWidth,m_PenColor);
pDC->SelectObject(Spen);
CBrush brush (bContour ? GetSysColor(COLOR_WINDOW) : m_BrushColor);
pDC->SelectObject(ibrush);
//====== Полигон изображается в предварительно
//====== подготовленном контексте устройства
pDC->Polygon(pts, nPoints);
//====== Освобождаем массив
delete [] pts;
pDC->RestoreDC(-1);
}

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


Задание координат полигонов является утомительным занятием, поэтому мы, учитывая учебный характер приложения, создали три вспомогательные функции, которые позволяют быстро воспроизвести три различных полигона: звезду, треугольник и пятиугольник. Далее нам необходим немодальный диалог, с помощью которого пользователь сможет создать произвольное количество новых полигонов, выбирая их типы с помощью нестандартных кнопок и управляя атрибутами полигонов (цветом фона, цветом и толщиной пера) с помощью синхронизированных между собой элементов управления. Дополните файл ТгееОос.срр кодами еще трех функций:
void CPolygon::MakeStar()
{
m_Points.clear();
//====== Вспомогательные переменные
double pi = 4. * atan(l.), // Углы
al = pi / 10.,
а2 = 3. * al,
//====== 2 характерные точки
xl = cos (al),
yl = sin(al),
x2 = cos(a2),
y2 = sin(a2);
//=== Вещественные (World) координаты углов звезды m_Points.push_back(CDPoint(0., 1.));
m_Points.push_back(CDPoint <-x2, -y2));
m_Points.push_back(CDPoint( xl, yl) ) ;
m_Points.push_back(CDPoint(-xl, yl)) ;
m_Points.push_back(CDPoint( x2, -y2));
//====== Габариты звезды
m_ptLT = CDPoint(-xl, 1.);
m_ptRB = CDPoint( xl,-y2);
//====== Генерация треугольника
void CPolygon::MakeTria() {
m_Points.clear();
double pi = 4. * atand(1.);
a = pi / 6.;
x = cos (a) ;
у = sin(a);
m_Points.push_back (CDPoint(0., 1.));
m_Points,push_back (CDPoint(-x, -y) );
m_Points.push_back (CDPoint( x, -y));
m_ptLT = CDPoint (-x, 1.) ;
m_ptRB = CDPoint ( x,-y);
//====== Генерация пятиугольника
void CPolygon::MakePent()
{
m_Points.clear ();
double pi = 4. * atan(l.),
al = pi / 10.,
a2 - 3. * al,
xl = cos(al),
yl = sin(al),
x2 = cos(a2),
y2 = sin(a2);
// Вещественные (World) координаты углов пятиугольника m_Points.push_back(CDPoint (0 ., 1.));

m_Points.push_back(CDPoint(-xl, yl));
m_Points.push_back(CDPoint(-x2, -y2));
m_Points.push_back(CDPoint( x2, -y2));
m_Points.push_back(CDPoint( xl, yl));
m_ptLT = CDPoint(-xl, 1.);
m_ptRB = CDPoint( xl,-y2);
  
Развитие класса документа
Теперь, когда мы имеем вспомогательные классы (CDPoint и CPolygon), можно подумать о структуре данных класса CTreeDoc. Нам понадобятся:

  • массив (контейнер) полигонов, которые соответствуют файлам документов, обнаруженных в текущем каталоге;
  • массив строк текста с файловыми путями этих документов;
  • один «дежурный» полигон, который в данный момент редактируется, то есть выбран для демонстрации в окне третьего представления (CDrawView);
  • размеры документа в логической системе координат (Page space);
  • коэффициент увеличения размеров при переходе из World в Page-пространство.

Кроме этого, нам понадобятся методы для управления тремя окнами: CLef tview, CRightView и CDrawView. Последний класс будет управлять окном, в котором полигон может быть отредактирован. Этот класс надо еще создать. Замените существующий интерфейс класса CTreeDoc на тот, который приведен ниже. Здесь мы также провели упрощение начальной заготовки по схеме, обсуждавшейся выше:
class CTreeDoc : public CDocument {
//==== Все 3 представления имеют право доступа
//==== к данным документа
friend class CLeftView;
friend class CRightView;
friend class CDrawView;
protected:
virtual ~CTreeDoc ();
CTreeDoc () ;
DECLARE_DYNCREATE(CTreeDoc) public:
//========== Данные документа============
//
CPolygon m_Poly; // Дежурный полигон VECPOLY m_Shapes;
// Контейнер полигонов
// ====== Контейнер имен файлов
vector<CString> m_sFiles;
//====== Размер документа в Page space
CSize m_szDoc;
//== Коэффициент увеличения при переходе World->Page
OINT m_nLogZoom;
//====== Флаг: открыто окно типа CTreeFrame
bool m_bTreeExist;
//=====Флаг: открыто окно типа CDrawFrame
bool m_bDrawExist;
//======Новые методы класса документ =====//
//====== Поиск нужного представления
CView* GetViewfconst CRuntimeClass* pClass);
//====== Создание нужного представления
bool MakeViewO ;
//====== Преобразование координат World -> Page
CPoint MapToLogPt(CDPointS pt);
//====== Преобразование координат Page -> World
CDPoint MapToWorldPt(CPolntS pt) ;
//===== Перерисовка окна редактирования
void UpdateDrawView();
// Чтение найденных документов и их демонстрация
void ProcessDocs();
//====== Освобождение контейнеров
void FreeDocs();
//====== Поиск выбранной точки
int FindPoint(CDPointS pt) ;
// Overrides
public:
virtual BOOL OnNewDocument();
virtual void Serialize(CArchiveS ar) ;
// Generated message map functions
protected:
DECLARE_MESSAGE_MAP()
);
Некоторым из данных документа можно присвоить значения по умолчанию. Обычно это делается в конструкторе класса. Зададимся неким произвольным размером (2000 х 2000) документа в логической (Page) системе координат. Чем больше эта область, тем точнее будут отражены детали конструкции, так как вещественные (World) координаты претерпят округление при приведении к целым (Page) координатам. Вспоминая, что две из наших тестовых фигур имеют габариты в 2 единицы в пространстве World, определяем коэффициент увеличения m_nLogZoom = 700. В этом случае габариты фигур в пространстве Page будут равны 1400 единиц, то есть они целиком поместятся в области документа. Выбрав произвольные начальные цвета фигуры и учтя соображения относительно установки обратного указателя, получим следующую версию конструктора класса CTreeDoc:
CTreeDoc::CTreeDoc() : m_szDoc(2000,2000), m_Poly()
{
//====== Установка обратного указателя и
//====== атрибутов дежурного полигона
m_Poly.Set(this, RGB(240,255,250), RGB(0,96,0), 2);
m_nLogZoom = 700;
}
Деструктор класса должен освобождать память, занимаемую динамическими структурами, входящими в состав класса. Метод FreeDocs мы создадим позже, поэтому при проверочных компиляциях проекта либо создайте заглушку — пустое тело функции FreeDocs, либо временно вставляйте символы комментария в строке с вызовом отсутствующей функции:
CTreeDoc::~CTreeDoc()
{
FreeDocs () ;
m_Poly .m_Points . clear () ;
}
Устойчивость данных документа обеспечивается функцией Serialize, и в стартовой заготовке класса уже есть этот метод. Его тело содержит схему сериализа-ции, но не имеет конкретных кодов записи или чтения данных из архива. Мы должны наполнить заготовку кодами так, чтобы документ мог полностью сохранить или восстановить свое состояние. Нет необходимости сохранять абсолютно все данные документа, так как некоторые из них носят тактический (временный) характер. Они заданы по умолчанию и не будут изменяться, например m_szDoc или m_nLogZoom. С долговременными данными документа мы отождествляем текущий или дежурный полигон m_Poly, который по легенде отражает выбранную и редактируемую в данный момент конструкцию. Он должен полностью изменить свои данные при выборе пользователем одной из картинок в окне правого представления. С этим окном мы связываем контейнер полигонов m_Shapes, который тоже носит временный характер, так как уничтожается и вновь создается при переходе из одной папки в другую и лишь помогает пользователю осуществить выбор. Таким образом, сериализацию документа мы отождествляем с сериали-зацией дежурного полигона. Поэтому тело функции Serialize выглядит весьма просто:
void CTreeDoc: : Serialize (CArchivei ar) {
// Просим объект выполнить сериализацию самостоятельно
m_Poly. Serialize (ar) ;
if (ar.IsStoringO ) {
// Здесь помещается код записи "обычных" данных }
else {
// Здесь помещается код чтения "обычных" данных
Мы могли бы поместить в ветви условного оператора такой код: ar « m_szDoc « m_nLogZoom; ar » m_szDoc » m_nLogZoom; но тогда для обработки документов, расположенных в текущей папке, было бы необходимо поддерживать динамический контейнер объектов CTreeDoc. Чтение документов сводилось бы к вызову Serialize для каждого из них. Такое решение будет более громоздким, чем поддержка контейнера полигонов. Поэтому мы оставляем ветви условного оператора пустыми.
Продолжая развитие темы преобразования координат, создадим тело функции MapToLogPt, которая, получив на входе точку с вещественными World-координатами, возвращает точку с целыми координатами в пространстве Page. В коде этой функции мы помещаем центр симметрии фигуры (точку с координатами
CDPoint(0,0)) в центр логической области, отведенной для документа, увеличиваем координаты и преобразуем их к целому типу:
CPoint CTreeDoc::MapToLogPt(CDPointS pt) {
{
//====== Растяжение и сдвиг
int x = m_szDoc.cx/2 +
int(m_nLogZoom * pt.x), у = m_szDoc.cy/2 +
int(m_nLogZoom * pt.y);
return CPoint(x,y);
+}
Введите также функцию обратного преобразования координат, которая, получив на входе точку с целыми координатами в пространстве Page, вычисляет соответствующую ей точку с вещественными координатами в пространстве World:
CDPoint CTreeDoc::MapToWorldPt(CPointS pt)
{
//====== Обратные операции
double x = double(pt.x - m_szDoc.cx/2) / m_nLogZoom,
у = double(pt.y - m_szDoc.cy/2) / m_nLogZoom;
return CDPoint(x, y);
}
В настоящий момент, если закомментировать вызовы FreeDocs и ProcessDocs в теле деструктора и функции OnSelchanged класса CLef tview, то вы можете запустить приложение, с тем чтобы устранить возможные ошибки. Но пока никакой новой функциональности оно не обнаружит, так как мы еще не занимались созданием и управлением других представлений его документа. Нам вновь придется вернуться к классу документ, но только после того, как будут разработаны классы связанных с ним представлений.
  
Документ и его представления
Библиотека MFC предоставляет 3 различных способа создания окон-рамок, обрамляющих представления. Два явных способа: вызов методов CFrameWnd:: Create или CFrameWnd:: LoadFrame и один неявный — путем создания шаблона документа. Класс MFC-приложения в рамках многодокументного интерфейса (MDI) поддерживает динамический список шаблонов документов, то есть объектов класса CMultiDocTemplate. Каждый из них управляет оркестром из четырех музыкантов: ресурсы, документ, связаное с ним представление и обрамляющее его окно-рамка. Этот квартет составляется в момент создания нового объекта CMultiDocTemplate внутри метода initinstance класса приложения, в нашем случае СТгееАрр. Найдите это место в программе и убедитесь, что параметрами конструктора являются перечисленные музыканты:
CMultiDocTemplate* pDocTemplate;
//====== Создание шаблона документов
pDocTemplate = new CMultiDocTemplate(IDR_TreeTYPE,
RUNTIME_CLASS(CTreeDoc), // документ
RUNTIME_CLASS(CTreeFrame), // окно-рамка
RUNTIME CLASS(CLeftView)); // представление
//====== Вставка адреса шаблона в динамический список
AddDocTemplate(pDocTemplate);
Мы можем при желании поменять состав квартета, но должны оставить неизменным количество его исполнителей. В наши цели входит создание еще одной новой комбинации. Тот же документ будет связан с другими ресурсами I DR_DrawTYPE, другим представлением (CDrawView) и другой рамкой (CDrawFrame). Так как мы хотим управлять обоими квартетами, то удобно запомнить в классе приложения их адреса и пользоваться ими при необходимости создать новое или активизировать существующее окно MDI-документа. Введите в состав класса CTreeApp две public-переменные, которые будут хранить эти адреса. С учетом незначительных сокращений интерфейс класса должен иметь такой вид:
class CTreeApp : public CWinApp
{
public:
//====== Два шаблона документов
CMultiDocTemplate *m_pTemplDraw; CMultiDocTemplate *m_pTemplTree;
CTreeApp () ;
virtual BOOL Initlnstance();
afx_msg void OnAppAbout();
DECLARE_MESSAGE_MAP()
}; 
Обзор функции Initlnstance
Внесем некоторые изменения и сокращения в файл реализации класса CTreeApp. Откройте файл Тгее.срр в окне редактора и просмотрите коды функции Initlnstance. Если у вас присутствует блок кодов
if (lAfxOlelnit()) {
AfxMessageBox(IDP_OLE_INIT_FAILED) ;
return FALSE;
}
AfxEnableControlContainer() ;
который представляет собой инициализацию поддержки OLE (Object Linking and Embedding), то его можно убрать, так как наше приложение не будет выполнять функции OLE-сервера или OLE-контейнера. Следующая строка:
SetRegistryKey(_T("Local AppWizard-Generated..."));
представляет собой создание нового ключа в реестре Windows для хранения некоторой информации о нашем приложении. Он действительно будет новым, если вы измените строку текста на имя вашей компании, как это было задумано при разработке функции, или на какое либо другое имя («My Soft»). После запуска приложения можно открыть реестр (в командной строке Windows дайте команду: RegEdit) и отыскать в нем по адресу HKEY_CURRENT_USER\Software вновь созданный ключ My Soft. Записывать информацию по этому ключу вы можете с помощью методов класса cwinApp, от которого происходит наш класс CTreeApp. Например, метод WriteProf ilelnt позволяет записать некое целое значение (value), соответствующее произвольной секции текущего ключа. Для эксперимента вставьте вместо строки SetRegistryKey такие три строки:
SetRegistryKey("My Soft");
WriteProfileStringC'My Data", "My Name","Alex");
WriteProfilelnt("My Data","My Age",54);
Запустите приложение, перейдите в окно реестра, обновите его (View > Refresh), найдите адрес HKEY_CURRENT_USER\Software\My Soft\Tree\My Data, поставьте в него курсор мыши и убедитесь в наличии двух записей, высвечиваемых в правом окне реестра. Удалите из реестра ключ My Soft, если вам нужен реестр, а не свалка мусора (чем он обычно и является). Уберите также учебный код из тела initinstance.
Для того чтобы увидеть, как работает функция LoadStdProf ileSettings, вызов которой виден в теле initinstance, запустите приложение и запишите хотя бы один документ (команда: File > Save). После этого вы можете найти в реестре (не забывайте освежать его) по тому же адресу новую секцию Recent File List, которая содержит запись — полный путь к только что записанному файлу. Параметр функции LoadStdProf ileSettings указывает, сколько записей может содержать список MRU (Most Recently Used) последних документов. Если вы зададите его равным нулю, то список не будет поддерживаться каркасом приложения.
Теперь можно приступить к созданию двух шаблонов документов вместо одного, рассмотренного выше. Для того чтобы задействовать второй шаблон, надо убрать из Initinstance код по созданию шаблона pDocTemplate и вставить вместо него такие строки:
//====== Создаем первый шаблон
m_pTemplTree = new CMultiDocTemplate(IDR_TreeTYPE,
RUNTIME_CLASS(CTreeDoc) ,
RUNTIME_CLASS(CTreeFrame) ,
RUNTIME_CLASS(CLeftView)) ;
//====== Помещаем его в список
AddDocTemplate(m_pTemplTree);
//====== Создаем второй шаблон
m_pTemplDraw = new CMultiDocTemplate(IDR_DrawTYPE,
RUNTIME_CLASS(CTreeDoc),
RUNTIME_CLASS(CDrawFrame),
RUNTIME_CLASS(CDrawView));
//====== Помещаем его в список
AddDocTemplate(m_pTemplDraw);
Второй шаблон тоже помещается в список шаблонов приложения. Каркас приложения устроен так, что теперь каждый раз, когда пользователь будет выбирать команду File > New, будет появляться диалог со списком шаблонов и просить его выбрать шаблон, которому должен соответствовать новый документ. Идентификатор ресурсов !DR_DrawTYPE определяется заранее, то есть в файле resource.h должна быть макроподстановка #def ine, заменяющая этот идентификатор целым положительным числом. Самым простым способом создания нового идентификатора является вызов команды Edit > Resource Symbols. Но этот способ будет некорректным в нашем случае, так как мы поместили второй шаблон в список шаблонов, ассоциированных с документами приложения, и его идентификатор должен быть связан с какими-то ресурсами.
Ресурсов, которые связаны со вторым шаблоном, может быть несколько, и мы покажем, как связать с ним значок, меню, панель инструментов и строковый ресурс, расположенный в таблице String Table. Последний является текстовой строкой, которая разбита символами ' \п' на отдельные части — подстроки. Каждая подстрока имеет определенное значение и используется каркасом приложения в разные моменты его жизни. Например, вторая подстрока является корнем для образования имен новых документов, и вы обычно видите ее в заголовке дочернего окна документа. Откройте окно Resource View, раскройте узел дерева под именем String Table и сделайте двойной щелчок на вложенном в него элементе. В таблице строк справа найдите iDR_TreeTYPE. Он идентифицирует комплексную строку:
\nTree\nTree\nTree Files (*.mgn)\n.mgn\nTree.Document\nTree.Document
Примечание
Вы можете получить справку по всем частям этой строки, если вызовете помощь (Help) по индексу GetDocString — методу класса CDocTemplate, позволяющему выделить нужную подстроку комплексной строки.
Если мы поместим в String Table новую строку с идентификатором !DR_DrawTYPE, то при открытии окон документов по шаблону m_pTemplDraw, они будут использовать этот ресурс. При вставке новой строки надо быть внимательным, так как ее индекс должен быть в определенном диапазоне.

  1. Сделайте два щелчка (не двойной, а два щелчка с паузой между ними) в колонке Caption, той строки текста, которая идентифицирована IDR_TreeTYPE. При втором щелчке на месте выделенной строки появится окно редактирования.
  2. Выделите, скопируйте в буфер всю текстовую строку и щелкните справа от окна редактирования.
  3. Если фокус выделения ушел, то поставьте его вновь на строку IDR_TreeTYPE. Это обеспечит правильное значение индекса для нового ресурса. Вызовите контекстное меню и выберите команду New > String.
  4. Появится новая строка, возможно, внизу экрана. Используя ту же технику повторного щелчка, создайте окно редактирования в новой строке и вставьте из буфера старый текст. Замените в нем две первые подстроки на Draw.
  5. Задайте для новой строки идентификаторIDR_DrawTYPE и нажмите Enter.
  6. Щелкните мышью заголовок столбца Value. Таблица будет отсортирована по возрастанию индексов.

Убедитесь в том, что индекс новой строки (видимо, 130) следует за индексом, соответствующим строке IDR_TreeTYPE, при этом строки двух шаблонов стоят рядом. Если индекс новой строки не попал в нужный диапазон, то придется все повторить. Замените поле Caption строкового ресурса IDR_MAINFRAME на Doc Viewer. Это необходимо для того, чтобы пользователь легче воспринял закономерность образования заголовков окон новых документов.
Завершая обзор функции Initinstance, расскажем, что делают остальные функции, вызов которых происходит при инициализации приложения. Вызов
m_pMainWnd->DragAcceptFiles();
с параметром TRUE, заданным по умолчанию, сообщает системе, что главное окно приложения способно обработать сообщение WM_DROPFILES. Благодаря этому пользователь может методом Drag&Drop переместить в открытое окно приложения файл нашего документа (mgn-файл), и он будет обработан командой File > Open. Вызов функции EnableShellOpen делает возможным запуск нашего приложения при двойном щелчке на mgn-файле или его значке (icon), а вызов RegisterShellFileTypes регистрирует новый тип файлов (файлы документов нашего приложения) и действия при его открытии двойным щелчком. Регистрация не производится, если данное расширение (mgn) уже присутствует в базе данных Windows и с ним связано какое-то действие. Например, если мы вместо mgn выберем расширение mag, то наши файлы будут рассматриваться системой как файлы Microsoft Access Diagram Shortcut или как файлы документов приложения ACDSee в зависимости от того, что установлено в системе. Это малоприятная история, выходом из которой, как нам говорят разработчики системы, является возможность задавать файлам документов более длинное расширение. Нет уверенности в том, что это будет хорошим решением, так как вероятность совпадений остается достаточно высокой.
В файле ТгееАрр.срр присутствует также декларация и определение класса CAbout Dig, производного от CDialog и обслуживающего окно простого диалога, ресурс которого (IDD_ABOUTBOX) уже имеется в каркасе приложения. Так как мы не собираемся развивать диалог, то можно убрать класс и все его методы, оставив лишь функцию вызова OnAppAbout, тело которой упрощается до:
void CTreeApp::OnAppAbout()
{
// Класс CDialog справляется с задачей
CDialog(IDD_ABOUTBOX).DoModaK);

Ресурсы шаблона документов
Если мы не поленимся и создадим для второго шаблона документов все остальные перечисленные выше ресурсы, то приложение действительно будет вести себя в соответствии с концепцией MDI, так как она трактуется компанией Microsoft. Это означает, что приложение будет следить за типом активного документа и автоматически изменять связанные с ним ресурсы (значок и меню). К сожалению, автоматическая смена панели инструментов все-таки потребует некоторых усилий. Имя текущего документа совпадает с именем файла, в котором он хранится, но если документ еще не был сохранен, то его имя генерируется автоматически и также зависит от выбранного шаблона. Перейдите в окно Resource View и откройте в дереве ресурсов узел Icon. Вы видите индексы двух значков, которые сгенерированы мастером AppWi zard и служат для идентификации приложения (IDR_MA IN FRAME) и его документов (IDR_TreeTYPE). При желании вы можете отредактировать изображения, открыв их в окне редактора или, что проще, заменив их на другие. Техника замены проста: открыть готовое изображение в рамках Studio.Net (их много по адресу ..\Microsoft Visual Studio.Net \ Common7\ Graphics\icons), скопировать в буфер, открыть существующий значок, нажать Delete и Ctrl+V. He забывайте, что на самом деле имеется 4 значка (2 маленьких и 2 больших). Переход между изображениями значков разных размеров производится в диалоге, вызываемом командой Image > Open > Image > Туре. Команда доступна, когда курсор стоит в окне редактора изображений. Теперь опишем, как добавить еще один значок.

  1. Вызовите контекстное меню на узле дерева IDR_TreeTYPE и дайте команду Insert Copy.
  2. В окне диалога измените язык ресурса на любой другой, нажмите ОК, переведите курсор на новый узел дерева ресурсов и перейдите в окно Properties.
  3. Измените идентификатор на IDR_DrawTYPE и верните язык. К сожалению, в моей версии изменения происходят только после того, как будет дана команда Save.
  4. Замените новые изображения, большое и маленькое, на какие-то другие.

Откройте в окне редактора меню IDR_TreeTYPE. Удалите из меню File команды Print, Print Preview, Print Setup и один разделитель (Separator). Повторите действия по копированию ресурсов и сделайте копию всего меню IDR_TreeTYPE. Поменяйте идентификатор копии на IDR_DrawTYPE. Откройте копию меню в окне редактора и уберите из него команду View > Split и добавьте View > Documents с идентификатором ID_VIEW_TOGGLE. В пункт меню Edit добавьте две новые команды: New Poly и Poly Color. ... Вновь откройте меню IDR_TreeTYPE, удалите в нем весь пункт меню Edit, добавьте команды View > Geometry с идентификатором ID_VIEW_TOGGLE и View > Refresh с идентификатором ID_VIEW_REFRESH. Команда ID_VIEW_70GGLE будет служить для переключения между двумя окнами CTreeFrame и CDrawFrame, содержащими три представления одного и того же документа.
В том и другом из рассматриваемых меню измените команду Window > Tile на Window > Tile Horizontally и добавьте новую команду Window > Tile Vertically, выбрав из выпадающего списка идентификатор ID_WINDOW_TILE_VERT. Обработчики этих команд уже существуют в каркасе приложения, поэтому нам не нужно их создавать.

Откройте инструментальную панель (Toolbar) IDR_MAINFRAME. Удалите из нее кнопки вырезания, копирования, вставки и печати. Используя технику копирования, добавьте две новые инструментальные панели:

IDR_TreeTYPE и IDR_DrawTYPE. В последнюю вставьте две новые кнопки, соответствующие двум командам меню: New Poly и Poly Color.. .. Добавьте два новых курсора, которые будут использованы в разные моменты жизни приложения. Один будет загружаться автоматически при попадании фокуса в окна демонстрации содержимого документов (типа CWndGeom). Другой будет использован в режиме визуального редактирования данных документа.

  1. Вызовите контекстное меню в окне Resource View и дайте команду Add Resource.
  2. В списке диалога выберите Cursor и нажмите кнопку New.
  3. Задайте идентификатор IDC_MYHAND и скопируйте изображение курсора H_POINT.CDR из папки Cursors студии.
  4. Повторите эти действия для создания курсора IDC_MOVE с изображением, скопированным из файла 4WAY02.CUR.

Если вы вместо IDC_MYHAND зададите IDC_HAND, то компилятор многократно сообщит о переопределении стандартного курсора с таким индексом. Внесите еще одно изменение, связанное с ресурсами. Так как мы сами будем перезагружать инструментальные панели, то надо упростить доступ к ним. В файле MainFrm.h перенесите следующие два объявления из секции protected в секцию public:
public:
CToolBar m_wndToolBar;
CStatusBar m_wndStatusBar;
  
Класс для нового представления документа
При создании второго шаблона документов мы определили новую комбинацию классов, которые будут поддерживать функционирование окон нового типа. Клиентской областью этих окон будет управлять класс CDrawView, к созданию которого мы и приступаем.

  1. Поставьте фокус на элемент Tree дерева классов в окне Class View, вызовите контекстное меню и выберите команду Add > Add Class.
  2. В окне появившегося диалога выберите категорию MFC, шаблон MFC Class и нажмите кнопку Open.
  3. В окне MFC Class Wizard задайте имя класса CDrawView и выберите класс CView в списке Base Class.
  4. Нажмите кнопку Finish.

Примечание
Среди классов списка Base Class теперь есть класс CObject. Как ни странно, но в Visual Studio 6 вы не могли бы рассчитывать на помощь ClassWizard при создании класса, производного от CObject.
В папке проекта появились еще два файла (DrawView.h и DrawView.cpp), которые были автоматически включены в состав проекта. Класс приложения должен их видеть, поэтому вставьте в конец списка директив #include файла Тгее.срр еще одну:
#include "DrawView.h"
Внесите изменения в интерфейс класса, так чтобы он стал:
#pragma once
class CTreeDoc; // Упреждающее объявление
class CDrawView : public CView {
DECLARE_DYNCREATE(CDrawView) protected:
CSize m_szView; // Размеры клиетской области окна
bool m_bNewPoints; // Флаг режима вставки новых точек
bool m_bReady; // Флаг готовности захвата вершины
bool m_bLock; // Флаг захвата вершины
int m_CurID; // Индекс полигона в массиве
HCURSOR m_hGrab; // Курсор захвата
CPen m_penLine; // Перо для изображения контура
CDrawView();
virtual ~CDrawView();
public:
CTreeDoc* GetDocument()
{
return dynamic_cast<CTreeDoc*>(m_pDocument);
}
virtual void OnDraw(CDC* pDC);
//====== Настройка контекста устройства
void SetDC(CDC* pDC);
//====== Перерисовка контура
void RedrawLines (CDC *pDC, CPointS point);
DECLARE_MESSAGE_MAP()
};
Так как мы ввели в класс новый метод GetDocument и тут же дали коды его реализации, то класс CTreeDoc должен быть известен компилятору до того, как он познакомится с классом CDrawView. Вставьте строку с директивой включения файла заголовков
#include "TreeDoc.h"
в список директив файла DrawView.cpp до строки, подключающей файл DrawView.h. Класс нового представления старого документа имеет простое назначение: изобразить в центре своего окна дежурный полигон m_Poly, имеющийся в составе документа. Для упрощения этой задачи мы ввели в класс переменную CSize m_szView, которая будет хранить текущие размеры клиентской области окна. Несколько позже мы дадим коды методов визуального редактирования. Эти методы используют параметры текущего состояния, которые надо инициализировать в конструкторе класса. Откройте файл с кодами реализации класса (DrawView.cpp) и измените конструктор и функцию перерисовки OnDraw:
CDrawView::CDrawView()
{
//====== Всё режимы редактирования выключены
m_bNewPoints = false;
m_bReady = false;
m_bLock = false;
m_CurID = -1;
}
void CDrawView: :OnDraw(CDC* pDC) { CTreeDoc* pDoc = GetDocument ();
{
//====== Настройка контекста устройства
SetDC(pDC) ;
//====== Если вершина перемещается,
//====== рисуем без заливки внутренних областей,
pDoc->m_Poly .Draw(pDC, m_bLock) ;
}
В режиме редактирования полигон рисуется без заливки внутренних областей, а в обычном режиме просмотра — с заливкой. Режим выбирает пользователь, а переменная m_bLock следит за тем, какой режим выбран. Настройка контекста устройства определяет трансформацию изображения: увеличение и сдвиг, по формуле, обсуждавшейся в уроке 2. Метод Setoc позволяет учесть текущие размеры окна:
void CDrawView: :SetDC(CDC* pDC)
{
CTreeDoc* pDoc = GetDocument ();
//====== Режим преобразования без искажений пропорций
pDC->SetMapMode (MM_ISOTROPIC) ;
//======Размеры логического окна хранит документ
pDC->SetWindowExt (pDoc->m_szDoc) ;
pDC->SetWindowOrg (pDoc->m_szDoc.cx/2, pDoc->m_szDoc.cy/2) ;
//====== Размеры физического окна хранит представление
pDC->SetViewportExt (m_szView.cx, -m_szView. су) ;
pDC->SetViewportOrg (m_szView.cx/2,. m_szView.cy/2) ;
}
Способом, который вы уже не раз применяли, введите в класс CDrawView реакцию на сообщение WM_SIZE и измените тело функции-обработчика.
void CDrawView: :OnSize(UINT nType, int ex, int су)
{
CView: :OnSize (nType, ex, cy) ;
// Каркас иногда вызывает эту функцию с нулевыми сх,су
if (cx==0 | | су==0)
return;
//====== Запоминаем размеры окна
m_szView = CSize (ex, cy) ;
}
Вспомните способ замещения виртуальных функций (Overrides) и используйте его для введения в класс заготовки функции OnlnitialUpdate. Введите в нее код для подготовки инструментов, которые понадобятся в процессе визуального редактирования данных:
void CDrawView::OnInitialUpdate() {
//====== Загружаем курсор перемещения
m_hGrab=((CTreeApp*)AfxGetApp())->LoadCursor(IDC_MOVE);
//=== Создаем перо перерисовки контура (при перемещении)
m_penLine.CreatePen (PS_DOT,О,COLORREF(0)); }
Настала очередь создания второго участника квартета, определяющего поведение окна MDI-документа. Это заявленный нами класс CDrawFrame. Для его создания повторите те же действия, которые вы производили при создании класса CDr awView, но при выборе родительского класса укажите на класс cMDichildWnd (без параметра splitter). Представьте приложению нового оркестранта, вставив директиву
#include "DrawFrame.h"
в список уже существующих директив файла Тгее.срр. Запустите приложение. Если вы не допустили ошибок или устранили их, то должны увидеть диалоговое окно New со списком из двух строк: Tree и Draw. Выбрав Draw, вы должны увидеть окно документа с заголовком Drawl и изображенной в центре окна звездой. Нажмите кнопку New на панели инструментов и во вновь появившемся диалоговом окне выберите на сей раз шаблон Tree. В меню Window выберите Tile, и вы увидите два окна, причем второе будет иметь заголовок Treel. Переводя фокус из одного окна в другое, обратите внимание на смену строк меню главного окна. Значки в верхнем левом углу окон документов тоже должны быть разными. Панели инструментов, как мы уже отмечали, автоматически не изменяются. Эту функциональность мы внесем позже.

 

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г. Яндекс.Метрика