The EX26B Example—OLE Drag and Drop

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);
}