///////////////////////////////////////////////////////////////////////////// // DragDropListCtrl.cpp : implementation file // // Adrian Stanley 13/02/2000 // Totally free source code - use however you like. // adrian@adrians.demon.co.uk. // // This class illustrates how to implement drag and drop for a MFC // list control. // // It 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 box 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 checked state of dragged items. // All code encapulated in the control - no changes required to parent // class. ///////////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "DragDropListCtrl.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif static const int SCROLL_TIMER_ID = 1; //////////////////////////////////////////////////////////////////////////// CDragDropListCtrl::CDragDropListCtrl() : m_nDropIndex(-1), m_pDragImage(NULL), m_nPrevDropIndex(-1), m_uPrevDropState(NULL), m_uScrollTimer(0), m_ScrollDirection(scrollNull), m_dwStyle(NULL), m_nCurIndex(-1) { } CDragDropListCtrl::~CDragDropListCtrl() { // Fail safe clean up. delete m_pDragImage; m_pDragImage = NULL; KillScrollTimer(); } BEGIN_MESSAGE_MAP(CDragDropListCtrl, CListCtrl) ON_WM_MOUSEMOVE() ON_WM_LBUTTONUP() ON_NOTIFY_REFLECT(LVN_BEGINDRAG, OnBeginDrag) ON_WM_TIMER() END_MESSAGE_MAP() // MFC控件事件函数-拖动时产生的事件; void CDragDropListCtrl::OnBeginDrag(NMHDR* pNMHDR, LRESULT* pResult) { NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR; if (pNMListView) { m_nPrevDropIndex = -1; m_uPrevDropState = NULL; // Items being dragged - can be one or more. // 可以拖动一个或多个子项; m_anDragIndexes.RemoveAll(); // 清空数组,重新添加被选中的拖动的项索引; POSITION pos = GetFirstSelectedItemPosition(); while (pos) { m_anDragIndexes.Add(GetNextSelectedItem(pos)); } // 获取当前控件的模式; DWORD dwStyle = GetStyle(); // 如果是单选,设置为多选样式以使能够拖动时能多个图标一起移动; if ((dwStyle & LVS_SINGLESEL) == LVS_SINGLESEL) { // List control is single select; we need it to be multi-select so // we can show both the drag item and potential drag target as selected. m_dwStyle = dwStyle; ModifyStyle(LVS_SINGLESEL, NULL); } // 如果选中的即将被拖动的项索引数不为0; if (m_anDragIndexes.GetSize() > 0) { // Create a drag image from the centre point of the item image. // Clean up any existing drag image first. // 清空所有之前存在的拖动图标,并以当前项中心点重新创建一个拖动图标; delete m_pDragImage; CPoint ptDragItem; // 创建新的拖动图标; m_pDragImage = CreateDragImageEx(&ptDragItem); if (m_pDragImage) { m_pDragImage->BeginDrag(0, ptDragItem); //m_pDragImage->DragEnter(CWnd::GetDesktopWindow(), pNMListView->ptAction); m_pDragImage->DragEnter(GetParent(), pNMListView->ptAction); // Capture all mouse messages in case the user drags outside the control. // 设置当前线程窗口捕获鼠标; SetCapture(); } } } *pResult = 0; } // Based on code by Frank Kobs. CImageList* CDragDropListCtrl::CreateDragImageEx(LPPOINT lpPoint) { // 单个项时的矩形; CRect rectSingle; // 所有项并集后的矩形大小; CRect rectComplete(0, 0, 0, 0); // 选中项的索引; int nIndex = -1; // 是否初始化并集矩形变量; BOOL bFirst = TRUE; // Determine the size of the drag image. // 确定拖动图标的大小; POSITION pos = GetFirstSelectedItemPosition(); while (pos) { nIndex = GetNextSelectedItem(pos); GetItemRect(nIndex, rectSingle, LVIR_BOUNDS); if (bFirst) { // Initialize the CompleteRect // 初始化并集矩形变量; GetItemRect(nIndex, rectComplete, LVIR_BOUNDS); bFirst = FALSE; } // 并集每一个项的大小; rectComplete.UnionRect(rectComplete, rectSingle); } // Create bitmap in memory DC // 获取当前客户区域DC句柄; CClientDC dcClient(this); CDC dcMem; CBitmap Bitmap; // 创建兼容内存位图DC; if (!dcMem.CreateCompatibleDC(&dcClient)) { return NULL; } // 在DC上创建兼容位图; if (!Bitmap.CreateCompatibleBitmap(&dcClient, rectComplete.Width(), rectComplete.Height())) { return NULL; } // 保存DC上创建的兼容位图; CBitmap* pOldMemDCBitmap = dcMem.SelectObject(&Bitmap); // Here green is used as mask color. // 用绿色作为屏蔽色; dcMem.FillSolidRect(0, 0, rectComplete.Width(), rectComplete.Height(), RGB(0, 255, 0)); // Paint each DragImage in the DC. // 在DC环境上画出每一个项的拖动图标; CImageList* pSingleImageList = NULL; CPoint pt; pos = GetFirstSelectedItemPosition(); while (pos) { nIndex = GetNextSelectedItem(pos); GetItemRect(nIndex, rectSingle, LVIR_BOUNDS); // 为每一个项创建拖动图标; pSingleImageList = CreateDragImage(nIndex, &pt); if (pSingleImageList) { // Make sure width takes into account not using LVS_EX_FULLROWSELECT style. IMAGEINFO ImageInfo; // 获取每个项的图标信息; pSingleImageList->GetImageInfo(0, &ImageInfo); rectSingle.right = rectSingle.left + (ImageInfo.rcImage.right - ImageInfo.rcImage.left); // 合并所有拖动图标并在并集矩形内重绘; pSingleImageList->DrawIndirect( &dcMem, 0, CPoint(rectSingle.left - rectComplete.left, rectSingle.top - rectComplete.top), rectSingle.Size(), CPoint(0,0)); // 释放内存; delete pSingleImageList; } } dcMem.SelectObject(pOldMemDCBitmap); // Create the imagelist with the merged drag images. // 用合并后的拖动图标矩形创建一个ImageList对象; CImageList* pCompleteImageList = new CImageList; pCompleteImageList->Create(rectComplete.Width(), rectComplete.Height(), ILC_COLOR | ILC_MASK, 0, 1); // Here green is used as mask color. pCompleteImageList->Add(&Bitmap, RGB(0, 255, 0)); Bitmap.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 ptCursor; GetCursorPos(&ptCursor); ScreenToClient(&ptCursor); lpPoint->x = ptCursor.x - rectComplete.left; lpPoint->y = ptCursor.y - rectComplete.top; } return pCompleteImageList; } void CDragDropListCtrl::OnMouseMove(UINT nFlags, CPoint point) { if (m_pDragImage) { // Must be dragging, as there is a drag image. // Move the drag image. // 移动拖动图标; CPoint ptDragImage(point); ClientToScreen(&ptDragImage); m_pDragImage->DragMove(ptDragImage); // Leave dragging so we can update potential drop target selection. // 去除DragLeave函数,在拖动时就不会闪烁了; //m_pDragImage->DragLeave(CWnd::GetDesktopWindow()); //m_pDragImage->DragLeave(GetParent()); // Force x coordinate to always be in list control - only interested in y coordinate. // In effect the list control has captured all horizontal mouse movement. static const int nXOffset = 8; CRect rect; GetWindowRect(rect); // 获取移动中鼠标下方的窗口句柄; CWnd* pDropWnd = CWnd::WindowFromPoint(CPoint(rect.left + nXOffset, ptDragImage.y)); // Get the window under the drop point. if (pDropWnd == this) { // Still in list control so select item under mouse as potential drop target. // 如果点仍在CListCtrl控件内,更新拖动图标的位置; point.x = nXOffset; // Ensures x coordinate is always valid. UpdateSelection(HitTest(point)); } // 获取控件的客户区域; CRect rectClient; GetClientRect(rectClient); // 获取拖动图标的坐标点; CPoint ptClientDragImage(ptDragImage); ScreenToClient(&ptClientDragImage); // Client rect includes header height, so ignore it, i.e., // moving the mouse over the header (and higher) will result in a scroll up. CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0); if (pHeader) { CRect rectHeader; pHeader->GetClientRect(rectHeader); rectClient.top += rectHeader.Height(); } if (ptClientDragImage.y < rectClient.top) { // Mouse is above the list control - scroll up. SetScrollTimer(scrollUp); } else if (ptClientDragImage.y > rectClient.bottom) { // Mouse is below the list control - scroll down. SetScrollTimer(scrollDown); } else { KillScrollTimer(); } // Resume dragging. //m_pDragImage->DragEnter(CWnd::GetDesktopWindow(), ptDragImage); m_pDragImage->DragEnter(GetParent(), ptDragImage); } else { KillScrollTimer(); } CListCtrl::OnMouseMove(nFlags, point); } void CDragDropListCtrl::UpdateSelection(int nDropIndex) { if (nDropIndex > -1 && nDropIndex < GetItemCount()) { // Drop index is valid and has changed since last mouse movement. RestorePrevDropItemState(); // Save information about current potential drop target for restoring next time round. m_nPrevDropIndex = nDropIndex; m_uPrevDropState = GetItemState(nDropIndex, LVIS_SELECTED); // Select current potential drop target. SetItemState(nDropIndex, LVIS_SELECTED, LVIS_SELECTED); m_nDropIndex = nDropIndex; // Used by DropItem(). UpdateWindow(); } } void CDragDropListCtrl::RestorePrevDropItemState() { if (m_nPrevDropIndex > -1 && m_nPrevDropIndex < GetItemCount()) { // Restore state of previous potential drop target. SetItemState(m_nPrevDropIndex, m_uPrevDropState, LVIS_SELECTED); } } void CDragDropListCtrl::SetScrollTimer(EScrollDirection ScrollDirection) { if (m_uScrollTimer == 0) { m_uScrollTimer = SetTimer(SCROLL_TIMER_ID, 100, NULL); } m_ScrollDirection = ScrollDirection; } void CDragDropListCtrl::KillScrollTimer() { if (m_uScrollTimer != 0) { KillTimer(SCROLL_TIMER_ID); m_uScrollTimer = 0; m_ScrollDirection = scrollNull; } } void CDragDropListCtrl::OnLButtonUp(UINT nFlags, CPoint point) { if (m_pDragImage) { KillScrollTimer(); // Release the mouse capture and end the dragging. // 释放对窗口鼠标的捕获; ::ReleaseCapture(); //m_pDragImage->DragLeave(CWnd::GetDesktopWindow()); m_pDragImage->DragLeave(GetParent()); m_pDragImage->EndDrag(); m_pDragImage->DeleteImageList(); delete m_pDragImage; m_pDragImage = NULL; // Drop the item on the list. DropItem(point); } CListCtrl::OnLButtonUp(nFlags, point); } void CDragDropListCtrl::DropItem() { RestorePrevDropItemState(); // Drop after currently selected item. // 删除当前选中的项; m_nDropIndex++; if (m_nDropIndex < 0 || m_nDropIndex > GetItemCount() - 1) { // Fail safe - invalid drop index, so drop at end of list. m_nDropIndex = GetItemCount(); }// 从尾部开始; int nColumns = 1; CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0); if (pHeader) { nColumns = pHeader->GetItemCount(); } // Move all dragged items to their new positions. // 将所有拖动项移动到新位置; for (int nDragItem = 0; nDragItem < m_anDragIndexes.GetSize(); nDragItem++) { // 获取拖动项的索引; int nDragIndex = m_anDragIndexes[nDragItem]; if (nDragIndex > -1 && nDragIndex < GetItemCount()) { // Get information about this drag item. // 获取指定索引位置的拖动项的信息,LV_ITEM必须指定iItem值,才能GetItem获取mask标识的其他项信息; // 同时,这属于一个GetItem的使用示例; char szText[256]; LV_ITEM lvItem; ZeroMemory(&lvItem, sizeof(LV_ITEM)); lvItem.iItem = nDragIndex; lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE | LVIF_PARAM; lvItem.stateMask = LVIS_DROPHILITED | LVIS_FOCUSED | LVIS_SELECTED | LVIS_STATEIMAGEMASK; lvItem.pszText = szText; lvItem.cchTextMax = sizeof(szText) - 1; GetItem(&lvItem); BOOL bChecked = GetCheck(nDragIndex); // Before moving drag item, make sure it is deselected in its original location, // otherwise GetSelectedCount() will return 1 too many. SetItemState(nDragIndex, static_cast(~LVIS_SELECTED), LVIS_SELECTED); // Insert the dragged item at drop index. // 重新赋值iItem,将拖动项插入到删除索引位置上(起始时m_nDropIndex为最后项索引); lvItem.iItem = m_nDropIndex; InsertItem(&lvItem); if (bChecked) { SetCheck(m_nDropIndex); } // Index of dragged item will change if item has been dropped above itself. if (nDragIndex > m_nDropIndex) { nDragIndex++; } // Fill in all the columns for the dragged item. // 填充所有拖动项的列信息(子项中的文字); lvItem.mask = LVIF_TEXT; lvItem.iItem = m_nDropIndex; for (int nColumn = 1; nColumn < nColumns; nColumn++) { _tcscpy(lvItem.pszText, (LPCTSTR)(GetItemText(nDragIndex, nColumn))); lvItem.iSubItem = nColumn; SetItem(&lvItem); } // Delete the original item. // 删除原始项; DeleteItem(nDragIndex); // Need to adjust indexes of remaining drag items. for (int nNewDragItem = nDragItem; nNewDragItem < m_anDragIndexes.GetSize(); nNewDragItem++) { int nNewDragIndex = m_anDragIndexes[nNewDragItem]; if (nDragIndex < nNewDragIndex && nNewDragIndex < m_nDropIndex) { // Item has been removed from above this item, and inserted after it, // so this item moves up the list. m_anDragIndexes[nNewDragItem] = max(nNewDragIndex - 1, 0); } else if (nDragIndex > nNewDragIndex && nNewDragIndex > m_nDropIndex) { // Item has been removed from below this item, and inserted before it, // so this item moves down the list. m_anDragIndexes[nNewDragItem] = nNewDragIndex + 1; } } if (nDragIndex > m_nDropIndex) { // Item has been added before the drop target, so drop target moves down the list. m_nDropIndex++; } } } if (m_dwStyle != NULL) { // Style was modified, so return it back to original style. ModifyStyle(NULL, m_dwStyle); m_dwStyle = NULL; } } void CDragDropListCtrl::OnTimer(UINT nIDEvent) { if (nIDEvent == SCROLL_TIMER_ID && m_pDragImage) { WPARAM wParam = NULL; int nDropIndex = -1; if (m_ScrollDirection == scrollUp) { wParam = MAKEWPARAM(SB_LINEUP, 0); nDropIndex = m_nDropIndex - 1; } else if (m_ScrollDirection == scrollDown) { wParam = MAKEWPARAM(SB_LINEDOWN, 0); nDropIndex = m_nDropIndex + 1; } m_pDragImage->DragShowNolock(FALSE); SendMessage(WM_VSCROLL, wParam, NULL); UpdateSelection(nDropIndex); m_pDragImage->DragShowNolock(TRUE); } else { CListCtrl::OnTimer(nIDEvent); } } int CDragDropListCtrl::GetPtItemIndex(CPoint pt) { // 判断点是否还在控件内; // CWnd* pDropWnd = CWnd::WindowFromPoint(pt); // if (pDropWnd != this) // { // AfxMessageBox(_T("不在控件上")); // return -1; // } // 遍历全部项,查找点在哪个项上; CRect rtItem; int nCount = GetItemCount(); int nIndex = 0; for (nIndex = 0; nIndex < nCount; nIndex++) { GetItemRect(nIndex, &rtItem, LVIR_BOUNDS);//LVIR_ICON if ( PtInRect(rtItem,pt) ) break; } // 返回项索引值; return (nIndex == nCount) ? -1 : nIndex; } void CDragDropListCtrl::DropItem(CPoint pt) { RestorePrevDropItemState(); // 获取点对应的项值; m_nCurIndex = GetPtItemIndex(pt); #if 0 CString strIndex; strIndex.Format(_T("%d~%d"), m_anDragIndexes[0], m_nCurIndex); AfxMessageBox(strIndex); #endif if(m_nCurIndex == -1) return; // 相等时,不处理; if (m_nCurIndex > m_anDragIndexes[0])// 拖动项往后拖; { /* int nColumns = 1; CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0); if (pHeader) nColumns = pHeader->GetItemCount(); int nDragIndex = m_anDragIndexes[0]; // 获取指定索引位置的拖动项的信息,LV_ITEM必须指定iItem值,才能GetItem获取mask标识的其他项信息; // 同时,这属于一个GetItem的使用示例; char szText[256] = {0}; LV_ITEM lvItem; ZeroMemory(&lvItem, sizeof(LV_ITEM)); lvItem.iItem = nDragIndex; lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE | LVIF_PARAM; lvItem.stateMask = LVIS_DROPHILITED | LVIS_FOCUSED | LVIS_SELECTED | LVIS_STATEIMAGEMASK; lvItem.pszText = szText; lvItem.cchTextMax = sizeof(szText) - 1; GetItem(&lvItem); BOOL bChecked = GetCheck(nDragIndex); SetItemState(nDragIndex, static_cast(~LVIS_SELECTED), LVIS_SELECTED); // Insert the dragged item at drop index. // 重新赋值iItem,将拖动项插入到删除索引位置上(起始时m_nDropIndex为最后项索引); lvItem.iItem = m_nDropIndex; InsertItem(&lvItem); if (bChecked) { SetCheck(m_nDropIndex); } // Index of dragged item will change if item has been dropped above itself. if (nDragIndex > m_nDropIndex) nDragIndex++; // Fill in all the columns for the dragged item. // 填充所有拖动项的列信息(子项中的文字); lvItem.mask = LVIF_TEXT; lvItem.iItem = m_nDropIndex; for (int nColumn = 1; nColumn < nColumns; nColumn++) { _tcscpy(lvItem.pszText, (LPCTSTR)(GetItemText(nDragIndex, nColumn))); lvItem.iSubItem = nColumn; SetItem(&lvItem); } // Delete the original item. // 删除原始项; DeleteItem(nDragIndex); */ } else if (m_nCurIndex < m_anDragIndexes[0])// 拖动项往前拖; { } #if 0 // Drop after currently selected item. // 删除当前选中的项; m_nDropIndex++; if (m_nDropIndex < 0 || m_nDropIndex > GetItemCount() - 1) { // Fail safe - invalid drop index, so drop at end of list. m_nDropIndex = GetItemCount(); }// 从尾部开始; int nColumns = 1; CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0); if (pHeader) { nColumns = pHeader->GetItemCount(); } // Move all dragged items to their new positions. // 将所有拖动项移动到新位置; for (int nDragItem = 0; nDragItem < m_anDragIndexes.GetSize(); nDragItem++) { // 获取拖动项的索引; int nDragIndex = m_anDragIndexes[nDragItem]; if (nDragIndex > -1 && nDragIndex < GetItemCount()) { // Get information about this drag item. // 获取指定索引位置的拖动项的信息,LV_ITEM必须指定iItem值,才能GetItem获取mask标识的其他项信息; // 同时,这属于一个GetItem的使用示例; char szText[256]; LV_ITEM lvItem; ZeroMemory(&lvItem, sizeof(LV_ITEM)); lvItem.iItem = nDragIndex; lvItem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE | LVIF_PARAM; lvItem.stateMask = LVIS_DROPHILITED | LVIS_FOCUSED | LVIS_SELECTED | LVIS_STATEIMAGEMASK; lvItem.pszText = szText; lvItem.cchTextMax = sizeof(szText) - 1; GetItem(&lvItem); BOOL bChecked = GetCheck(nDragIndex); // Before moving drag item, make sure it is deselected in its original location, // otherwise GetSelectedCount() will return 1 too many. SetItemState(nDragIndex, static_cast(~LVIS_SELECTED), LVIS_SELECTED); // Insert the dragged item at drop index. // 重新赋值iItem,将拖动项插入到删除索引位置上(起始时m_nDropIndex为最后项索引); lvItem.iItem = m_nDropIndex; InsertItem(&lvItem); if (bChecked) { SetCheck(m_nDropIndex); } // Index of dragged item will change if item has been dropped above itself. if (nDragIndex > m_nDropIndex) { nDragIndex++; } // Fill in all the columns for the dragged item. // 填充所有拖动项的列信息(子项中的文字); lvItem.mask = LVIF_TEXT; lvItem.iItem = m_nDropIndex; for (int nColumn = 1; nColumn < nColumns; nColumn++) { _tcscpy(lvItem.pszText, (LPCTSTR)(GetItemText(nDragIndex, nColumn))); lvItem.iSubItem = nColumn; SetItem(&lvItem); } // Delete the original item. // 删除原始项; DeleteItem(nDragIndex); // Need to adjust indexes of remaining drag items. for (int nNewDragItem = nDragItem; nNewDragItem < m_anDragIndexes.GetSize(); nNewDragItem++) { int nNewDragIndex = m_anDragIndexes[nNewDragItem]; if (nDragIndex < nNewDragIndex && nNewDragIndex < m_nDropIndex) { // Item has been removed from above this item, and inserted after it, // so this item moves up the list. m_anDragIndexes[nNewDragItem] = max(nNewDragIndex - 1, 0); } else if (nDragIndex > nNewDragIndex && nNewDragIndex > m_nDropIndex) { // Item has been removed from below this item, and inserted before it, // so this item moves down the list. m_anDragIndexes[nNewDragItem] = nNewDragIndex + 1; } } if (nDragIndex > m_nDropIndex) { // Item has been added before the drop target, so drop target moves down the list. m_nDropIndex++; } } } if (m_dwStyle != NULL) { // Style was modified, so return it back to original style. ModifyStyle(NULL, m_dwStyle); m_dwStyle = NULL; } #endif }