CreateDragImage for multiple selected items in CListCtrl

Sample Image

Environment: VC6 SP2, NT4 SP4

Currently the CreateDragImage method from CListCtrl only supports the generation of drag images of a single selected item (at least I didn't found an alternative in the Win API). Inspired by an article from Pel K Txnder I found one way to create drag images for multiple selections.

The trick is to create a bitmap in a memory dc and paint the drag image from each selected item into it. This bitmap is added to the drag imagelist. The code enhances a derived CListCtrl (CListCtrlEx) with the method CreateDragImageEx. However, it should also work with a few modifications without subclassing the CListCtrl.


CImageList *CListCtrlEx::CreateDragImageEx( LPPOINT lpPoint )
{
	CRect	cSingleRect;	
	CRect	cCompleteRect( 0,0,0,0 );
	int	nIdx;
	BOOL	bFirst = TRUE;
	//
	// Determine the size of the drag image 
	//
	POSITION pos = GetFirstSelectedItemPosition();
	while (pos)
	{
		nIdx = GetNextSelectedItem( pos );
		GetItemRect( nIdx, cSingleRect, LVIR_BOUNDS );
		if (bFirst)
		{
			// Initialize the CompleteRect
			GetItemRect( nIdx, cCompleteRect, LVIR_BOUNDS );
			bFirst = FALSE;
		}
		cCompleteRect.UnionRect( cCompleteRect, cSingleRect );
	}

	//
	// Create bitmap in memory DC
	//
	CClientDC	cDc(this);	
	CDC		cMemDC;	
	CBitmap   	cBitmap;

	if(!cMemDC.CreateCompatibleDC(&cDc))		
		return NULL;	

	if(!cBitmap.CreateCompatibleBitmap(&cDc, cCompleteRect.Width(), cCompleteRect.Height()))
		return NULL;
	
	CBitmap* pOldMemDCBitmap = cMemDC.SelectObject( &cBitmap );
	// Here green is used as mask color
	cMemDC.FillSolidRect(0,0,cCompleteRect.Width(), cCompleteRect.Height(), RGB(0, 255, 0)); 

	//
	// Paint each DragImage in the DC
	//
	CImageList *pSingleImageList;
	CPoint		cPt;

	pos = GetFirstSelectedItemPosition();
	while (pos)
	{
		nIdx = GetNextSelectedItem( pos );
		GetItemRect( nIdx, cSingleRect, LVIR_BOUNDS );

		pSingleImageList = CreateDragImage( nIdx, &cPt);
		if (pSingleImageList)
		{
			pSingleImageList->DrawIndirect( &cMemDC, 
							0, 
							CPoint( cSingleRect.left-cCompleteRect.left, 
							cSingleRect.top-cCompleteRect.top ),
							cSingleRect.Size(), 
							CPoint(0,0));
			delete pSingleImageList;
		}
	}

	cMemDC.SelectObject( pOldMemDCBitmap );
	//
	// Create the imagelist	with the merged drag images
	//
	CImageList* pCompleteImageList = new CImageList;
	
	pCompleteImageList->Create(	cCompleteRect.Width(), 
					cCompleteRect.Height(), 
					ILC_COLOR | ILC_MASK, 0, 1);
	// Here green is used as mask color
	pCompleteImageList->Add(&cBitmap, RGB(0, 255, 0)); 

	cBitmap.DeleteObject();
	//
	// as an optional service:
	// Find the offset of the current mouse cursor to the imagelist
	// this we can use in BeginDrag()
	//
	if ( lpPoint )
	{
		CPoint cCursorPos;
		GetCursorPos( &cCursorPos );
		ScreenToClient( &cCursorPos );
		lpPoint->x = cCursorPos.x - cCompleteRect.left;
		lpPoint->y = cCursorPos.y - cCompleteRect.top;
	}

	return( pCompleteImageList );
}
Sample usage:

void CDialogWhichUsesListCtrl::OnBeginDrag(NMHDR* pNMHDR, LRESULT* pResult)
{
	NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
	POINT pt;

	m_pDragImage = m_ctlList.CreateDragImageEx( &pt );

	m_pDragImage->BeginDrag( 0, pt );	
	m_pDragImage->DragEnter( GetDesktopWindow(), pNMListView->ptAction);	
	SetCapture();	
	*pResult = 0;
}

Downloads

Download source (same as above, but with proper formatting) - 1 Kb