A Drag and Drop List Control

WEBINAR: On-demand webcast

How to Boost Database Development Productivity on Linux, Docker, and Kubernetes with Microsoft SQL Server 2017 REGISTER >

There are already several articles on CodeGuru that describe aspects of drag and drop in list and tree controls, particularly Dragging Items to Rearrange Rows by Wayne Berthin, and CreateDragImage for multiple selected items in CListCtrl by Frank Kobs. Unfortunately, none of these articles present a complete solution with working code, which is thus the purpose of this contribution.

This article includes a demo project, a CListCtrl derived class, and a release build EXE for a quick demonstration of the capabilities of the class. I don't intend the class to be all singing and dancing, but it is good enough for me to use in my current project. No doubt there are further enhancements that could be made it, so please be my guest.

The new class, CDragDropListCtrl, has the following features:

  • Supports dragging of single and multiple selections.
  • Potential drag targets are highlighted (selected) as the mouse moves over them.
  • The list control will scroll when you try to drag out of the top or bottom.
  • Horizontal mouse movement is ignored; if the mouse is to the left or right of the list control, dragging still occurs as though the mouse is over the control.
  • Works with LVS_EX_FULLROWSELECT style on or off.
  • Preserves the checked state of dragged items.
  • All code is encapsulated in the control—no changes are required to the parent class.

The class grew out of the original article by Wayne Berthin, but I've rewritten most of the code, and added a heap more. The CreateDragImageEx method is almost the same as that given by Frank Kobs, with a minor bug fix so that it works correctly when the LVS_EX_FULLROWSELECT style is not set.

To use the new class, first add the DragDropListCtrl.cpp and DragDropListCtrl.h files to your project. Then, add a list control to your dialog resource, and set the View style to Report. Use ClassWizard to add a variable for the list control. The Category for the variable should be Control, and the Variable type should be CDragDropListCtrl. Finally, add this line to the top of your dialog header file:

#include "DragDropListCtrl.h"

That's all there is to it!



Downloads

Comments

  • Line Separator

    Posted by Dimo on 08/10/2017 07:53am

    I needed Line where elements will be droped so i decided to leave it here if someone decides to use it DrawDropLine replaces the item selection and RemovePreviousLine replaces RestorePrevDropItemState I made alot of changes to the source so don't use this blindly void CDragAndDropListControl::RemovePreviousLine() { if (m_nPrevDropIndex GetItemCount()) return; CClientDC oClientDC(this); COLORREF ccfBgColor = oClientDC.GetBkColor(); CPen pen( PS_SOLID, 0, ccfBgColor ); oClientDC.SelectObject( &pen ); if ( m_nPrevDropIndex == GetItemCount() ) DrawSeparatorAtBottom(oClientDC, m_nPrevDropIndex - 1); else DrawSeparatorAtTop(oClientDC, m_nPrevDropIndex); } void CDragAndDropListControl::DrawDropLine( int nDropIndex ) { CClientDC oClientDC(this); CPen pen( PS_SOLID, 0, RGB_LINE_SEPARATOR_COLOR ); oClientDC.SelectObject( &pen ); if ( m_nPrevDropIndex == GetItemCount() ) DrawSeparatorAtBottom(oClientDC, nDropIndex - 1); else DrawSeparatorAtTop(oClientDC, nDropIndex); } void CDragAndDropListControl::DrawSeparatorAtTop( CClientDC& oClientDC, int nIndex ) { CRect rectItem; GetItemRect( nIndex, rectItem, LVIR_BOUNDS); POINT ptTopLeft = rectItem.TopLeft(); oClientDC.MoveTo(ptTopLeft.x + rectItem.Width(), ptTopLeft.y); oClientDC.LineTo( ptTopLeft.x, ptTopLeft.y ); if ( nIndex == 0 ) oClientDC.MoveTo(ptTopLeft.x + rectItem.Width(), ptTopLeft.y); else oClientDC.MoveTo(ptTopLeft.x + rectItem.Width(), ptTopLeft.y - 2); oClientDC.LineTo(ptTopLeft.x + rectItem.Width(), ptTopLeft.y + 3); if ( nIndex == 0 ) oClientDC.MoveTo(ptTopLeft.x, ptTopLeft.y); else oClientDC.MoveTo(ptTopLeft.x, ptTopLeft.y - 2); oClientDC.LineTo(ptTopLeft.x, ptTopLeft.y + 3); } void CDragAndDropListControl::DrawSeparatorAtBottom( CClientDC& oClientDC, int nIndex ) { CRect rectItem; GetItemRect( nIndex, rectItem, LVIR_BOUNDS); POINT ptTopLeft = rectItem.BottomRight(); oClientDC.MoveTo(ptTopLeft.x - rectItem.Width(), ptTopLeft.y); oClientDC.LineTo( ptTopLeft.x, ptTopLeft.y ); if ( nIndex == 0 ) oClientDC.MoveTo(ptTopLeft.x - rectItem.Width(), ptTopLeft.y); else oClientDC.MoveTo(ptTopLeft.x - rectItem.Width(), ptTopLeft.y - 2); oClientDC.LineTo(ptTopLeft.x - rectItem.Width(), ptTopLeft.y + 3); if ( nIndex == 0 ) oClientDC.MoveTo(ptTopLeft.x, ptTopLeft.y); else oClientDC.MoveTo(ptTopLeft.x, ptTopLeft.y - 2); oClientDC.LineTo(ptTopLeft.x, ptTopLeft.y + 3); }

    Reply
  • Not getting LButtonUp on single clicks

    Posted by yooper on 07/27/2009 10:56am

    I've implemented this control in one of my projects, and I'm seeing some slightly strange behavior. When I click my left mouse button on any item and release without dragging, I don't get the WM_ON_LBUTTONUP message. However, if I double-click on any item, I do get the WM_ON_LBUTTONUP message. Could this have something to do with the SetCapture()? Or is there something else I'm missing? Thanks!

    Reply
  • Problems with common controls v6 (XP and Vista style appearance)

    Posted by rjones on 01/07/2008 06:31pm

    There are several problems using this control with the new-style XP and Vista controls (common controls v6), including excessive flickering, autoscroll not working, and the drag image having an incorrect appearance. See the following thread for more details: http://www.codeguru.com/forum/showthread.php?t=442854 At present the thread describes a fix for the flickering and autoscroll problems, but there's still no fix for the odd-looking drag image. Can anyone find a fix for that?

    Reply
  • This is the fix for dragging to upper items

    Posted by jastek on 06/22/2005 01:07am

    All you have to do is change line #361 ---
    Original:
    356: void CDragDropListCtrl::DropItem()
    357: {
    358: RestorePrevDropItemState();
    359:
    360: // Drop after currently selected item.
    361: m_nDropIndex++;
    New:
    356: void CDragDropListCtrl::DropItem()
    357: {
    358: RestorePrevDropItemState();
    359:
    360: // Drop after currently selected item.
    361: if (m_nDropIndex > m_anDragIndexes[0]) m_nDropIndex++;

    Reply
  • Fix for using with itemdata for each item

    Posted by mecug on 03/16/2004 01:32am

    Before deleting drag item you must change value for lParam to NULL to prevent memory leak on droping item.
    
    void CDragDropListCtrl::DropItem()
    {
      ...
      // Change lParam data to NULL to prevent memory leak after DropItem
      lvItem.iItem=nDragIndex;
      lvItem.iSubItem=0;
      lvItem.mask=LVIF_PARAM;
      lvItem.lParam=(LPARAM)0L;
      SetItem(&lvItem);
    
      // Delete the original item.
      DeleteItem(nDragIndex);
      ...
    }
    
    Other fix: delete dcMem:
    
    CImageList* CDragDropListCtrl::CreateDragImageEx(LPPOINT lpPoint)
    {
     ...
     dcMem.SelectObject(pOldMemDCBitmap);
    
     // created with CreateCompatibleDC and you must be delete it 
     dcMem.DeleteDC();
     ...
    }

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • As all sorts of data becomes available for storage, analysis and retrieval - so called 'Big Data' - there are potentially huge benefits, but equally huge challenges...
  • The agile organization needs knowledge to act on, quickly and effectively. Though many organizations are clamouring for "Big Data", not nearly as many know what to do with it...
  • Cloud-based integration solutions can be confusing. Adding to the confusion are the multiple ways IT departments can deliver such integration...

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date