Click here to Skip to main content
15,889,842 members
Articles / Desktop Programming / MFC

Drag and Drop Listbox Items using OLE

Rate me:
Please Sign up or sign in to vote.
4.58/5 (10 votes)
9 Dec 2005CPOL5 min read 72.5K   2.8K   46  
Rearrange listbox items using drag and drop using OLE.
// OLEDragAndDropListBox.cpp : implementation file
//

#include "stdafx.h"
#include "OLEDragAndDropListBox.h"
#include ".\oledraganddroplistbox.h"

#define TID_SCROLLDOWN  100
#define TID_SCROLLUP    101

// COLEDragAndDropListBox

IMPLEMENT_DYNAMIC(COLEDragAndDropListBox, CListBox)
COLEDragAndDropListBox::COLEDragAndDropListBox()
: m_DraggedIndex(-1)
, m_DropIndex(-1)
, m_Interval(0)
, m_CanIntenalDrop(TRUE)
{
}

COLEDragAndDropListBox::~COLEDragAndDropListBox()
{
}


BEGIN_MESSAGE_MAP(COLEDragAndDropListBox, CListBox)
   ON_WM_LBUTTONDOWN()
   ON_WM_MOUSEMOVE()
   ON_WM_LBUTTONUP()
   ON_WM_TIMER()
END_MESSAGE_MAP()

// COLEDragAndDropListBox message handlers

void COLEDragAndDropListBox::PreSubclassWindow()
{
   __super::PreSubclassWindow();
   VERIFY(Register(this) == TRUE);
   //register our own Clipboard format
   m_cfFormat = RegisterClipboardFormat("{2FCA1C31-D8F1-4f20-8051-B0CCF7B6FD0D}");
}

void COLEDragAndDropListBox::OnLButtonDown(UINT nFlags, CPoint point)
{
   __super::OnLButtonDown(nFlags, point);

   //keep track of the item that was clicked on
   //WM_MOUSEMOVE message is going to use that to 
   //create the COleDataSource
   m_Interval = 0;
   m_DropIndex = LB_ERR;
   m_DraggedIndex = LB_ERR;
   BOOL Outside;
   int Index = ItemFromPoint(point,Outside);
   if (Index != LB_ERR && !Outside)
   {
      m_DraggedIndex = Index;
      SetCurSel(Index);
   }
}

void COLEDragAndDropListBox::OnMouseMove(UINT nFlags, CPoint point)
{
   if (m_DraggedIndex != LB_ERR && (nFlags & MK_LBUTTON))
   {
      //create the COleDataSource, and attach the data to it
      COleDataSource DataSource;
      HGLOBAL hData = GetData(m_DraggedIndex);
      if (hData)
      {
         DataSource.CacheGlobalData(m_cfFormat,hData);

         //allow the user to drag it.
         DROPEFFECT DropEffect = DataSource.DoDragDrop(GetDragItemEffects(m_DraggedIndex));
         //if the user wanted to move the item then delete it
         //Only do this if it was dragged to another window
         //OnDrop handles deleting a moved item within the same window
         if (DropEffect & DROPEFFECT_MOVE && m_DraggedIndex != LB_ERR)
         {
            RemoveItem(m_DraggedIndex);
         }
         m_DraggedIndex = LB_ERR;
         GetParent()->SendMessage(WM_COMMAND,MAKEWPARAM(GetDlgCtrlID(),LBN_SELCHANGE),(LPARAM)CListBox::m_hWnd);
      }
   }

   __super::OnMouseMove(nFlags, point);
}

void COLEDragAndDropListBox::OnLButtonUp(UINT nFlags, CPoint point)
{
   //cancel everything
   KillTimer(TID_SCROLLDOWN);
   KillTimer(TID_SCROLLUP);
   m_Interval = 0;
   m_DropIndex = LB_ERR;
   Invalidate();

   __super::OnLButtonUp(nFlags, point);
}

DROPEFFECT COLEDragAndDropListBox::OnDragEnter(CWnd* pWnd,COleDataObject* pDataObject,DWORD dwKeyState,CPoint point)
{
   if (DragOriginateInSameWindow() && !GetCanInternalDrop())
   {
      return DROPEFFECT_NONE;
   }
   //if the data is the kind we want
   if (pDataObject->IsDataAvailable(m_cfFormat))
   {
      //bring window to top
      ActivateWindow();
      //draw the line where it would be inserted
      DrawTheLines(GetItemAt(point));
      //scroll if needed
      DoTheScrolling(point);
      //return how the user can drop the item
      return GetDropItemEffects(pDataObject,dwKeyState);
   }
   return DROPEFFECT_NONE;
}

DROPEFFECT COLEDragAndDropListBox::OnDragOver(CWnd* pWnd,COleDataObject* pDataObject,DWORD dwKeyState,CPoint point)
{
   if (DragOriginateInSameWindow() && !GetCanInternalDrop())
   {
      return DROPEFFECT_NONE;
   }
   //if the data is the kind we want
   if (pDataObject->IsDataAvailable(m_cfFormat))
   {
      //draw the line where it would be inserted
      DrawTheLines(GetItemAt(point));
      //scroll if needed
      DoTheScrolling(point);
      //return how the user can drop the item
      return GetDropItemEffects(pDataObject,dwKeyState);
   }
   return DROPEFFECT_NONE;
}



void COLEDragAndDropListBox::OnDragLeave(CWnd* pWnd)
{
   //cancel everything
   KillTimer(TID_SCROLLDOWN);
   KillTimer(TID_SCROLLUP);
   //Clear The line that was drawn
   CDC *pDC = GetDC(); 
   ClearOldLine(pDC,m_DropIndex);
   ReleaseDC(pDC);
   m_Interval = 0;
   m_DropIndex = LB_ERR;
}

BOOL COLEDragAndDropListBox::OnDrop(CWnd* pWnd,COleDataObject* pDataObject,DROPEFFECT dropEffect,CPoint point)
{
   BOOL Ret = FALSE;
   if (DragOriginateInSameWindow() && !GetCanInternalDrop())
   {
      return FALSE;
   }
   //if it is a format that we handle
   if (pDataObject->IsDataAvailable(m_cfFormat))
   {
      //get the index of where the user want's to insert item
      int Index = GetItemAt(point);
      HGLOBAL hGlobal = pDataObject->GetGlobalData(m_cfFormat);

      //if the Drag was initiated in the same window as the drop and
      //user wants to move the item then we have to handle the delete here
      //because if the original item was at a higher index than the dropped index
      //we would have to delete the dragged item first before we
      //insert the new item
      if (DragOriginateInSameWindow() && dropEffect == DROPEFFECT_MOVE)
      {
         if (m_DraggedIndex < Index)
         {
            Ret = DroppedAt(Index,hGlobal);
            RemoveItem(m_DraggedIndex);
            m_DraggedIndex = LB_ERR;
            SetCurSel(Index-1);
         }
         else if (m_DraggedIndex > Index)
         {
            RemoveItem(m_DraggedIndex);
            m_DraggedIndex = LB_ERR;
            Ret = DroppedAt(Index,hGlobal);
         }
      }
      else //simply drop the item in the desired index
      {
         Ret = DroppedAt(Index,hGlobal);
         GetParent()->SendMessage(WM_COMMAND,MAKEWPARAM(GetDlgCtrlID(),LBN_SELCHANGE),(LPARAM)CListBox::m_hWnd);
      }
   }
   m_DropIndex = LB_ERR;
   return Ret;
}


BOOL COLEDragAndDropListBox::DragOriginateInSameWindow()
{
   //if m_DraggedIndex is equal to LB_ERR then the dragging
   //did not originate in this window
   return m_DraggedIndex != LB_ERR;
}

//find the item under the point, 
//returns -1 if it is passed the last item in the list
int COLEDragAndDropListBox::GetItemAt(CPoint Point)
{
   BOOL Outside;
   int Index = ItemFromPoint(Point,Outside);
   if (Outside)
   {
      Index = -1;
   }
   return Index;
}

void COLEDragAndDropListBox::DrawTheLines(int Index)
{
   CRect ClientRect;
   GetClientRect(&ClientRect);

   CDC *pDC = GetDC();

   ClearOldLine(pDC,m_DropIndex);

   m_DropIndex = Index;

   DrawNewLine(pDC,m_DropIndex);

   ReleaseDC(pDC);
}

void COLEDragAndDropListBox::ClearOldLine(CDC *pDC,int Index)
{
   CRect ClientRect;
   GetClientRect(&ClientRect);

   CRect Rect;
   int Width = 2;
   CPen Pen(PS_SOLID,Width,RGB(255,255,255));
   CPen *pOldPen = pDC->SelectObject(&Pen);
   if (Index != LB_ERR)
   {
      GetItemRect(m_DropIndex,&Rect);
      if (ClientRect.PtInRect(Rect.TopLeft()))
      {
         pDC->MoveTo(Rect.left,Rect.top+1);
         pDC->LineTo(Rect.right-(Width/2),Rect.top+1);
      }
   }
   else
   {
      GetItemRect(GetCount()-1,&Rect);
      if (ClientRect.PtInRect(CPoint(0,Rect.bottom)))
      {
         pDC->MoveTo(Rect.left,Rect.bottom);
         pDC->LineTo(Rect.right-(Width/2),Rect.bottom);
      }
   }
   pDC->SelectObject(pOldPen);
}

void COLEDragAndDropListBox::DrawNewLine(CDC *pDC,int Index)
{
   CRect ClientRect;
   GetClientRect(&ClientRect);

   int Width = 2;

   CRect Rect;
   CPen Pen(PS_SOLID,Width,RGB(0,0,0));
   CPen *pOldPen = pDC->SelectObject(&Pen);
   if (Index != LB_ERR)
   {
      GetItemRect(Index,&Rect);
      if (ClientRect.PtInRect(Rect.TopLeft()))
      {
         pDC->MoveTo(Rect.left,Rect.top+1);
         pDC->LineTo(Rect.right-(Width/2),Rect.top+1);
      }
   }
   else
   {
      GetItemRect(GetCount()-1,&Rect);
      if (ClientRect.PtInRect(CPoint(0,Rect.bottom)))
      {
         pDC->MoveTo(Rect.left,Rect.bottom);
         pDC->LineTo(Rect.right-(Width/2),Rect.bottom);
      }
   }
   pDC->SelectObject(pOldPen);
}

void COLEDragAndDropListBox::DoTheScrolling(CPoint Point)
{
   CRect ClientRect;
   GetClientRect(&ClientRect);
   if (Point.y > ClientRect.Height()-10)
   {
      DWORD Interval = 250 / (1+ ((Point.y-ClientRect.Height())/GetItemHeight(0)));
      if (Interval != m_Interval)
      {
         m_Interval = Interval;
         SetTimer(TID_SCROLLDOWN,Interval,NULL);
         OnTimer(TID_SCROLLDOWN);
      }
   }
   else if (Point.y < 10)
   {
      DWORD Interval = 250 / (1+(abs(Point.y)/GetItemHeight(1)));
      if (Interval != m_Interval)
      {
         m_Interval = Interval;
         SetTimer(TID_SCROLLUP,Interval,NULL);
         OnTimer(TID_SCROLLUP);
      }
   }
   else
   {
      KillTimer(TID_SCROLLDOWN);
      KillTimer(TID_SCROLLUP);
      m_Interval = 0;
   }
}

void COLEDragAndDropListBox::OnTimer(UINT nIDEvent)
{
   if (nIDEvent == TID_SCROLLDOWN)
   {
      if (m_DropIndex != -1)
      {
         DrawTheLines(m_DropIndex+1);
         SetTopIndex(GetTopIndex()+1);
      }
      else
      {
         KillTimer(nIDEvent);
         m_Interval = 0;
      }
   }
   else if (nIDEvent == TID_SCROLLUP)
   {
      if (GetTopIndex() != 0)
      {
         DrawTheLines(m_DropIndex-1);
         SetTopIndex(GetTopIndex()-1);
      }
      else
      {
         KillTimer(nIDEvent);
         m_Interval = 0;
      }
   }

   __super::OnTimer(nIDEvent);
}


HGLOBAL COLEDragAndDropListBox::GetData(int ForIndex)
{
   CString Text;
   GetText(ForIndex,Text);
   HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE,Text.GetLength()+1);
   char *pChar = (char *)GlobalLock(hGlobal);
   strcpy(pChar,Text);
   GlobalUnlock(hGlobal);
   return hGlobal;
}

BOOL COLEDragAndDropListBox::DroppedAt(int InsertBefore,HGLOBAL hGlobal)
{
   char * pChar = (char *)GlobalLock(hGlobal);
   int Index = InsertString(InsertBefore,pChar);
   SetCurSel(Index);
   GlobalUnlock(hGlobal);
   
   return TRUE;
}

DROPEFFECT COLEDragAndDropListBox::GetDropItemEffects(COleDataObject* /*pDataObject*/,DWORD dwKeyState)
{
   if (DragOriginateInSameWindow())
   {
      if (dwKeyState & MK_CONTROL)
      {
         return DROPEFFECT_COPY;
      }
      return DROPEFFECT_MOVE;
   }

   if (dwKeyState & MK_SHIFT)
   {
      return DROPEFFECT_MOVE;
   }
   return DROPEFFECT_COPY;
}

DROPEFFECT COLEDragAndDropListBox::GetDragItemEffects(int Index)
{
   return DROPEFFECT_COPY|DROPEFFECT_MOVE;
}

void COLEDragAndDropListBox::RemoveItem(int Index)
{
   DeleteString(Index);
}

void COLEDragAndDropListBox::ActivateWindow()
{
   CWnd *pWnd = GetParent();
   if (pWnd)
   {
      pWnd->SetForegroundWindow();
      pWnd->BringWindowToTop();
      pWnd->SetActiveWindow();
   }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
United States United States
Ali Rafiee has been developing windows applications using C++ since 1991, and he hasn't looked back since. Ali has been a software development consultant for must of his career, but he has finally settled down and has been working for an educational software company since 2000. While he is not working, he is either learning C#, flying airplanes, playing with his daughter, or answering peoples question on newsgroups, he finds that to be a great learning tool for himself.

Ali is also a Microsoft Visual C++ MVP.

Comments and Discussions