Kselax.ru

Hacker Kselax — the best hacker in the world

Menu
  • Блог
  • Контакты
  • wp plugin генератор
  • Русский
    • Русский
    • English
Menu

MFC — создание документа и усовершенствование представления

Posted on 17 ноября, 2014 by admin

Здорова ребятки!

Сегодня мы будем улучшать нашу программу разработанную в посте MFC — заставляем редактор рисовать фигуры и улучшенную в посте Книга «visual C++ полный курс» решение задач глава 16 мы там в задачах улучшали наше приложение и оно имеет вид

MFC appи если мы например свернем или развернем приложение или я сейчас его спрячу часть и выведу на экран, то половина рисунка сотрется смотрите скрин ниже

MFC half app

Что бы такого не происходило нужно сохранять наши рисуемые элементы в классе документа и затем при недействительности области перерисовывать их. В общем приступим к реализации.

Создание документа приложения Sketcher

Мы будем хранить элементы в контейнере STL list<CElement*>, подключите его к классу документа в заголовочном файле после #pragma

1
2
3
4
5
6
7
8
9
// Sketcher13Doc.h : интерфейс класса CSketcher13Doc
//
 
 
#pragma once
#include "sketcherconstants.h"
#include <list>
#include "Elements.h"
...

Дальше к классу документа добавьте открытый член AddElement

1
2
3
4
void AddElement(CElement* pElement)//Добавить элемент в список
{
m_ElementList.push_back(pElement);
}

и в документ добавьте защищенный член std::list<CElement*> m_ElementList;

1
std::list<CElement*> m_ElementList;//Список элементав эскиза

Дальше реализуем деструктор документа, в нем мы должны освобождать память и удалять список

1
2
3
4
5
6
7
8
9
10
CSketcher13Doc::~CSketcher13Doc()
{
//Удаление элементов, на которые указывает каждая запись в списке
for(auto iter=m_ElementList.begin();
iter!=m_ElementList.end();++iter)
{
delete *iter;//освобождение памяти
}
m_ElementList.clear();//удаление всех указателей
}

Создадим еще в классе документа две открытые функции begin и end обеспечивающие безопасный доступ к m_ElementList

1
2
3
4
5
6
//Получить итератор начала списка
std::list<CElement*>::const_iterator begin() const
{return m_ElementList.begin();}
//Получить итератор конца списка
std::list<CElement*>::const_iterator end() const
{return m_ElementList.end();}

Изменим функцию вида OnDraw, вот ее код

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void CSketcher13View::OnDraw(CDC* pDC)
{
CSketcher13Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
 
CElement* pElement(nullptr);
for(auto iter=pDoc->begin();iter!=pDoc->end();++iter)
{
pElement=*iter;
//если элемент видим нарисовать его
if(pDC->RectVisible(pElement->GetBoundRect()))
pElement->Draw(pDC);
}
}

RectVisible получает прямоугольник и проверяет видим ли элемент, если видим, то его нужно нарисовать.

Добавление элемента в документ будет происходить в функции OnLButtonUp класса вида, вот ее код

1
2
3
4
5
6
7
8
9
10
11
12
13
void CSketcher13View::OnLButtonUp(UINT nFlags, CPoint point)
{
if(this==GetCapture())
ReleaseCapture();//Прекратить захват сообщений мышки
 
//Если есть элемент, добавить его к документу
if(m_pTempElement)
{
GetDocument()->AddElement(m_pTempElement);
InvalidateRect(nullptr);//Перерисовать текущее окно
m_pTempElement=nullptr;//обнуляем указатель
}
}

 

Все дальше компилируем и смотрим что у нас получилось! В общем все у нас работает, можем теперь наше приложение свернуть — развернуть или завести за область видимости экрана и обратно и у нас не пропадают линии, так как они перерисовываются.

Обновление множественных представлений

Почитать как это можно делать и зачем вы можете в посте MFC — множественное обновление видов представления. Кстати я сейчас посмотрел у нас если создать вкладку для представления выбрав пункт меню Окно -> Новое окно, там обновляется все, и если мы нарисуем что нить в одной вкладке у нас рисуется это и во второй вкладке, ну в принципе нам можно наверно и не делать множественное обновление представлений так как все обновляется, но все же мы сделаем.

Для этого изменим функцию OnLButtonUp класса вида

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void CSketcher13View::OnLButtonUp(UINT nFlags, CPoint point)
{
if(this==GetCapture())
ReleaseCapture();//Прекратить захват сообщений мышки
 
//Если есть элемент, добавить его к документу
if(m_pTempElement)
{
GetDocument()-&gt;AddElement(m_pTempElement);
//Сообщить всем представлениям
GetDocument()-&gt;UpdateAllViews(0,0,m_pTempElement);
m_pTempElement=nullptr;//обнуляем указатель
}
}

Теперь нам следует добавить для класса вида переопределение события OnUpdate для этого заходим в свойства выбираем переопределение и выбираем событие OnUpdate

MFC OnUpdateУ нас создастся  функция-обработчик OnUpdate и от ее код

1
2
3
4
5
6
7
8
9
10
void CSketcher13View::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
{
//Объявить недействительной область, соответствующую указанному
//элементу, если он есть, иначе объявить недействительной
//всю клиентскую область
if(pHint)
InvalidateRect(static_cast<CElement*>(pHint)->GetBoundRect());
else
InvalidateRect(nullptr);//делаем недействительной всю областьы
}

Можно откомпилировать и все будет работать как обычно.

Прокрутка представлений

Почитать про то как делать прокрутку представлений можно в статье MFC — прокрутка представлений класс CScrollView.

Для того что бы появились полосы прокрутки у нас нужно класс вида сделать наследником от класса CScrollView. Заменим CView на CScrollView в заголовочном файле

1
2
3
4
5
6
7
8
...
//class CSketcher13View : public CView
class CSketcher13View : public CSclollView
{
protected: // создать только из сериализации
CSketcher13View();
DECLARE_DYNCREATE(CSketcher13View)
...

и в файле с определением .cpp заменить также две строчки

1
2
3
4
5
6
7
...
//IMPLEMENT_DYNCREATE(CSketcher13View, CView)
IMPLEMENT_DYNCREATE(CSketcher13View, CScrollView)
 
//BEGIN_MESSAGE_MAP(CSketcher13View, CView)
BEGIN_MESSAGE_MAP(CSketcher13View, CScrollView)
...

Так же нам нужно перегрузить переопределение OnInitialUpdate для класса вида

MFC OnInitialUpdateи от код созданного обработчика OnInitialUpdate

1
2
3
4
5
6
7
8
9
10
void CSketcher13View::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
 
//Определить размер документа
CSize DocSize(20000,20000);
 
//Установить режим отображения и размер документа
SetScrollSizes(MM_TEXT,DocSize,CSize(500,500),CSize(50,50));
}

 

Все откомпилируем программу и у нас появились полосы прокрутки

MFC scrolling

Так идем дальше, у нас есть проблемы, мы когда прокручиваем колесико вниз или вбок например и попытаемся нарисовать какую нить фигуру они у нас не рисуются, а почему так происходит? А потому что мы рисуем в клиентских координатах, для того что б элементы рисовались нам нужно переводить клиентские координаты в логические

Робота с клиентскими координатами

Почитать про клиентские и логические координаты вы можете все в той же статье MFC — прокрутка представлений класс CScrollView. А мы идем дальше, изменим обработчик сообщения нажатия левой клавиши мышки OnLButtonDown класса вида

1
2
3
4
5
6
7
8
9
void CSketcher13View::OnLButtonDown(UINT nFlags, CPoint point)
{
CClientDC aDC(this);//создать контекст устройства
OnPrepareDC(&aDC);//получить уточненную начальную точку
aDC.DPtoLP(&point);//преобразовать точку в логические координаты
 
SetCapture();//перехватыватьв се последующие сообщения
m_StartPoint=point;//сохраняем первую точку.
}

Меняем обработчик OnMouseMove класса вида

Показать »

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void CSketcher13View::OnMouseMove(UINT nFlags, CPoint point)
{
//контекст устройства для текущего представления
CClientDC aDC(this);
OnPrepareDC(&aDC);//получить уточненную начальную точку
aDC.DPtoLP(&point);//преобразовать точку в логические координаты
 
//проверяем нажата ли кнопка мышки и курсор пренадлежит нашему виду
if((nFlags&MK_LBUTTON)&&(this==GetCapture()))
{
m_EndPoint=point;
//если нарисован элемент, то его нужно удалить
if(m_pTempElement)
{
if(CURVE==GetDocument()->GetElementType())
{
//добавляем точку к временному элементу
static_cast<CCurve*>(m_pTempElement)->AddSegment(m_EndPoint);
m_pTempElement->Draw(&aDC);//перерисовываем элемент
return;
}
 
aDC.SetROP2(R2_NOTXORPEN);//устанавливаем режим
m_pTempElement->Draw(&aDC);//удаляем элемент
delete m_pTempElement;//удаляем память
m_pTempElement=nullptr;//обнуляем указатель
}
//рисуем элемент
m_pTempElement=CreateElement();
m_pTempElement->Draw(&aDC);
}
}

Изменим еще метод OnUpdate который мы переопределили вот его новый код

Показать »

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void CSketcher13View::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
{
//Объявить недействительной область, соответствующую указанному
//элементу, если он есть, иначе объявить недействительной
//всю клиентскую область
if(pHint)
{
CClientDC aDC(this);//получить контекст устройства
OnPrepareDC(&aDC);//уточнить начальную точку
 
//Получить ограничивающий прямоугольник и преобразовать
//в клиентские координаты
CRect r=static_cast<CElement*>(pHint)->GetBoundRect();
aDC.LPtoDP(r);//преобразовать из логических в клиентские координаты
InvalidateRect(r);//делаем область недействительной
}
else
InvalidateRect(nullptr);//делаем недействительной всю областьы
}

Теперь откомпилируем и запустим программу, прокрутим колесика ниже и попытаемся нарисовать какую нить фигуру, я нарисовал квадрат и она у меня никуда не исчезла

MFC Scrolling normal

ну и добавим режим MM_LOINGLISH для этого изменим переопределенную функцию OnInitialUpdate класса вида на следующую

1
2
3
4
5
6
7
8
9
10
void CSketcher13View::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
 
//Определить размер документа 30 на 30 дюймов
CSize DocSize(3000,3000);
 
//Установить режим отображения и размер документа
SetScrollSizes(MM_LOENGLISH,DocSize);
}

и от компилируем запустим все у нас работает, изменилось только то что нижняя полоса стал ползунок побольше

MFC MM_LOENGLISH

Удаление и перемещение фигур

И так нам нужно реализовать удаление и перемещение фигур. Нам нужно создать 2 контекстных меню. Заходим в Окно ресурсов, выбираем меню и добавляем новое меню.

MFC insert menu

Переименуйте его в IDR_ELEMENT_MENU и добавьте 3 пункта к нему Move, Delete, Send To Back

MFC contextMenuИ добавьте еще одно меню и назовите его IDR_NOELEMENT_MENU и добавьте туда уже существующие пункты меню Element и Color просто перейдите в меню где созданы эти пункты, выделите их и скопируйте их в ваше новое меню IDR_NOELEMENT_MENU

MFC menu

MFC noelement

Все меню мы создали, теперь нам нужно связать наши меню с классом, для этого зайдите в класс самого приложения App и измените функцию PreLoadState

1
2
3
4
5
6
7
8
9
10
11
void CSketcher13App::PreLoadState()
{
/*BOOL bNameValid;
CString strName;
bNameValid = strName.LoadString(IDS_EDIT_MENU);
ASSERT(bNameValid);
GetContextMenuManager()->AddMenu(strName, IDR_POPUP_EDIT);*/
 
GetContextMenuManager()->AddMenu(_T("element menu"), IDR_ELEMENT_MENU);
GetContextMenuManager()->AddMenu(_T("noelement menu"), IDR_NOELEMENT_MENU);
}

и так мы закомментировали старый код, нам он уже не нужен, он создавал старое контекстное меню которое создавалось по умолчанию мастером.

Дальше идем в класс вида и выбираем функцию OnContextMenu, она у нас уже создана, если б ее небыло пришлось бы ее создавать так же как мы создавали другие обработчики, ну то есть ее перегрузить бы пришлось выбрав соответствующий пункт меню сообщение и там уже выбрать сообщение WM_CONTEXTMENU

MFC WM_CONTEXTMENU

Вот код этого обработчика

1
2
3
4
5
6
7
8
9
10
void CSketcher13View::OnContextMenu(CWnd* /* pWnd */, CPoint point)
{
#ifndef SHARED_HANDLERS
//theApp.GetContextMenuManager()->ShowPopupMenu(IDR_POPUP_EDIT, point.x, point.y, this, TRUE);
if(m_pSelected)
theApp.GetContextMenuManager()->ShowPopupMenu(IDR_ELEMENT_MENU,point.x,point.y,this,TRUE);
else
theApp.GetContextMenuManager()->ShowPopupMenu(IDR_NOELEMENT_MENU,point.x,point.y,this,TRUE);
#endif
}

код который закоменнтирован — это старый код и нам он уже не нужен, он подключал старое меню. Как видно мы проверяем новую переменную m_pSelected и ее нужно добавить в класс вида как защищенный член. Добавим ее! А в конструкторе ее инициализируем nullptr

1
2
3
4
5
6
7
8
...
CSketcher13View::CSketcher13View()
: m_StartPoint(0)
, m_EndPoint(0)
, m_pTempElement(nullptr)
, m_pSelected(nullptr)
{
...

Идентификация выбранного элемента

Нам осталось теперь сделать так, что бы наше приложение знало когда выбран элемент, а когда нет, для того чтобы правильно выводить то или иное меню. Для этого изменим обработчик OnMouseMove класса вида

Показать »

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
void CSketcher13View::OnMouseMove(UINT nFlags, CPoint point)
{
//контекст устройства для текущего представления
CClientDC aDC(this);
OnPrepareDC(&aDC);//получить уточненную начальную точку
aDC.DPtoLP(&point);//преобразовать точку в логические координаты
 
//проверяем нажата ли кнопка мышки и курсор пренадлежит нашему виду
if((nFlags&MK_LBUTTON)&&(this==GetCapture()))
{
m_EndPoint=point;
//если нарисован элемент, то его нужно удалить
if(m_pTempElement)
{
if(CURVE==GetDocument()->GetElementType())
{
//добавляем точку к временному элементу
static_cast<CCurve*>(m_pTempElement)->AddSegment(m_EndPoint);
m_pTempElement->Draw(&aDC);//перерисовываем элемент
return;
}
 
aDC.SetROP2(R2_NOTXORPEN);//устанавливаем режим
m_pTempElement->Draw(&aDC);//удаляем элемент
delete m_pTempElement;//удаляем память
m_pTempElement=nullptr;//обнуляем указатель
}
//рисуем элемент
m_pTempElement=CreateElement();
m_pTempElement->Draw(&aDC);
}
else
{
//Мы не создаем элемент, значит выделить его
//Получить указатель на документ
CSketcher13Doc* pDoc=GetDocument();
//Установить выделенный элемент
m_pSelected=pDoc->FindElement(point);
}
}

Добавим в класс документа функцию FindElement

1
2
3
4
5
6
7
8
9
10
11
12
13
//найти элемент под точкой
CElement* CSketcher13Doc::FindElement(const CPoint& point)const
{
for(auto rIter=m_ElementList.rbegin();
rIter!=m_ElementList.rend();++rIter)
{
//если точка находится в описывающем четырехугольнике
//то возврат элемента
if((*rIter)->GetBoundRect().PtInRect(point))
return *rIter;
}
return nullptr;
}

Компилируем и пытаемся вызвать наши меню, раз вызываем на пустом месте и другой раз вызываем над фигурой и у нас получаются каждый раз разные меню

над элементом

MFC contextMenu moveи над пустым местом

MFC contextMenu nadElementomкак видите менюшки у нас работают все отлично. Пункты меню для перемещения удаления элементов не подсвечиваются потому, что для них мы еще не создали обработчики и поэтому они не активны, как только мы создадим обработчики они станут активными.

Подсветка элемента

Дальше реализуем подсветку элемента, для этого нам необходимо изменить виртуальную функцию Draw для нашего класса CElement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class CElement : public CObject
{
protected:
DWORD m_Style;//стиль линии
int m_PenWidth;//ширина пера
COLORREF m_Color;//цвет фигуры
CRect m_EnclosingRect;//описывающий четырехугольник
public:
virtual ~CElement();
virtual void Draw(CDC* pDC,CElement* pElement=nullptr){};//рисует фигуру
virtual CRect GetBoundRect();//возвращает описывающий четырехугольник
protected:
CElement();//предотвращение вызова в предках
};

Мы передали еще одну переменную в этот класс pElement типа CElement* которой присвоили значение по умолчанию nullptr, это сделано для того чтобы эту функцию можно было вызывать с одним параметром. Так же эту функцию нужно изменить и для других классов, например для CLine так же добавляем второй элемент

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class CLine :
public CElement
{
protected:
CPoint m_StartPoint;
CPoint m_EndPoint;
 
public:
~CLine(void);
//функция для отображения линии
virtual void Draw(CDC* pDC,CElement* pElement=nullptr);
//конструктор преобразования
CLine(const CPoint& start, const CPoint& end, COLORREF color,DWORD aStyle);
protected:
CLine();//запрет вызова
};

и изменим определение это функции для линии

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void CLine::Draw(CDC* pDC,CElement* pElement)
{
CPen aPen;//общект нового пера
//создаем перо, если не удача то закрываем программу
if(!aPen.CreatePen(m_Style,m_PenWidth,this==pElement?SELECTED_COLOR:m_Color))
{
//выводим сообщение о неудаче и завершаем программу
AfxMessageBox(_T("Не удалось создать перо для линии"),MB_OK);
AfxAbort();//завершение программы
}
//выбираем новое перо и сохраняем старое
CPen* pOldPen=pDC->SelectObject(&aPen);
//рисуем линию
pDC->MoveTo(m_StartPoint);
pDC->LineTo(m_EndPoint);
//возвращаем старое перо
pDC->SelectObject(pOldPen);
}

и создадим новую переменную SELECTED_COLOR в файле где хранятся наши константы SketcherContstants.h

1
const COLORREF SELECTED_COLOR=RGB(255,0,180);

Подключите этот файл директивой include в файл с нашим классом CElement.

Функцию Draw изменяем для каждого элемента так же как и для линии.

Дальше изменим обработчик OnMouseMove класса вида

Показать »

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
void CSketcher13View::OnMouseMove(UINT nFlags, CPoint point)
{
//контекст устройства для текущего представления
CClientDC aDC(this);
OnPrepareDC(&aDC);//получить уточненную начальную точку
aDC.DPtoLP(&point);//преобразовать точку в логические координаты
 
//проверяем нажата ли кнопка мышки и курсор пренадлежит нашему виду
if((nFlags&MK_LBUTTON)&&(this==GetCapture()))
{
m_EndPoint=point;
//если нарисован элемент, то его нужно удалить
if(m_pTempElement)
{
if(CURVE==GetDocument()->GetElementType())
{
//добавляем точку к временному элементу
static_cast<CCurve*>(m_pTempElement)->AddSegment(m_EndPoint);
m_pTempElement->Draw(&aDC);//перерисовываем элемент
return;
}
 
aDC.SetROP2(R2_NOTXORPEN);//устанавливаем режим
m_pTempElement->Draw(&aDC);//удаляем элемент
delete m_pTempElement;//удаляем память
m_pTempElement=nullptr;//обнуляем указатель
}
//рисуем элемент
m_pTempElement=CreateElement();
m_pTempElement->Draw(&aDC);
}
else
{
//Мы не создаем элемент, значит выделить его
//Получить указатель на документ
CSketcher13Doc* pDoc=GetDocument();
CElement* pOldSelected(m_pSelected);
//Установить выделенный элемент
m_pSelected=pDoc->FindElement(point);
if(m_pSelected==pOldSelected)
{
if(m_pSelected)//подсвечиваем новый элемент
InvalidateRect(m_pSelected->GetBoundRect(),FALSE);
if(pOldSelected)//снимаем подсветку со старого элемента
InvalidateRect(pOldSelected->GetBoundRect(),FALSE);
pDoc->UpdateAllViews(nullptr);//обновить все виды
}
}
}

И наконец изменяем функцию OnDraw класса вида которая и ресует все элементы, вот ее код

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void CSketcher13View::OnDraw(CDC* pDC)
{
CSketcher13Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
 
CElement* pElement(nullptr);
for(auto iter=pDoc->begin();iter!=pDoc->end();++iter)
{
pElement=*iter;
//если элемент видим нарисовать его
if(pDC->RectVisible(pElement->GetBoundRect()))
pElement->Draw(pDC,m_pSelected);//видим нарисовать его
}
}

Дальше компилируем программу и запускаем на выполнение и ура у нас фигуры уже выделяюстя

MFC vudilenie

Теперь нам нужно реализовать удаление элемента для этого создадим обработчик события для пункта контекстного меню Delete в классе вида

1
2
3
4
5
6
7
8
9
10
11
void CSketcher13View::OnElementDelete()
{
if(m_pSelected)
{
//Получить указатель на документ
CSketcher13Doc* pDoc=GetDocument();
pDoc->DeleteElement(m_pSelected);//удалить элемент
pDoc->UpdateAllViews(nullptr);//обвновить все виды
m_pSelected=nullptr;//Обнулить указатель на выбраный элемент
}
}

и добавим функцию DeleteElement в класс документа

1
2
3
4
5
6
7
8
void CSketcher13Doc::DeleteElement(CElement* pElement)//удалить элемент
{
if(pElement)
{
m_ElementList.remove(pElement);//удалить указатель из списка
delete pElement;//удалить элемент из распределяемой памяти
}
}

Все если вы откомпилируете программу, то вы уже сможете удалять выделенные элементы.

Перемещение элемента

Теперь реализуем перемещение элемента, для начала добавим три защищенные переменные в класс вида

1
2
3
bool m_MoveMode;//флаг перемещения элемента
CPoint m_CursorPos;//позиция курсора
CPoint m_FirstPos;//исходная позиция в перемещении

Новые члены должны быть также инициализированы в конструкторе класса вида

1
2
3
4
5
6
7
8
9
10
11
CSketcher13View::CSketcher13View()
: m_StartPoint(0)
, m_EndPoint(0)
, m_pTempElement(nullptr)
, m_pSelected(nullptr)
, m_MoveMode(false)
, m_CursorPos(CPoint(0,0))
, m_FirstPos(CPoint(0,0))
{
// TODO: добавьте код создания
}

Добавьте обработчик для пункта меню Move в класс вида, вот его код

1
2
3
4
5
6
7
8
9
10
11
12
13
void CSketcher13View::OnElementMove()
{
CClientDC aDC(this);
OnPrepareDC(&aDC);//установить контекст устройства
//Получить позицию курсора в экранных координатах
GetCursorPos(&m_CursorPos);
//Преобразовать в клиентские координаты
ScreenToClient(&m_CursorPos);
//Преобразовать в логические координаты
aDC.DPtoLP(&m_CursorPos);
m_FirstPos=m_CursorPos;//Запомнить первую позицию
m_MoveMode=true;//запустить режим перемещения
}

И дальше изменим обработчик события WM_MOUSEMOVE класса вида

Показать »

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
void CSketcher13View::OnMouseMove(UINT nFlags, CPoint point)
{
//контекст устройства для текущего представления
CClientDC aDC(this);
OnPrepareDC(&aDC);//получить уточненную начальную точку
aDC.DPtoLP(&point);//преобразовать точку в логические координаты
 
//если включен режим перемещения
//передвинуть выбранный элемент и вернуть управление
if(m_MoveMode)
{
MoveElement(aDC,point);//Переместить элемент
return;
}
 
//проверяем нажата ли кнопка мышки и курсор пренадлежит нашему виду
if((nFlags&MK_LBUTTON)&&(this==GetCapture()))
{
m_EndPoint=point;
//если нарисован элемент, то его нужно удалить
if(m_pTempElement)
{
if(CURVE==GetDocument()->GetElementType())
{
//добавляем точку к временному элементу
static_cast<CCurve*>(m_pTempElement)->AddSegment(m_EndPoint);
m_pTempElement->Draw(&aDC);//перерисовываем элемент
return;
}
 
aDC.SetROP2(R2_NOTXORPEN);//устанавливаем режим
m_pTempElement->Draw(&aDC);//удаляем элемент
delete m_pTempElement;//удаляем память
m_pTempElement=nullptr;//обнуляем указатель
}
//рисуем элемент
m_pTempElement=CreateElement();
m_pTempElement->Draw(&aDC);
}
else
{
//Мы не создаем элемент, значит выделить его
//Получить указатель на документ
CSketcher13Doc* pDoc=GetDocument();
CElement* pOldSelected(m_pSelected);
//Установить выделенный элемент
m_pSelected=pDoc->FindElement(point);
if(m_pSelected==pOldSelected)
{
if(m_pSelected)//подсвечиваем новый элемент
InvalidateRect(m_pSelected->GetBoundRect(),FALSE);
if(pOldSelected)//снимаем подсветку со старого элемента
InvalidateRect(pOldSelected->GetBoundRect(),FALSE);
pDoc->UpdateAllViews(nullptr);//обновить все виды
}
}
}

Создадим функцию в классе вида MoveElement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void CSketcher13View::MoveElement(CClientDC& aDC, CPoint& point)//перемещение элемента
{
//Получить расстояние перемещения
CSize distance=point-m_CursorPos;
//Установить текущую точку как первую для следующего раза
m_CursorPos=point;
 
//Если есть выбранный элемент переместить его
if(m_pSelected)
{
aDC.SetROP2(R2_NOTXORPEN);
//Нарисовать элемент, чтобы стереть его
m_pSelected->Draw(&aDC,m_pSelected);
//Переместить элемент
m_pSelected->Move(distance);
//Нарисовать передвинутый элемент
m_pSelected->Draw(&aDC,m_pSelected);
}
}

Теперь нам нужно заставить элемент перемещать самого себя, для этго создадим виртуальную функцию в классе CElement и назовем ее Move

1
virtual void Move(const CSize& aSize){}//Переместить элемент

Теперь добавим эту функцию Move в виде открытого члена в каждый класс производный от класса CElement. Дальше реализуем функцию Move для класса CLine в файле Elements.cpp

1
2
3
4
5
6
void CLine::Move(const CSize& aSize)
{
m_StartPoint+=aSize;//переместить начальную точку
m_EndPoint+=aSize;//и конечную
m_EnclosingRect+=aSize;//переместить описывающий прямоугольник
}

Для класса CRectangle

1
2
3
4
void CRectangle::Move(const CSize& aSize)
{
m_EnclosingRect+=aSize;//переместить описывающий прямоугольник
}

Для CCircle

1
2
3
4
void CCircle::Move(const CSize& aSize)
{
m_EnclosingRect+=aSize;//переместить описывающий прямоугольник
}

и для CCurve

1
2
3
4
5
6
7
8
void CCurve::Move(const CSize& aSize)
{
m_EnclosingRect+=aSize;//переместить описывающий прямоугольник
 
//Теперь переместите все точки
std::for_each(m_Points.begin(),m_Points.end(),
[&aSize](CPoint& p){p+=aSize;});
}

для std::for_each подключите заголовочный файл algorithm

1
#include <algorithm>

Все если вы сейчас откомпилируете программу и запустите, то пункт Move будет уже активным и можно перемещать элементы, мы входим режим перемещения, но его нельзя збросить, сейчас как раз займемся разработкой зброса элемента.

Сначала позаботимся о левой кнопке мышки OnLButtonDown

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void CSketcher13View::OnLButtonDown(UINT nFlags, CPoint point)
{
CClientDC aDC(this);//создать контекст устройства
OnPrepareDC(&aDC);//получить уточненную начальную точку
aDC.DPtoLP(&point);//преобразовать точку в логические координаты
 
if(m_MoveMode)
{
//В режиме перемещения, поэтому сбросить элемент
m_MoveMode=false;//Отменить режим перемещения
m_pSelected=nullptr;//снять выделение с элемента
GetDocument()->UpdateAllViews(0);//переместить все представления
return;
}
 
SetCapture();//перехватыватьв се последующие сообщения
m_StartPoint=point;//сохраняем первую точку.
}

Добавьте в класс вида обработчик сообщения WM_RBUTTONDOWN вот его код

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void CSketcher13View::OnRButtonDown(UINT nFlags, CPoint point)
{
if(m_MoveMode)
{
//в режиме перемещения, поэтому отбросить элемент в
//исходную позицию
CClientDC aDC(this);
OnPrepareDC(&aDC);//исправить начальную точку
//переместить элемент в исходную позицию
MoveElement(aDC,m_FirstPos);
m_pSelected=nullptr;//снять выделение с элемента
GetDocument()->UpdateAllViews(nullptr);//перерисовать все представления
}
}

Последнее что осталось сделать, — отключить режим перемещения в обработчике отпускания кнопки.

1
2
3
4
5
6
7
8
9
10
void CSketcher13View::OnRButtonUp(UINT /* nFlags */, CPoint point)
{
if(m_MoveMode)
{
m_MoveMode=false;
return;
}
ClientToScreen(&point);
OnContextMenu(this, point);
}

Все, если вы откомпилируете программу и запустите, то вы уже сможете перемещать элементы. По нажатию левой клавиши мышки будет элемент перемещен, а по нажатию правой вернется назад.

Нам теперь осталось добавить последний обработчик контекстного меню пункта Send To Back вот его код

1
2
3
4
5
void CSketcher13View::OnElementSendToBack()
{
        //переместить элемент в списке
GetDocument()->SendToBack(m_pSelected);
}

и добавим в класс документа функцию SendToBack

1
2
3
4
5
6
7
8
void CSketcher13Doc::SendToBack(CElement* pElement)
{
if(pElement)
{
m_ElementList.remove(pElement);//удалить из списка
m_ElementList.push_front(pElement);//поеместить его обратно в конец списка
}
}

И все на этом наше приложение готово, откомпилируйте и запустите и у вас уже будут работать все пункты, можно перемещать и удалять элементы

MFC end

[youtube]https://www.youtube.com/watch?v=SXRzjuFNaOQ[/youtube]

 

Добавить комментарий Отменить ответ

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Рубрики

  • C++ (293)
  • JavaScript (1)
  • linux (1)
  • MFC (39)
  • node.js (2)
  • React (3)
  • vBulletin (5)
  • Visual Studio (9)
  • wordpress (18)
  • Разное (29)

Метки

Ajax bootstrap CentOS CLI expressjs FormData GDlib google Invisible reCAPTCHA JWT media MFC php react-router-dom redux repository wordpress RTTI STL vBulletin vector Visual Studio WINAPI wordpress wp-plugins XMLHttpRequest Двоичное дерево Задачи С++ Игры С++ Исключения С++ О-большое Операторы_С++ Перегрузка операторов С++ Поиск С++ Потоки Проектирование_С++ С++ Типы_С++ Типы С++ Шаблоны С++ библиотеки локализация макросы С++ сортировка С++

Свежие комментарии

  • ExchiNuGs к записи Программка для заполнения форума на vBulletin 3.8.7
  • RA3PKJ к записи visual C++, создание диалоговых окон.
  • admin к записи Как удалить изображение из google
  • Shakanris к записи Программка для заполнения форума на vBulletin 3.8.7
  • костя к записи visual C++, создание диалоговых окон.
©2021 Kselax.ru Theme by ThemeGiant