This example picks up where the EX26A example leaves off. It adds drag-and-drop support, using the existing SaveDib and DoPasteDib helper functions. All of the clipboard code is the same. You should be able to adapt EX26B to other applications that require drag and drop for data objects.
To prepare EX26B, open the \vcpp32\ex26b\ex26b.dsw workspace and build the project. Run the application, and test drag and drop between child windows and between instances of the program.
The CEx26bDoc Class
This class is just like the EX26A version except for an added flag data member, m_bDragHere. This flag is TRUE when a drag-and-drop operation is in progress for this document. The flag is in the document and not in the view because it is possible to have multiple views attached to the same document. It doesn't make sense to drag a DIB from one view to another when both views reflect the document's m_dib member.
The CEx26bView Class
To start with, this class has three additional data members and a constructor that initializes all the data members, as shown here:
CRect m_rectTrackerEnter; // original logical coordinates COleDropTarget m_dropTarget; CSize m_dragOffset; // device coordinates CEx26bView::CEx26bView() : m_sizeTotal(800, 1050), // 8-by-10.5 inches // when printed m_rectTracker(50, 50, 250, 250), m_dragOffset(0, 0), m_rectTrackerEnter(50, 50, 250, 250) { }
The OnInitialUpdate function needs one additional line to register the drop target:
m_dropTarget.Register(this);
Following are the drag-and-drop virtual override functions. Note that OnDrop replaces the DIB only if the document's m_bDragHere flag is TRUE, so if the user drops the DIB in the same window or in another window connected to the same document, nothing happens.
DROPEFFECT CEx26bView::OnDragEnter(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point) { TRACE("Entering CEx26bView::OnDragEnter, point = (%d, %d)\n", point.x, point.y); m_rectTrackerEnter = m_rectTracker; // save original coordinates // for cursor leaving // rectangle CClientDC dc(this); OnPrepareDC(&dc); dc.DrawFocusRect(m_rectTracker); // will be erased in OnDragOver return OnDragOver(pDataObject, dwKeyState, point); } DROPEFFECT CEx26bView::OnDragOver(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point) { if (!pDataObject->IsDataAvailable(CF_DIB)) { return DROPEFFECT_NONE; } MoveTrackRect(point); if ((dwKeyState & MK_CONTROL) == MK_CONTROL) { return DROPEFFECT_COPY; } // Check for force move if ((dwKeyState & MK_ALT) == MK_ALT) { return DROPEFFECT_MOVE; } // default -- recommended action is move return DROPEFFECT_MOVE; } void CEx26bView::OnDragLeave() { TRACE("Entering CEx26bView::OnDragLeave\n"); CClientDC dc(this); OnPrepareDC(&dc); dc.DrawFocusRect(m_rectTracker); m_rectTracker = m_rectTrackerEnter; // Forget it ever happened } BOOL CEx26bView::OnDrop(COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point) { TRACE("Entering CEx26bView::OnDrop -- dropEffect = %d\n", dropEffect); BOOL bRet; CEx26bDoc* pDoc = GetDocument(); MoveTrackRect(point); if (pDoc->m_bDragHere) { pDoc->m_bDragHere = FALSE; bRet = TRUE; } else { bRet = DoPasteDib(pDataObject); } return bRet; }
The handler for the WM_LBUTTONDOWN message needs substantial overhaul. It must call DoDragDrop if the cursor is inside the rectangle and Track if it is on the rectangle border. The revised code is shown here:
void CEx26bView::OnLButtonDown(UINT nFlags, CPoint point) { CEx26bDoc* pDoc = GetDocument(); if (m_tracker.HitTest(point) == CRectTracker::hitMiddle) { COleDataSource* pSource = SaveDib(); if (pSource) { // DoDragDrop returns only after drop is complete CClientDC dc(this); OnPrepareDC(&dc); CPoint topleft = m_rectTracker.TopLeft(); dc.LPtoDP(&topleft); // `point' here is not the same as the point parameter in // OnDragEnter, so we use this one to compute the offset m_dragOffset = point - topleft; // device coordinates pDoc->m_bDragHere = TRUE; DROPEFFECT dropEffect = pSource->DoDragDrop( DROPEFFECT_MOVE | DROPEFFECT_COPY, CRect(0, 0, 0, 0)); TRACE("after DoDragDrop -- dropEffect = %ld\n", dropEffect); if (dropEffect == DROPEFFECT_MOVE && pDoc->m_bDragHere) { pDoc>OnEditClearAll(); } pDoc->m_bDragHere = FALSE; delete pSource; } } else { if (m_tracker.Track(this, point, FALSE, NULL)) { CClientDC dc(this); OnPrepareDC(&dc); // should have some way to prevent it going out of bounds m_rectTracker = m_tracker.m_rect; dc.DPtoLP(m_rectTracker); // update logical coordinates } } Invalidate(); }
Finally, the new MoveTrackRect helper function, shown here, moves the tracker's focus rectangle each time the OnDragOver function is called. This job was done by CRectTracker::Track in the EX26A example.
void CEx26bView::MoveTrackRect(CPoint point) { CClientDC dc(this); OnPrepareDC(&dc); dc.DrawFocusRect(m_rectTracker); dc.LPtoDP(m_rectTracker); CSize sizeTrack = m_rectTracker.Size(); CPoint newTopleft = point - m_dragOffset; // still device m_rectTracker = CRect(newTopleft, sizeTrack); m_tracker.m_rect = m_rectTracker; dc.DPtoLP(m_rectTracker); dc.DrawFocusRect(m_rectTracker); }