Introduction
This listbox class demonstrates how to rearrange listbox items using Drag and Drop without the overhead of OLE. The advantage of this method is that you don't have to create bulky global memory handles to simply move data within the same window in the same application. 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 initialized 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);
m_MovingIndex = LB_ERR;
m_MoveToIndex = LB_ERR;
m_Captured = FALSE;
m_Interval = 0;
BOOL Outside;
int Index = ItemFromPoint(point,Outside);
if (Index != LB_ERR && !Outside)
{
m_MovingIndex = Index;
SetCurSel(Index);
}
}
The WM_WMMOUSEMOVE
handler method does three things. One is to capture the mouse if it hasn't done so. Two is to scroll the listbox if the user has dragged the mouse above or below the listbox window (it does this with the help of timers). And finally, to 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 (Outside)
{
CRect ClientRect;
GetClientRect(&ClientRect);
if (ClientRect.PtInRect(point))
{
KillTimer(TID_SCROLLDOWN);
KillTimer(TID_SCROLLUP);
m_Interval = 0;
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 then there is nothing to do. If on the other hand it was released within the window, then 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 (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.
class CDragDropListBoxSampleDlg : public CDialog
{
......
protected:
CDragAndDropListBox m_ListBox;
......
};