380 likes | 388 Views
Learn how to create and improve a Sketcher Document, remember and redraw elements, handle multiple views, and enable scrolling. Includes tips on deleting and moving shapes, highlighting elements, implementing a context menu, and more.
E N D
The Sketcher Document Does Not Remember What You Have Drawn • Whenever you resize the window, the elements disappear! • Inspect class CSketcherDoc, you only have the following data members: protected: // Current element type ElementType m_Element; // Current drawing color COLORREF m_Color;
Store Elements in a List (P.1010) • Data Member: • std::list<CElement*> m_ElementList; • Member Function: • void AddElement(CElement* pEleemnt) { m_ElementList.push_back(pElement); } • To Make CSketcherDoc recognize these data types: • #include <list> • #include "Elements.h"
Destroy the elements in the destructor CSketcherDoc::~CSketcherDoc() { // Delete the element pointed to by each list entry for (auto iter = m_ElementList.begin(); iter != m_ElementList.end(); ++iter) delete *iter; // Finally delete all pointers m_ElementList.clear(); } • The auto keyword specifies the correct data type according to the initial value.
const_iterator • The list is owned by the list, as a protected data member. You can’t access it directly from the view. class CSketcherDoc : public CDocument { // Operations public: // Add an element to the list void AddElement(CElement* pElement) { m_ElementList.push_back(pElement); } // Get list begin iterator std::list<CElement*>::const_iterator begin() const { return m_ElementList.begin(); } // Get list end iterator std::list<CElement*>::const_iterator end() const { return m_ElementList.end(); }
Determine which element needs to be redrawn • RectVisible() determines whether a rectangle area overlaps the area that Windows must redraw. void CSketcherView::OnDraw(CDC* pDC) { CSketcherDoc* 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); // If the element is visible // ... draw it } }
Adding an Element to the Document • OnLButtonUP(), add the temporary element to the document. void CSketcherView::OnLButtonUp(UINT nFlags, CPoint point) { if (this == GetCapture()) ReleaseCapture(); // Stop capturing mouse messages // Make sure there is an element if (m_pTempElement) { // Call a document class function to store the element // pointed to by m_pTempElement in the document object GetDocument()->AddElement(m_pTempElement); InvalidateRect(nullptr); // Redraw the current window m_pTempElement = 0; // Reset the element pointer } } • Note that the statement deleting m_pTempElement has been removed. • The document destructor will handle this.
Exercising the Document • Now the document remembers all the elements you have drawn. • Try to draw “The Happy Programmer”, and then resize the window.
Updating Multiple Views • Right now, if you draw in a view, the other view will not be updated automatically. • Each view is acting independently of the others. • They don’t know what’s happening in other views. void CSketcherView::OnLButtonUp(UINT nFlags, CPoint point) { if (this == GetCapture()) ReleaseCapture(); // Stop capturing mouse messages // Make sure there is an element if (m_pTempElement) { // Call a document class function to store the element // pointed to by m_pTempElement in the document object GetDocument()->AddElement(m_pTempElement); // Tell the views GetDocument()->UpdateAllViews(nullptr, 0, m_pTempElement); InvalidateRect(nullptr); // Redraw the current window m_pTempElement = 0; // Reset the element pointer } }
UpdateAllViews() Useful when the current view is already up to date
Add an override for OnUpdate() • Right-click CSketcherView and choose Override in the Properties Window. • <Add> OnUpdate() void CSketcherView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) { if (pHint) InvalidateRect( static_cast<CElement*>(pHint)->GetBoundRect()); else InvalidateRect(nullptr); } • Now both views are updated.
Scrolling Views • Change the base class for CSketcherView from CView to CScrollView. • In SketcherView.h: • class CSketcherView : public CScrollView • In SketcherView.cpp: • IMPLEMENT_DYNCREATE(CSketcherView, CScrollView) • BEGIN_MESSAGE_MAP(CSketcherView, CScrollView)
Specify the size and scrolling distance override void CSketcherView::OnInitialUpdate() { CScrollView::OnInitialUpdate(); // Define document size CSize DocSize(20000, 20000); // Set mapping mode and document size SetScrollSizes(MM_TEXT, DocSize, CSize(500,500), CSize(50,50)); }
Exercise to Demo • Use a vector instead of a list (as in Chapter 17) to store the elements.
Deleting and Moving Shapes • Highlighting Elements • Implementing a Context Menu • Servicing the Menu Messages • Deleting an Element • Moving an Element
Context Menus • Right-click will select an element and pops up a menu relating to actions that can be performed on that object. • For different elements, different menus will be displayed. That’s why it is call a “context-sensitive menu”.
You Will Need Two Context Menus • When there is an element under the mouse cursor: • Move and Delete • When there isn’t: • Menu items from the Element and Color menus
Implementing a Context Menu • Resource View • Right-click the Menu folder • Insert Menu • The default ID is IDR_MENU1 • Select this new menu, and pressing Alt-Enter to display the Properties window. • Change its resource ID to be IDR_ELEMENT_MENU.
Create items on the menu • Editor pane. • The caption won’t be displayed to the user. • Add the Move and Delete items to the element pop-up. • The default IDs are ID_ELEMENT_MOVE and ID_ELEMENT_DELETE.
IDR_NOELEMENT_MENU • Creating another menu with ID IDR_NOELEMENT_MENU • The caption no element won’t be shown to users. • Copy all items from the Element menu and the Color menu. • Click the first item and then click the last item while holding down the Shift key. • Press Ctrl+C to copy. • Click the no element menu. • Press Ctrl+V to paste. • The copied menu items will have the same IDs as the originals. • You may have a separator between the Element menu items and the Color menu items.
Associating a Menu with a Class • AddMenu() function in an CContextManager object. void CSketcherApp::PreLoadState() { // Delete the remarked portion on P.1023 GetContextMenuManager()->AddMenu( _T("Element menu"), IDR_ELEMENT_MENU); GetContextMenuManager()->AddMenu( _T("No element menu"), IDR_NOELEMENT_MENU); } The _T() macro selects the correct type for the string, depending on whether or not the _UNICODE symbol is defined.
Identifying a Selected Element • An element is a sketch will be selected whenever the mouse cursor is within the bounding rectangle. • Add FindElement() to the document class as a public member: // Finds the element under the point CElement* CSketcherDoc::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; }
Check which element is selected whenever the mouse moves • Use a variable m_pSelected to store the address of the element under the mouse cursor, or nullptr if there isn’t one. void CSketcherView::OnMouseMove(UINT nFlags, CPoint point) { // Define a Device Context object for the view CClientDC aDC(this); // DC is for this view if((nFlags & MK_LBUTTON) && (this == GetCapture()) ) { // Code as before ... } else { // We are not creating an element, so select an element CSketcherDoc* pDoc=GetDocument(); m_pSelected = pDoc->FindElement(point); } }
m_pSelected • Add m_pSelected as a protected member of the CSketcherView class as type CElement*. • Initialize this member to nullptr in the view class constructor.
ShowPopupMenu() • Look at CSketcherView class implementation. In OnRButtonUP() handler, it already calls the OnContextMenu() handler to show a popup menu. • Modify OnContextMenu(): void CSketcherView::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); else theApp.GetContextMenuManager()->ShowPopupMenu( IDR_NOELEMENT_MENU, point.x, point.y, this); #endif }
Try It Out • Build and execute Sketcher. • Right-click the mouse, or press Shift+F10 • A context menu will be displayed. • If there are no elements under the cursor, the second context pop-up appears, enabling you to change the element type and color. • If there is an element under the cursor, the first context menu will appear with Move and Delete on it. • It won’t do anything at this moment.
Highlighting Elements • You should draw the element under the cursor with a different color, so that users can make sure the right-click will be applied on which element. • Change the Draw() member function for an element. • Pass an extra argument • m_pSelected, which is the currently-selected element • Compare it with this pointer, and draw in a special color if this == m_pSelected.
CSketcherView::OnDraw() void CSketcherView::OnDraw(CDC* pDC) { CSketcherDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; CElement* pElement(nullptr); for (auto iter = pDoc->begin(); iter != pDoc->end(); ++iter) { pElement = *iter; // If the element is visible if (pDC->RectVisible(pElement->GetBoundRect())) pElement->Draw(pDC, m_pSelected); // ... draw it } }
Draw() in CElement class • Modify the definition of Draw() in the base class CElement class CElement : public CObject { protected: int m_PenWidth; // Pen width COLORREF m_Color; // Color of an element CElement(); public: virtual ~CElement(); // Virtual draw operation virtual void Draw(CDC* pDC, CElement* pElement=nullptr) {} // Get the bounding rectangle for an element CRect GetBoundRect(void); }; • Remember to remove the virtual CElement::Draw() in Elements.cpp
Change the CLine class definition class CLine : public CElement { public: virtual ~CLine(void); // Function to display a line virtual void Draw(CDC* pDC, CElement* pElement=nullptr); // Constructor for a line object CLine(const CPoint& start, const CPoint& end, COLORREF aColor); protected: CPoint m_StartPoint; // Start point of line CPoint m_EndPoint; // End point of line CLine(void); // Default constructor should not be used };
Implementation of CLine::Draw() void CLine::Draw(CDC* pDC, CElement* pElement) { // Create a pen for this object and // initialize it to the object color and line width of 1 pixel CPen aPen; if(!aPen.CreatePen(PS_SOLID, m_PenWidth, this==pElement ? SELECT_COLOR : m_Color)) { // Pen creation failed. Abort the program AfxMessageBox(_T("Pen creation failed drawing a line"), MB_OK); AfxAbort(); } CPen* pOldPen = pDC->SelectObject(&aPen); // Select the pen // Now draw the line pDC->MoveTo(m_StartPoint); pDC->LineTo(m_EndPoint); pDC->SelectObject(pOldPen); // Restore the old pen } In SketcherConstants.h: const COLORREF SELECT_COLOR = RGB(255,0,180); Apply the same change on CRectangle, CCircle, and CCurve.
Tell MFC which elements to re-draw void CSketcherView::OnMouseMove(UINT nFlags, CPoint point) { // Define a Device Context object for the view CClientDC aDC(this); // DC is for this view if((nFlags & MK_LBUTTON) && (this == GetCapture()) ) { // Code as before } else { // We are not creating an element, so select an element CSketcherDoc* 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); } } } • If both pOldSelected and m_pSelected contain a valid address, the element has been already highlighted. • If they are both zero, nothing is highlighted. • So, you need to do something only when m_pSelected is not equal to pOldSelected. Not to erase the background Now try it out. The highlighted element is drawn in magenta.
Deleting an Element void CSketcherView::OnElementDelete() { if (m_pSelected) { CSketcherDoc* pDoc = GetDocument(); // Get the document ptr pDoc->DeleteElement(m_pSelected); // Delete the element pDoc->UpdateAllViews(nullptr); // Redraw all the views m_pSelected = nullptr; // Reset selected element ptr } }
DeleteElement() • Added as a public member of CSketcherDoc (P.1031) void CSketcherDoc::DeleteElement(CElement* pElement) { if (pElement) { // Remove the pointer from the list m_ElementList.remove(pElement); // Delete the element from the heap delete pElement; } } List Heap CElement