Здорова ребятки!
Сегодня мы будем улучшать нашу программу разработанную в посте MFC — заставляем редактор рисовать фигуры и улучшенную в посте Книга «visual C++ полный курс» решение задач глава 16 мы там в задачах улучшали наше приложение и оно имеет вид
и если мы например свернем или развернем приложение или я сейчас его спрячу часть и выведу на экран, то половина рисунка сотрется смотрите скрин ниже
Что бы такого не происходило нужно сохранять наши рисуемые элементы в классе документа и затем при недействительности области перерисовывать их. В общем приступим к реализации.
Создание документа приложения 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()->AddElement(m_pTempElement); //Сообщить всем представлениям GetDocument()->UpdateAllViews(0,0,m_pTempElement); m_pTempElement=nullptr;//обнуляем указатель } } |
Теперь нам следует добавить для класса вида переопределение события OnUpdate для этого заходим в свойства выбираем переопределение и выбираем событие 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 для класса вида
и от код созданного обработчика 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 — прокрутка представлений класс 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);//делаем недействительной всю областьы } |
Теперь откомпилируем и запустим программу, прокрутим колесика ниже и попытаемся нарисовать какую нить фигуру, я нарисовал квадрат и она у меня никуда не исчезла
ну и добавим режим 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); } |
и от компилируем запустим все у нас работает, изменилось только то что нижняя полоса стал ползунок побольше
Удаление и перемещение фигур
И так нам нужно реализовать удаление и перемещение фигур. Нам нужно создать 2 контекстных меню. Заходим в Окно ресурсов, выбираем меню и добавляем новое меню.
Переименуйте его в IDR_ELEMENT_MENU и добавьте 3 пункта к нему Move, Delete, Send To Back
И добавьте еще одно меню и назовите его IDR_NOELEMENT_MENU и добавьте туда уже существующие пункты меню Element и Color просто перейдите в меню где созданы эти пункты, выделите их и скопируйте их в ваше новое меню IDR_NOELEMENT_MENU
Все меню мы создали, теперь нам нужно связать наши меню с классом, для этого зайдите в класс самого приложения 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
Вот код этого обработчика
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; } |
Компилируем и пытаемся вызвать наши меню, раз вызываем на пустом месте и другой раз вызываем над фигурой и у нас получаются каждый раз разные меню
над элементом
как видите менюшки у нас работают все отлично. Пункты меню для перемещения удаления элементов не подсвечиваются потому, что для них мы еще не создали обработчики и поэтому они не активны, как только мы создадим обработчики они станут активными.
Подсветка элемента
Дальше реализуем подсветку элемента, для этого нам необходимо изменить виртуальную функцию 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);//видим нарисовать его } } |
Дальше компилируем программу и запускаем на выполнение и ура у нас фигуры уже выделяюстя
Теперь нам нужно реализовать удаление элемента для этого создадим обработчик события для пункта контекстного меню 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);//поеместить его обратно в конец списка } } |
И все на этом наше приложение готово, откомпилируйте и запустите и у вас уже будут работать все пункты, можно перемещать и удалять элементы
[youtube]https://www.youtube.com/watch?v=SXRzjuFNaOQ[/youtube]