Класс представления документа 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) уже должен быть известен системе.
Примечание 1
Примечание 1
Прекрасное решение дал 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, которая должна:
Дальше события развиваются автоматически. После создания окна 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 () ;
}