This listbox class demonstrates how to rearrange listbox items using Drag and Drop without the overhead of OLE. (If you are trying to drag items from one ListBox to another, refer to the OLE verion of this article.)
MFC has a CDragListBox that virtually does the same thing. The advantage of this class over MFC’s CDragListBox is that it allows the user to insert the item at the end of the list, whereas CDragListBox does not. The concept is very simple: Keep track of the item that is being dragged, indicate where the drop will be when the user is dragging the item around, and finally, insert the item in its new location once the user releases the mouse button.
To accomplish this task, you will need to catch three messages for your listbox window: WM_LBUTTONDOWN, WM_MOUSEMOVE, and WM_LBUTTONUP. The WM_LBUTTONDOWN handler method simply initializes the drag and drop process by finding the item that the user clicked on.
void CDragAndDropListBox::OnLButtonDown(UINT nFlags, CPoint point) { CListBox::OnLButtonDown(nFlags, point); //clear all the flags m_MovingIndex = LB_ERR; m_MoveToIndex = LB_ERR; m_Captured = FALSE; m_Interval = 0; BOOL Outside; //Find the item that they want to drag and keep track of it. //Later in the mouse move, we will capture the mouse if this //value is set. int Index = ItemFromPoint(point,Outside); if (Index != LB_ERR && !Outside) { m_MovingIndex = Index; SetCurSel(Index); } }
The WM_WMMOUSEMOVE handler method does three things:
- Capture the mouse if it hasn’t done so.
- Scroll the listbox if the user has dragged the mouse above or below the listbox window (it does this with the help of timers).
- Draw a line above the item where the dragged item will be dropped, it also keeps track of the item’s index value so that WM_LBUTTONUP does not have to find the information again.
void CDragAndDropListBox::OnMouseMove(UINT nFlags, CPoint point) { CListBox::OnMouseMove(nFlags, point); if (nFlags & MK_LBUTTON) { if (m_MovingIndex != LB_ERR && !m_Captured) { SetCapture(); m_Captured = TRUE; } BOOL Outside; int Index = ItemFromPoint(point,Outside); //if they our not on a particular item if (Outside) { CRect ClientRect; GetClientRect(&ClientRect); //If they are still within the listbox window, simply //select the last item as the drop point; else, if //they are outside the window, scroll the items. if (ClientRect.PtInRect(point)) { KillTimer(TID_SCROLLDOWN); KillTimer(TID_SCROLLUP); m_Interval = 0; //indicates that the user wants to drop the item //at the end of the list Index = LB_ERR; Outside = FALSE; } else { DoTheScrolling(point,ClientRect); } } else { KillTimer(TID_SCROLLDOWN); KillTimer(TID_SCROLLUP); m_Interval = 0; } if (Index != m_MoveToIndex && !Outside) { DrawTheLines(Index); } } }
Next, the WM_LBUTTONUP message handler will do the actual drop operation. It first checks to make sure that there was a item being dragged to begin with. Next, it checks to make sure that the button was released within the listbox window. If it wasn’t, there is nothing to do. If, on the other hand, it was released within the window, simply insert the item into the listbox at the index indicated by the WM_MOUSEMOVE handler.
void CDragAndDropListBox::OnLButtonUp(UINT nFlags, CPoint point) { if (m_MovingIndex != LB_ERR && m_Captured) { KillTimer(TID_SCROLLDOWN); KillTimer(TID_SCROLLUP); m_Interval = 0; m_Captured = FALSE; ReleaseCapture(); CRect Rect; GetClientRect(&Rect); //if they are still within the listbox window if (Rect.PtInRect(point)) { InsertDraggedItem(); } else { Invalidate(); UpdateWindow(); } m_MovingIndex = LB_ERR; m_MoveToIndex = LB_ERR; } CListBox::OnLButtonUp(nFlags, point); }
Using the Code
To use this class, simply attach a variable (subclass) to the listbox control on your window, and then change the variable from CListBox to CDragAndDropListBox. That’s all.
// CDragDropListBoxSampleDlg dialog class CDragDropListBoxSampleDlg : public CDialog { ...... // Implementation protected: CDragAndDropListBox m_ListBox; ...... };