Работа с Visual Studio.Net

         

Окна с геометрией данных


Характерный для 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".

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