Окна
с геометрией данных
Характерный
для 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".