Animating a rectangle

Add member variables to the view class. These are only used to remember the state of the view, so they do not need to be stored in the document. It would not be wrong to add them to the document, but they are not needed there. If you were to retrieve data from a file, it would not make sense to set the state of these variables with values from the file. These variables only deal with the current actions of the user. 

bool m_bCapture; //Initialize it as false
CPoint m_pntStart; //Initialize it as (0,0)

Add the size of the rectangle as a variable to the document.
Note: By making this a variable, it is possible to make the size of the rectangle change in response to some type of user input.
Challenge: When the control key is down while pressing the right-mouse button, resize the rectangle according to the distance that the mouse moves.

CSize m_szRect; //Initialize it as (20,20)

When the right mouse button is pressed, test if the current point is in first rectangle. If so, then capture mouse and set the state variable, m_bCapture, to true. Save the current point in the variable m_pntStart.

void CRecsView::OnRButtonDown(UINT nFlags, CPoint point)
{
  if (GetDocument()->m_pointIndex == 0)
    return;

  CRect rect(GetDocument()->m_points[0], GetDocument()->m_szRect);

  if (rect.PtInRect(point)) {
    m_bCaptured = true;
    m_pntStart = point;
    SetCapture();
  }
}

Add a helper method that invalidates the old rectangle, calculates the new point, invalidates the new rectangle, and sets the starting point to the current point. This will have the effect of erasing the old rectangle and drawing the new rectangle.
Experiment: Try commenting out one or the other of the InvalidateRect calls. You will see that the old rectangle is not erased, or that part of the new rectangle is not drawn. Also, try commenting out the last statement. You will notice that the reactangle drags at a faster and faster rate. Can you explain why these things happen?

void CRecsView::InvalidateOldAndNew(CPoint point)
{
  CRect rect(GetDocument()->m_points[0], GetDocument()->m_szRect);
  InvalidateRect(&rect);

  GetDocument()->m_points[0] += point - m_pntStart;

  rect = CRect(GetDocument()->m_points[0], GetDocument()->m_szRect);
  InvalidateRect(&rect);

  m_pntStart = point;
}

If the mouse is captured as it moves, call the helper method to invalidate the old rectangle and the new rectangle.

void CRecsView::OnMouseMove(UINT nFlags, CPoint point)
{
  if (m_bCapture) {
    InvalidateOldAndNew(point);
  }
}

On mouse up, call the helper method, release mouse, and clear m_bCapture

void CRecsView::OnRButtonUp(UINT nFlags, CPoint point)
{
  if (m_bCaptured) {	
    InvalidateOldAndNew(point);
    m_bCaptured = false;
    ::ReleaseCapture();
  }
}

Example: Recs

Animating a rectangle in a new mapping mode

If a mapping mode other than MM_TEXT is being used, then care must be taken to be sure that comparisons are made using coordinates in the correct mode. The only methods that use logical coordinates are the methods that are part of the device context. This means that the CPoint parameter of the OnRButtonDown method is in device coordinates, not logical coordinates. It also means that InvalidateRect expects device coordinates, not logical coordinates. On the other hand, the Rectangle method of the device context expects logical coordinates.
Note: It is also preferable to store logical coordinates in the document, especially if a scroll view is being used. Can you explain why?

For these reasons, the following changes must be made to the application if MM_HIENGLISH is used instead of MM_TEXT.

Initialize the CSize in the document as (200, -200).
Note: The x- and y-coordinates in MM_HIENGLISH agree with the traditional Cartesean coordinate system.

Override OnPrepareDC and set the maping mode in it

void CRecsView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo)
{
  // TODO: Add your specialized code here and/or call the base class
  pDC->SetMapMode(MM_HIENGLISH);
}

Modify OnLButtonDown so that the point is saved in the document in logical coordinates. It is necessary to create a client DC and prepare it so that it is similar to the DC that is used when drawing.

void CRecsView::OnLButtonDown(UINT nFlags, CPoint point)
{
    CRecsDoc *pDoc = GetDocument();

    // don't go past the end of the 100 points allocated
    if (pDoc->m_pointIndex == 100)
       return;

    CClientDC dc(this);
    OnPrepareDC(&dc);

    CPoint pointLP(point);
    dc.DPtoLP(&pointLP);

    //store the click location
    pDoc->m_points[pDoc->m_pointIndex] = pointLP;
    pDoc->m_pointIndex++;

    Invalidate();
}

Modify OnRButtonDown so that the starting point is saved in logical coordinates and the rectangle is in device coordinates. If arithmetic is being done between two points or between a point and a rectangle, then it is important that they are all in the same coordinate system. The starting point will be added to the top-left point of the rectangle when the rectangle when it is dragged. Since the top left point is stored in the document, it should be stored in logical coordinates. The rectangle should be translated to device, since it is being tested against a device point.
Note: It would be possible to do the arithmetic for the point in device coordinates, but then the final result would have to be translated back to logical coordinates to be stored in the document. By changing to logical in the first place, an additional translation is avoided. It would also be possible to leave the rectangle in logical and use the logical point to do the hit testing. See the Experiment below for a discussion of this technique.

void CRecsView::OnRButtonDown(UINT nFlags, CPoint point)

{
	// TODO: Add your message handler code here and/or call default

    	if (GetDocument()->m_pointIndex == 0)
       		return;

	CRect rect(GetDocument()->m_points[0], GetDocument()->m_szRect);
	
	CClientDC dc(this);
	OnPrepareDC(&dc);

	dc.LPtoDP(&rect);
	CPoint pointLP(point);
	dc.DPtoLP(&pointLP);

	if (rect.PtInRect(point)) {
		m_bCaptured = true;
		m_pntStart = pointLP;
		this->SetCapture();
	}
}

Experiment: It would seem that extra work is being done here. It is necessary to change the point into logical, but why is it necessary to change the rectangle to device? Why not test if the logical point is contained in the logical rectangle, instead of testing if the device point is in the device rectangle? This could be done be commenting out
dc.LPtoDP(&rect);
and changing the PtInRect test to
rect.PtInRect(pointLP)
Try it! You will see that the code doesn't work: the rectangle cannot be dragged. The problem is with the logical coordinate system. Since the PtInRect method is not part of a DC, it expects a coordinate system that is organized like device coordinates: positive y moves down the screen. Because of this, it expects a rectangle defined with the top-left x-coordinate less than the bottom-right x-coordinate, and the top-left y-coordinate less than the bottom-right y-coordinate. That is not the case if the logical rectangle is used. The solution is to replace
dc.LPtoDP(&rect);
with a call to
rect.NormalizeRect();
which will define the same rectangle, but will adust the top-left and bottom-right points to agree with the expected coordinate system.

Modify InvalidateOldAndNew so that device coordinate rectangles are sent to InvalidateRect. It is necessary to create a client DC and prepare it so that it is similar to the DC that is used when drawing. The code is almost identical to the original method, except for the calls to LPtoDP and the call to DPtoLP. The decision about which to use is based upon how the reactangle or point will be used. If it is being saved in the document, then be sure to translate to logical, if it is being used in a method that is not part of a DC, then translate to device.

void CRecsView::InvalidateOldAndNew(CPoint point)
{
  	CClientDC dc(this);
	OnPrepareDC(&dc);
	
	CRect rect(GetDocument()->m_points[0], GetDocument()->m_szRect);
	dc.LPtoDP(&rect);
	InvalidateRect(&rect);

	CPoint pointLP(point);
	dc.DPtoLP(&pointLP);
	GetDocument()->m_points[0] += pointLP - m_pntStart;

	rect = CRect(GetDocument()->m_points[0], GetDocument()->m_szRect);
	dc.LPtoDP(&rect);
	InvalidateRect(&rect);

	m_pntStart = pointLP;
}

Example: RecsLP

Challenge: Can you modify the code so that any one of the rectangles can be dragged?