Класс для просмотра изображений
Класс представления документа CRightView служит для иллюстрации содержимого всех документов, обнаруженных в текущей выбранной папке. В окне CRightView мы рядами и столбцами разместим другие простые окна, управляемые классом CWndGeom, которые будут иметь одинаковый размер и изображать геометрию конструкции, соответствующей данным документа. Причем изображение в контексте окна воспроизведут сами документы, точнее объекты m_poly, которые есть в каждом из них. Далее окна класса CWndGeom мы будем называть картинками.
Так как количество документов в текущей папке произвольно и заранее не известно (но они все должны быть доступны пользователю), то, чтобы разместить все картинки, размеры окна CRightView должны быть переменными. Окно должно быть «резиновым». Класс CRightView был изначально создан мастером AppWizard как класс, способный прокручивать содержимое своего окна, так как в качестве базового класса для него был выбран csroliview. Благодаря этому класс приобрел способность следить за размерами своего окна и при необходимости создавать полосы горизонтальной и вертикальной прокрутки. Наша цель — научиться программно управлять размерами окна прокрутки, динамически создавать и уничтожать окна картинок и правильно изображать в них геометрию конструкции, опираясь на данные документа. Скорректируйте коды стартовой заготовки с интерфейсом класса так, как показано ниже:
#pragma once
//====== Класс для демонстрации содержимого документов
class CRightView : public CScrollView {
//====== Упреждающее объявление класса картинок
friend class CWndGeom; protected:
CSize m_szView; // Реальные размеры окна
CSize m_szScroll; // Размеры прокручиваемого окна
CSize m_szltem; // Размеры картинки
CSize m_szMargin; // Размеры полей
CString m_WndClass; // Строка регистрации картинки
CRightView () ;
DECLARE_DYNCREATE(CRightView) public: //====== Контейнер картинок
vector<CWndGeom*> m_pWnds;
CTreeDoc* GetDocument()
{
return dynamic_cast<CTret=Doc*> (m_pDocument) ;
}
virtual -CRightView();
void Show(); // Демонстрация картинок
void Clear();
// Освобождение ресурсов
// Overrides public:
virtual void OnDraw(CDC* pDC) ;
protected:
virtual void OnlnitialUpdate() ;
DECLARE_MESSAGE_MAP() };
Внесите сокращения и изменения в коды реализации класса так, как показано ниже:
IMPLEMENTJDYNCREATE(CRightView, CScrollView)
BEGIN_MESSAGE_MAP(CRightView, CScrollView) END_MESSAGE_MAP()
CRightView::CRightView()() CRightView::-CRightView(){}
void CRightView::OnDraw(CDC* pDC)
{
CTreeDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc);
}
Полосы прокрутки автоматически появляются, когда реальные размеры окна (m_szview) становятся меньше размеров прокручиваемого окна (m_szScroll), которые надо задать в качестве аргумента функции SetScrollSizes. Если пользователь увеличил размеры окна и они стали равными или больше тех, что были указаны, то полосы автоматически исчезают. Отсюда следует, что программист должен как-то задать первоначальные размеры m_szScroll, когда еще не известны требования к ним. Обычно это делается в функции OnlnitialUpdate. Просмотрите коды этой функции, и вы увидите, какие размеры прокручиваемого окна (по умолчанию) задал мастер AppWizard. Для слежения за размерами окна представления введите в класс CRightview реакцию на сообщение WM_SI ZE, так же как вы это делали в классе CDrawView. Измените коды этой функции, а также функции OnlnitialUpdate, в которой мы приравниваем начальные размеры прокручиваемого окна к реальным:
void CRightView::OnSize(UINT nType, int ex, int cy)
{ CScrollView::OnSize(nType, ex, cy) ;
if (cx==0 || cy==0)
return;
//====== Запоминаем размеры окна представления
m_szView = CSize (ex, cy);
}
void CRightView::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
//====== Начальные размеры окна
m_szScroll = m_szView; SetScrollSizes(MM_TEXT, m_szScroll) ;
}
Функция SetScrollSizes одновременно с размерами задает и режим преобразования координат. Самым неприятным и непонятным моментом в наследовании от класса CScrollView является то, что функция SetScrollSizes не позволяет задавать режимы MM_ISOTROPIC и MM_ANISOTROPIC, которые позволяют, как вы помните работать с формулами. Этот недостаток MFC широко дискутировался как в MSDN, так и на одном из самых популярных сайтов для программистов — www. CodeGuru.com. Там же вы можете обнаружить некоторые решения этой проблемы. Измените конструктор класса. В момент своего рождения объект класса CRi'ghtView должен подготовиться к работе с окнами, управляемыми классом CWndGeom. К тому моменту, когда ему понадобится создать серию таких окон, их тип (класс окон в смысле структуры типа WNDCLASS) уже должен быть известен системе.
Примечание
Прекрасное решение дал Brad Pirtle, и вы можете найти его в одном из разде-лов CodeGuru, включив поиск по имени. Он создал свой класс CZoomView (производный от CScrolLView), в котором заменил функцию SetScrollSizes на другую — SetZoomSizes, а также переопределил (overrode) виртуальную функцию OnPrepareDC, родительская версия которой обнаруживает и запрещает попытку использовать формульные режимы. В своей версии OnPrepareDC он обходит вызов родительской версии, то есть версии CSrollView, и вместо этого вызывает «дедушкину» версию CView::OnPrepareDC, которая терпимо относится к формульным режимам. Этот пример, на мой взгляд, очень убедительно демонстрирует гибкость объектно-ориентированного подхода при разработке достаточно сложных приложений.
CRightView::CRightView() {
m_szltem = CSize (200,150); // Размеры картинки
m_szMargin = CSize (20,20); // Размеры полей
try
{
//====== Попытка зарегистрировать класс окон
m_WndClass=AfxRegisterWndClass(CS_VREDRAWICS_HREDRAW, ::LoadCursor(GetModuleHandle(0),(char*)IDC_MYHAND), (HBRUSH)CreateSolidBrush(GetSysColor(COLOR_INFOBK)));
}
catch (CResourceException* pEx)
{
AfxMessageBox(_T("Класс уже зарегистрирован")); pEx->Delete ();
}
}
В конструкторе класса CRightView происходит попытка зарегистрировать новый класс окон. Обычно отказов здесь не бывает, но технология требует проверить наличие сбоя, поэтому включаем механизм обработки исключений (try-catch). Мы хотим добиться особого поведения окон с картинками, поэтому зададим для них свою форму курсора и свой цвет фона. Цвет фона выбирается из того набора, который предоставляет система (см. справку по функции GetSysColor), а курсор создали сами. Дело в том, что системный курсор, идентифицируемый как i DC_HAND, работает не во всех версиях Windows. Если вы работаете в среде Windows 2000, то можете заменить в параметре функции LoadCur sor вызов GetModuleHandle (0) на 0, а идентификатор IDC_MYHAND на IDC_HAND и работать с системным курсором. В этом случае ресурс курсора IDC_MYHAND окажется лишним и его можно удалить.
В данный момент мы предполагаем, что в классе документа уже создан динамический контейнер m_Shapes объектов класса CPolygon, каждый элемент которого соответствует данным, полученным в результате чтения документов, обнаруженных в текущем каталоге. Теперь приступим к разработке самой сложной функции в составе класса CRightView, которая должна:

  1. Пройти по всему перечню объектов m_shapes класса CPolygon.
  2. Вычислить исходя из текущего размера окна количество рядов и колонок мини-окон с изображениями полигонов.
  3. Создать для каждого из них окно, управляемое классом CWndGeom.

Дальше события развиваются автоматически. После создания окна cwndGeom система пошлет ему сообщение WM_PAINT, в обработке которого надо создать и настроить контекст устройства мини-окна, а затем вызвать функцию Draw для того полигона из контейнера m_Shapes, индекс которого соответствует индексу окна CWndGeom. Каждый полигон рисует себя сам в заданном ему в качестве параметра контексте устройства. Введите в файл реализации класса CRightView следующий код:
void CRightView::Show()
{
CTreeDoc *pDoc = GetDocument0;
//====== Количество картинок
int nPoly = pDoc->m_Shapes.size();
//=== Вычисление шага, с которым выводятся картинки
int dx = m_szltem.cx + m_szMargin.ex,
dy = m_szltem.cy + m_szMargin.cy,
nCols = m_szView.cx/dx; // Количество колонок
//====== Коррекция
if (nCols < 1)nCols = 1;
if (nCols > nPoly)nCols = nPoly;
//====== Количество рядов
int nRows = ceil(double(nPoly)/nCols);
//=== Вычисление и установка размеров окна прокрутки
m_szScroll = CSize(nCols*dx, nRows*dy);
SetScrollSizes(MM_TEXT, m_szScroll);
//====== Координаты и размеры первой картинки
CRect r (CPoint(0,0), m_szltem);
r.OffsetRect (15,15);
//====== Стиль окна картинки
DWORD style = WS_CHILD | WS_BORDER | WS_VISIBLE;
//====== Цикл прохода по рядам (n - счетчик картинок)
for (int 1=0, n=0; i<nRows; i++)
{
//====== Цикл прохода по столбцам
for (int j=0; j<nCols && rKnPoly; j++, n++)
{
//====== Создаем класс окна картинки
CWndGeora *pWnd = new CWndGeom(this, n);
//====== Запоминаем его в контейнере
m_pWnds.push_back(pWnd);
//====== Создаем Windows-окно
pWnd->Create (m_WndClass, 0, style, r, this, 0);
//====== Сдвигаем позицию окна вправо
r.OffsetRect (dx, 0);
}
//=== Начинаем новый ряд картинок (сдвиг влево-вниз)
r.OffsetRect (-nCols*dx, dy);
}
}
Существенным моментом в алгоритме является то, что размер прокручиваемого окна (m_szScroll) зависит от количества картинок. Поэтому сколько бы их не было в текущей папке — все будут доступны с помощью полос прокрутки. Расположение и размеры картинок определяются с помощью объекта класса CRect. Метод Of f setRect этого класса позволяет сдвигать прямоугольник окна в нужном нам направлении.
Обслуживание контейнера m_pWnds дочерних окон типа cwndGeom сопряжено с необходимостью следить за освобождением памяти, занимаемой окнами, в те моменты, когда происходит переход от папки к папке в окне CLef tview. Для этой цели служит вспомогательная функция Clear, которую надо вызывать как в отмеченные выше моменты, так и при закрытии окна. Последний случай сопровождается автоматическим вызовом деструктора класса CRightview. С учетом сказанного введите такие добавки в файл RightView.cpp:
void CRightview::Clear()
{
//====== Цикл прохода по всем адресам контейнера
for (UINT i=0; Km_pWnds. size () ; i++)
{
//====== Уничтожение Windows-окна
m_pWnds[i]->DestroyWindow();
// Освобождение памяти, занимаемой объектом
delete m_pWnds[ i ] ;
}
//===== Освобождение памяти, занимаемой контейнером m_pWnds.clear();
}
//===== Деструктор класса вызывает
Clear CRightview::~CRightview()
{
Clear () ;
}
  
Окна с геометрией данных
Характерный для MFC двухступенчатый способ создания окна cwndGeom объясняется тем, что с каждым окном связаны две сущности: Windows-окно, характеризуемое описателем окна, и объект класса cwndGeom, который мы еще должны разработать. В коде функции show для каждого полигона сначала динамически создается объект класса cwndGeom (конструктор класса), а затем — управляемое им Windows-окно (Create). При создании объекта мы передаем ему указатель на класс родительского окна и индекс полигона в контейнере. Поэтому окно впоследствии сможет найти нужный полигон в документе и изобразить его в своем контексте. Мы запоминаем адреса всех объектов CwndGeom в массиве m_pWnds, для того чтобы потом можно было уничтожить все Windows-окна (вызвав DestroyWindow), так же, как и все объекты класса cwndGeom (вызвав деструктор класса CWndGeom). Эту процедуру надо выполнять каждый раз, когда пользователь выбирает новый узел в файловом дереве.
Вам уже знакома процедура ввода в проект новых классов. Сейчас настала пора применить ее для ввода в проект класса cwndGeom. При работе с мастером MFC Class Wizard выберите в качестве базового класс cwnd и измените предлагаемые по умолчанию имена файлов, в которых будут размещены стартовые коды нового класса. Вместо WndGeom.h и WndGeom.cpp задайте RightView.h и RightView.cpp. После того как мастер закончит работу, вставьте в начало файла. RightView.h упреждающее объявление class CWndGeom; так как класс CRightview содержит массив указателей этого типа, а его объявление стоит до объявления cwndGeom.
Примечание

Надо отметить, что в бета-версии Studio.Net описываемый способ размещения нового класса работает неверно и для исправления ситуации мне пришлось убрать вновь добавленную директиву #pragma

once из файла RightView.h и три новые, вставленные мастером, директивы #include из файла RightView.cpp. Надеюсь, что у вас ошибок такого рода не будет.
Изготовленная мастером заготовка класса содержит несколько больше элементов, чем нам необходимо. В частности, не нужны макросы DECLARE_DYNAMIC и IMPLEMENT^ DYNAMIC, так как мы не собираемся использовать унаследованную от CObject функцию isKindOf. Посмотрите справку по концепции наследования от CObject, чтобы понять, как связаны макросы с функцией IsKindOf, затем уберите макросы и внесите изменения в интерфейс класса так, чтобы он был:
class CWndGeom : public CWnd
{
public:
CTreeDoc *m_pDoc;
// Адрес документа (для удобства)
CRightview *m_pView;
// Адрес родительского окна
int m_ID;
// Индекс окна документа в массиве CRect m_Rect;
// Координаты в правом окне
//====== Удобный для нас конструктор
CWndGeom (CRightview *p, int id);
~CWndGeom();
protected: DECLARE_MESSAGE_MAP()
};
В файле реализации класса измените коды конструктора, как показано ниже. Затем с помощью Studio.Net введите в класс реакции на следующие сообщения: WM_PAINT, WM_LBUTTONDOWN и WM_MOUSEMOVE. Цель этих действий такова. При наведении курсора мыши на одно из окон, управляемых классом CWndGeom, оно должно проявить признаки готовности быть выбранным. Для этого рисуем в нем обрамляющий прямоугольник, который исчезает при выходе указателя мыши за пределы окна. Эта функциональность реализуется за счет пары функций SetCapture - ReleaseCapture. Метод CWnd: : SetCapture захватывает текущее окно как адресат для последующих сообщений мыши независимо от позиции курсора. Поэтому при перемещении курсора мыши можно выйти из пределов клиентской области окна и все равно получить и обработать сообщение им_ MOUSEMOVE. На этом свойстве окна и построен алгоритм его подсветки. Функция ReleaseCapture «освобождает мышь», то есть вновь восстанавливает обычный порядок обработки мышиных сообщений. Мы вызываем функцию после того, как обнаружен факт выхода за пределы окна и снята подсветка, то есть стерт обрамляющий прямоугольник:
CWndGeom::CWndGeom(CRightView *p, int id)
{
//====== Запоминаем адрес родительского окна
m_pView = р;
//====== Запоминаем адрес документа
m_pDoc = p->GetDocument();
//====== и индекс окна в массиве
m_ID = id;
}
void CWndGeom::OnPaint()
{
CPaintDC dc(this);
dc.SetMapMode(MM_ISOTROPIC) ;
//====== Настраиваем логическое окно
dc.SetWindowOrg (m_pDoc->m_szDoc.cx/2, m_pDoc->m_szDoc.cy/2), dc.SetWindowExt(m_pDoc->m_szDoc);
//====== Узнаем текущие размеры окна
GetClientRect(&m_Rect);
int w = m_Rect.Width (), h = m_Rect.Height ();
//====== Настраиваем аппаратное окно
dc.SetViewportOrg (w/2, h/2);
dc.SetViewportExt (w, -h);
//=== Выбираем в контейнере нужный полигон и просим
//=== его изобразить себя в подготовленном контексте m_pDoc->m_Shapes[m_ID].Draw(Sdc);
}
void CWndGeom: :OnLButtonDown (UINT nFlags, CPoint point)
{
//====== Изменяем дежурный полигон
m_pDoc->m_Poly = m_pDoc->m_Shapes [m_ID] ;
//== Если не было CDrawView, то создаем его
if (m_pDoc->MakeView() )
return;
//=== Если он был, то находим его и делаем активным
CView *pView = m_pDoc->GetView (RUNTIME_CLASS (CDrawView) ;
{
(CMDIChildWnd*)pView->GetParentFrame ()
} ->MDIActivate () ;
//====== Перерисовка с учетом изменений
pView->Invalidate ( ) ;
Все «мышиные» сообщения сопровождаются параметрами, которые информируют нас о том, где и как произошло событие. Первый параметр есть набор битов, указывающих, какие виртуальные клавиши были нажаты в момент события. Например, если nFlags содержит бит с именем MK_CONTROL (символическая константа), то это означает, что в момент нажатия левой кнопки мыши также была нажата клавиша Ctrl. Второй параметр содержит координаты (х, у) местоположения курсора в момент события. Они заданы относительно верхнего левого угла клиентской области окна:
void CWndGeom: lOnMouseMove (UINT nFlags, CPoint point)
{
//====== Если указатель мыши в пределах окна,
if (m_Rect.Pt!nRect (point))
{
//====== то захватываем мышь, выбираем перо
//====== и рисуем обрамляющий прямоугольник
SetCapture () ;
CClientDC dc(this) ;
CPen pen (PS_SOLID, 4, RGB (192, 192, 255));
dc.SelectObject (&pen);
dc.MoveTo(m_Rect.left+4, m_Rect . top+4) ;
dc.LineTo (m_Rect.right-4, m_Rect . top+4) ;
dc.LineTo (m_Rect.right-4, m_Rect .bottom-4) ;
dc.LineTo (m_Rect.left+4, m_Rect .bottom-4) ;
dc.LineTo (m_Rect.left+4 , m_Rect . top+4) ;
}
else
{
ReleaseCapture () ;
// Освобождаем мышь Invalidated;
// Прямоугольник будет стерт
}
}
Так как в коде функции OnLButtonDown содержится обращение к объекту класса CDrawView, то необходимо в список директив препроцессора текущего файла (RightView.cpp) вставить еще одну: #include "DrawView.h".
  
Взаимодействие представлений документа
В данный момент мы имеем три класса (CLef tview, CRightView, CDrawView) для управления тремя представлениями одного документа. Взаимодействие между ними должно быть реализовано с помощью методов класса CTreeDoc, так как именно документ поддерживает список всех своих представлений. Начнем с того, что обеспечим видимость классов, вставив в список директив препроцессора файла ТгееDос.срр еще две:
#include "RightView.h"
#include "DrawView.h"
Затем перейдем к реализации заявленного в классе документа метода Getview (поиск адреса нужного представления). Его параметром служит адрес статической структуры типа CRuntimeClass, которая присутствует во всех классах, произведенных от cob j ect. Она является общей для всех объектов одного и того же класса и содержит ряд полезных полей, в том числе и поле m_lpszClassName, которое позволяет узнать имя класса на этапе выполнения программы. Обычно для того, чтобы узнать, принадлежит ли объект (адрес структуры CRuntimeClass которого вы знаете) тому или иному классу, пользуются функцией isKindOf, унаследованной от CObject. Она, в свою очередь, для ответа на этот вопрос использует поле m_lpszClassName структуры CRuntimeClass:
CView* CTreeDoc::GetView(const CRuntimeClass* pClass)
{
// Становимся в начало списка представлений документ^
POSITION pos = GetFirstViewPosition();
//====== Пессимистический прогноз
CView *pView = 0;
//====== Цикл поиска нужного представления
while (pos)
{
pView = GetNextView(pos);
//=== Если нашли, то возвращаем адрес
if (pView->IsKindOf(pClass))
break;
}
//===== Возвращаем результат поиска
return pView;
}
В процессе работы с MDI-приложением пользователь закрывает одни документы и открывает другие. Вновь открытый документ в начальный момент представлен одним из двух возможных типов окон: либо расщепленным окном типа CTreeFrame, которое содержит два окна CLef tview и CRightview, либо обычным MDI-child-окном типа CDrawFrame, которое содержит одно окно CDrawView. В ситуации, когда пользователь по картинке выбрал в правом окне один из документов, по сценарию необходимо создать новое окно типа CDrawFrame и в его клиентскую область поместить альтернативное представление (CDrawView) выбранного документа. Целесообразно реализовать и обратный сценарий, когда, имея окно типа CDrawView, пользователь хочет создать окно типа CTreeFrame, обрамляющего другие два представления документа.
Создание и инициализация новых окон того или иного типа в MDI-приложени-ях производится с помощью методов класса CDocTemplate, так как именно шаблон документа хранит информацию обо всех членах квартета, ответственных за создание окна документа. Список всех шаблонов документов, поддерживаемых приложением, хранит объект theApp класса СТгееАрр. Класс cwinApp, от которого происходит класс СТгееАрр, предоставляет стандартные методы для работы со списком шаблонов. Метод GetFirstDocTemplatePosition устанавливает позицию (переменную вспомогательного типа POSITION для работы со списками) на первый шаблон списка. Метод GetNextDocTemplate обычным образом возвращает адрес текущего шаблона и после этого сдвигает позицию на следующий элемент списка. Подобный стиль работы со списками поддерживается и другими классами MFC. Привыкнув к нему, вы сэкономите массу усилий в будущем.
Однако в нашем случае, когда существуют только два шаблона документов, нет необходимости искать в списке шаблонов. Мы просто запомнили их адреса (m_pTemplTree, m_pTemplDraw) в объекте theApp класса СТгееАрр. Теперь в любой момент жизни приложения мы можем добыть их и использовать, например для создания новых окон того или иного типа. Ниже приведен метод MakeView класса CTreeDoc, который выполняет указанное действие.
Примечание
Каркас MDI-приложения в принципе позволяет создать произвольное количество окон-двойников одного и того же документа и даже имеет для этой цели специальную команду (Window > New Window). Иногда это полезно, но в наш сценарий такая команда не вписывается. Поэтому мы ее убрали и пользуемся флагами m_bDrawExist и m_bTreeExist которые должны следить за ситуацией, чтобы не допустить дублирования окон.
Вы помните, что в любой точке программы мы имеем право вызвать глобальную функцию MFC. Напомним, однако, что почти все глобальные объекты MFC имеют префикс Afx (Application frameworks) — каркас приложения. Среди них есть много действительно полезных функций. Посмотрите справку по индексу Af х, и вы увидите все множество. Традиционно, для того чтобы достать адрес объекта theApp класса приложения, пользуются функцией Af xGetApp. Существует и второй способ — непосредственно использовать глобально определенный объект theApp, но для этого необходимо в начало срр-файла, где предполагается его использовать, поместить строку, разрешающую проблему видимости объекта theApp:
extern СТгееАрр theApp; // Определен в другом месте
В файл реализации класса CTreeDoc вставьте тело функции MakeView, которое приведено ниже. В ней реализован доступ к приложению с помощью глобальной функции AfxGetApp, но вы можете опробовать и второй способ, заменив "рАрр->" на " theApp. " и учтя сказанное выше. При этом также отпадает необходимость в строке кода СТгееАрр* рАрр = (СТгееАрр*) Af xGetApp ();.
bool CTreeDoc::MakeView()
{
//==== Если недостает какого-либо из представлений
if (!m_bDrawExist || !m_bTreeExist)
{
//====== Добываем адрес приложения
CTreeApp* pApp = (CTreeApp*) AfxGetApp ();
CDocTemplate *pTempl;
//====== Выбираем шаблон недостающего типа
if ( !m_bDrawExist)
{
pTempl = pApp->m_pTemplDraw;
m_bDrawExist = true;
}
else
{
pTempl = pApp->m_pTemplTree;
m_bTreeExist = true;
// Создаем окно документа
// Тип рамки и представления определяется шаблоном
CFrameWnd *pFrarae = pTempl->CreateNewFrame (this, 0) ; pTempl->InitialUpdateFrame (pFrarae, this) ;
return true;
}
return false;
}
Если вы хотите иметь современный и чуть более надежный код, то используйте вызов:
CTreeApp* pApp = dynamic_cast<CTreeApp*> (AfxGetApp ());
Всю работу по созданию окна-рамки и помещения в его клиентскую область выполняют методы CreateNewFrame И InitialUpdateFrame класса CDocTemplate, который является базовым для класса CMultiDocTemplate. Вы помните, что два объекта последнего класса мы создали в теле функции initlnstance для реализации MDI-функциональности по нашему сценарию. Сценарий еще пока не реализован. Введем изменения в метод OnNewDocument, для того чтобы правильно установить флаги существования окон:
BOOL CTreeDoc: : OnNewDocument ()
{
//====== При создании нового документа
if ( ICDocument: : OnNewDocument () )
return FALSE;
//====== Документ знает свой шаблон
CDocTemplate* pTempl = GetDocTemplate () ;
CString s;
//====== Выясняем его тип из строкового ресурса
pTempl->GetDocStrlng (s, CDocTemplate: : fileNewName) ;
m_bDrawExist — s == "Draw";
m_bTreeExist = !m_bDrawExist;
return TRUE;
}
При создании нового документа пользователь выбирает один из двух шаблонов (Tree, Draw), предложенных ему в диалоге New, который, как вы помните, поддерживает каркас приложения. Наша задача — выяснить выбор, сделанный пользователем. Это можно сделать с помощью одного из членов квартета, а именно строкового ресурса, связанного с каждым из шаблонов. Метод GetDocString выделяет подстроку комплексной строки, и по ее содержимому мы узнаем выбор пользователя.
Перейдем к разработке следующего метода класса CTreeDoc. При переводе фокуса с одного узла дерева на другой мы должны освободить память, занимаемую контейнером полигонов m_Shapes и другими временными данными, которые соответствуют документам, обнаруженным в текущей папке. Эти действия выполняет метод FreeDocs. При освобождении контейнера методом clear он вызывает для каждого из своих объектов деструктор. Так.как класс CPolygon мы снабдили деструктором, освобождающим свой вложенный контейнер точек (CDPoint), то вызов m_Shapes. clear (); порождает целую цепочку действий, которую вы можете проследить. Для этого установите точку останова (F9) в теле деструктора класса CPolygon, запустите приложение в режиме отладки (F5) и откройте окно Call Stack, которое позволяет увидеть всю цепочку вызовов функций. Открыть окно Call Stack вы сможете, дав команду Debug > Windows > Call Stack. Команда доступна только в режиме отладки (F5):
void CTreeDoc::FreeDocs()
{
m_sFiles.clear(); m_Shapes.clear();
//====== Выясняем адрес правого окна
CRightView *pView = dynamic_cast<CRightView*>
(GetView(RUNTIME_CLASS(CRightView)));
//====== Освобождаем окна-картинки
if (pView) pView->Clear();
}
При обращении к функции Getview мы должны подать на вход адрес структуры CRuntimeClass, которая характеризует искомый класс. Это можно сделать двумя способами: используя макроподстановку RUNTIME_CLASS(), как и сделано выше, или подставив более длинное, но разъясняющее суть макроса, выражение:
Getview(SCRightView::classCRightView)
Выражения:
RUNTIME_CLASS(CRightView)
И
&CRightView::classCRightView
эквивалентны. Вторая форма записи подсказывает вам, что в классе CRightView определена статическая переменная classCRightview типа CRuntimeClass, которая помогает по адресу объекта определить его тип на этапе выполнения.
Рассмотрим метод ProcessDocs класса CTreeDoc, который обрабатывает информацию о файлах документов, обнаруженных в текущей папке. Здесь демонстрируется, как связать архив (объект класса CArchive) с файлом (объектом класса CFile) и заставить объект прочесть данные из файла. Для этой цели используется всего" один временный объект poly класса с Polygon. Данные очередного документа сначала читаются из файла в этот объект — poly. Serialize (ar); а затем весь объект помещается в контейнер — m_Shapes .push_back (poly). Контейнеры устроены таким образом, что они создают свою собственную копию объекта и именно ее и хранят. Благодаря этому мы можем многократно использовать временный объект poly:
void CTreeDoc::ProcessDocs()
{
UINT nFiles = m_sFiles.size();
//====== Если документы не обнаружены
if (!nFiles)
return;
for (UINT i=0; i < nFiles; i++)
{
//====== Читаем все документы
GFile file; // Класс, управляющий файлами
CFileException e; // Класс для обработки сбоев
CString fn = m_sFiles[i); // Имя файла
if (Ifile.Open (fn, CFile::modeRead |
CFile::shareDenyWrite, &e) )
{
//=== В случае сбоя в зависимости от причины
//=== выдаем то или иное сообщение
CString rasg =
e.m_cause == CFileException::fileNotFound ? "Файл: " + fn + " не найден" : "Невозможно открыть " + fn; AfxMessageBox(msg);
return;
}
//====== Связываем архив с файлом
CArchive ar (sfile, CArchive::load);
CPolygon poly; // Временный полигон poly.Set(this);
// Обратный указатель poly.Serialize (ar);
//Читаем данные m_Shapes.push_back(poly);
// Запоминаем в массиве
}
//====== Отображаем результат в правом окне
CRightView *pView - dynamic_cast<CRightView*>
(GetView(RUNTIME_CLASS(CRightView)));
pView->Show();
}
При работе с классами CFile, CFileException и CArchive используются статические переменные, которые задают режимы работы. Так, битовые флаги CFile::modeRead (для чтения) и CFile::shareDenyWrite (запретить запись всем другим процессам) задают режим открытия файла. Переменная CArchive::load (чтение) определяет направление сериализации.
Мы сделали достаточно много для правильного взаимодействия представлений документа, но при закрытии какого-либо из окон флаги m_bTreeExist и m_bDrawExist остаются неизменными, что, несомненно, нарушит логику поведения приложения. Событие закрытия окна-рамки необходимо обработать и скорректировать соответствующий флаг. Поэтому введите в классы CTreeFrame и CDrawFrame реакции на сообщение WM_CLOSE и вставьте внутрь обработчиков следующие коды:
void CTreeFrame::OnClose()
{
//====== Добываем адрес активного документа
CTreeDoc *pDoc = dynamic_cast<CTreeDoc*> (GetActiveDocument());
pDoc->m_bTreeExist = false;
CMDIChildWnd::OnClose();
}
void CDrawFrame::OnClose()
void CDrawFrame::OnClose()
{
CTreeDoc *pDoc = dynamic_cast<CTreeDoc*> (GetActiveDocument());
pDoc->m_bDrawExist = false;
CMDIChildWnd::OnClose() ;
}
Вы уже, наверное, привыкли к тому, что при введении функций-обработчиков, которые обращаются к объектам других классов приложения, надо корректировать директивы подключения заголовочных файлов. Вот и сейчас надо вставить директиву #include "TreeDoc.h" в файл реализации класса CDrawFrame.
В настоящий момент приложение готово к запуску. Уберите временные комментарии, которые вставляли раньше, запустите приложение, устраните ошибки и протестируйте. Его поведение должно быть ближе к задуманному. Для проверки необходимо с помощью команды File > Save as записать некоторое количество документов, давая им различные имена. После этого следует убедиться, что каждый раз, как фокус выбора попадает в папку, где записаны документы, в правом окне появляются мини-окна типа cwndGeom с изображением полигона. При выборе одного их них щелчком левой кнопки мыши должно создаваться и активизироваться новое окно типа CDrawView. В этот момент полезно дать команду Window > Tile Horizontally, для того чтобы увидеть оба типа окон-рамок со всеми тремя представлениями одного документа. Если документы сохранить на гибком диске (и держать диск в дисководе), то они должны отображаются сразу после запуска приложения, так как сообщение =TVN_SELCHANGED поступает при инициализации левого окна.

 

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