Handling a Drag-and-Drop Operation

HomeOverviewHow Do I

The actual work of handling a drag-and-drop operation is done by overriding four member functions defined by CView

virtual BOOL OnDrop(COleDataObject* pDataObject,
         DROPEFFECT dropEffect, CPoint point);
virtual DROPEFFECT OnDragEnter(COleDataObject* pDataObject,
            DWORD grfKeyState, CPoint point);
virtual DROPEFFECT OnDragOver(COleDataObject* pDataObject,
            DWORD grfKeyState, CPoint point);
virtual void OnDragLeave();

These functions are called by the OLE system DLL (via the MFC framework) during a drag-and-drop operation.

The first function called during an actual drag-and-drop operation is OnDragEnter, which is called when the mouse first enters the window: 

DROPEFFECT CDrawView::OnDragEnter(COleDataObject* pDataObject,
   DWORD grfKeyState, CPoint point)
{
   ASSERT(m_prevDropEffect == DROPEFFECT_NONE);
   m_bDragDataAcceptable = FALSE;
   if (!COleClientItem::CanCreateFromData( pDataObject ))
      return DROPEFFECT_NONE;

   GetObjectInfo(pDataObject, &m_dragSize, &m_dragOffset);
   CClientDC dc(NULL);
   dc.HIMETRICtoDP(&m_dragSize);
   dc.HIMETRICtoDP(&m_dragOffset);

   return OnDragOver(pDataObject, grfKeyState, point);
}

This function first checks whether the COleDataObject provided by the drop source contains data that DRAWCLI can use. If not, the function sets a flag indicating that the data is unacceptable, and returns DROPEFFECT_NONE, indicating that a drop operation would have no effect. On the other hand, if DRAWCLI can use the data, the function computes the size and position of the focus rectangle. It does this by using the GetObjectInfo helper function to get the size of the object in the COleDataObject object.

The next function called during a drag-and-drop operation is OnDragOver, which is called whenever the mouse moves within the window. This function is responsible for determining whether the window can accept the drop operation, and if so, for providing target feedback:

DROPEFFECT CDrawView::OnDragOver(COleDataObject*,
   DWORD grfKeyState, CPoint point)
{
   if (m_bDragDataAcceptable == FALSE)
      return DROPEFFECT_NONE;

   point -= m_dragOffset;  // adjust target rect by cursor offset

   // check for point outside logical area 
   // GetTotalSize() returns the size passed to SetScrollSizes()
   CRect rectScroll(CPoint(0, 0), GetTotalSize());

   CRect rectItem(point,m_dragSize);
   rectItem.OffsetRect(GetDeviceScrollPosition());

   DROPEFFECT de = DROPEFFECT_NONE;
   CRect rectTemp;
   if (rectTemp.IntersectRect(rectScroll, rectItem))
   {
      // check for force link
      if ((grfKeyState & (MK_CONTROL|MK_SHIFT)) == 
         (MK_CONTROL|MK_SHIFT))
         de = DROPEFFECT_NONE; // we don’t support linking
      // check for force copy
      else if ((grfKeyState & MK_CONTROL) == MK_CONTROL)
         de = DROPEFFECT_COPY;
      // check for force move
      else if ((grfKeyState & MK_ALT) == MK_ALT)
         de = DROPEFFECT_MOVE;
      // default -- recommended action is move
      else
         de = DROPEFFECT_MOVE;
   }

   if (point == m_dragPoint)
      return de;

   // else, cursor has moved -- need to update the drag feedback
   CClientDC dc(this);
   if (m_prevDropEffect != DROPEFFECT_NONE)
   {
      // erase previous focus rect
      dc.DrawFocusRect(CRect(m_dragPoint, m_dragSize));
   }
   m_prevDropEffect = de;
   if (m_prevDropEffect != DROPEFFECT_NONE)
   {
      m_dragPoint = point;
      dc.DrawFocusRect(CRect(point, m_dragSize));
   }
   return de;
}

This function first checks the flag m_bDragDataAcceptable to see if it’s necessary to do any more processing. If so, the function then checks which, if any, keys are being depressed, determining whether the user wants a link operation, a move, or a copy. Since DRAWCLI isn’t a linking container, this function returns DROPEFFECT_NONE when a link operation is specified, meaning that the view won’t accept the dragged object. DRAWCLI does accept copy or move operations, so the function returns DROPEFFECT_COPY and DROPEFFECT_MOVE in those instances. (The drop source receives these DROPEFFECT values and modifies the mouse cursor appropriately.) Finally, if the operation is a copy or a move, the function draws the focus rectangle to indicate where the object would land if it were dropped.

If the mouse leaves the window without having dropped the object, the function that gets called is OnDragLeave. This function simply performs a little clean-up:

void CDrawView::OnDragLeave()
{
   CClientDC dc(this);
   if (m_prevDropEffect != DROPEFFECT_NONE)
   {
      // erase previous focus rect
      dc.DrawFocusRect(CRect(m_dragPoint,m_dragSize)); 
      m_prevDropEffect = DROPEFFECT_NONE;
   }
}

If the drag-and-drop operation was one that DRAWCLI was willing to accept, the function removes the target feedback by erasing the last focus rectangle drawn.

Finally, the function that gets called if the user actually performs the drop is OnDrop:

BOOL CDrawView::OnDrop(COleDataObject* pDataObject,
   DROPEFFECT dropEffect, CPoint point)
{
   ASSERT_VALID(this);

   // clean up focus rect
   OnDragLeave();

   // offset point as appropriate for dragging
   GetObjectInfo(pDataObject, &m_dragSize, &m_dragOffset);
   CClientDC dc(NULL);
   dc.HIMETRICtoDP(&m_dragSize);
   dc.HIMETRICtoDP(&m_dragOffset);
   point -= m_dragOffset;

   // invalidate current selection since it will be deselected
   OnUpdate(NULL, HINT_UPDATE_SELECTION, NULL);
   m_selection.RemoveAll();
   if (m_bDragDataAcceptable)
      PasteEmbedded( *pDataObject, point );

   // update the document and all views
   GetDocument()->SetModifiedFlag();
   GetDocument()->UpdateAllViews(NULL, 0, NULL); 

   return TRUE;
}

This function determines the point at which the dropped object resides, deselects the currently selected object, and creates an OLE embedded object, using the COleDataObject object provided by the drop source. Note that the CDrawView::PasteEmbedded function now takes an additional parameter compared with the previous version of DRAWCLI; this parameter lets the caller specify the location of a new embedded object, something that is unnecessary for Paste operations but is useful for drag-and-drop operations.

It would also be possible to make DRAWCLI a drop source, allowing the user to drag a selection from one of DRAWCLI’s windows into another application’s. However, DRAWCLI doesn’t offer any common Clipboard formats, nor is DRAWCLI an OLE server, so there are no applications that could accept a dragged object originating from DRAWCLI. Consequently, the current version of DRAWCLI would not be a useful drop source.

For more information on MFC’s drag-and-drop support, see Drag and Drop (OLE). For more information about drag-and-drop in general, see Inside OLE or the OLE documentation on the MSDN Library CD.