在CListCtrl之间及CListCtrl内部实现拖放
参考我的资源里.上传后地址还没出来,等出来后再放上
介绍
本篇文章将介绍如何实现使用拖放方法从一个CListCtrl移动项目到另一个CListCtrl,同样也显示了如何使用拖放
从在CListCtrl内部实现改变项目顺序。这是我很早就想学的一些东西,并且同样在Code Project看到很多关于这个的问题。
特别是如何在一个CListCtrl内部重排项目顺序。我希望这篇文章能回答那些问题。我很肯定还有其他方法可实现同样功能,
也许那些方法更好用,但我这个对我来说已经工作的很好了。
首先,我要先说明,网上的微软知识库有一个很好的工程例子(http://support.microsoft.com/default.aspx?scid=kb;en-us;Q148738),
它给我很大帮助。我的代码跟那个很像。那个例子说明了从CListView拖放到CTreeView。那跟我现在要介绍的有一些细微差别。不过微软
知识库的例子只给了代码,没有说明文字,尽管它有很好的注释。
同样,这篇文章假设你已经至少很熟悉如何使用CListCtrl了。在Code Project有两篇很好的文章介绍了如何使用CListCtrl和CHeadCtrl控件。
并且Chris Maunder写了一篇非常好的文章介绍使用CListCtrl的回调。如果你没有阅读过他们,请赶快阅读下哦。
概要:
Drag 和 Drop操作至少涉及以下操作步骤:
1.你处理拖放操作的通知消息(即用户点了鼠标左键,并开始拖动)
2.你必须跟踪鼠标的移动,那时用户在拖动项目。
3.最后,当鼠标按键释放时,实现实际的复制和移动动作。
对话框需要添加的成员变量:
在我们完成拖放操作时需要保存一些信息,所以我们给对话框添加一些成员变量,他们只需要实现为protected成员,因为只需要内部使用。
m_nDragIndex 是我们将要移动的CListCtrl项目索引
m_nDropIndex 是CListCtrl中鼠标将要停放的索引
m_pDragWnd 和 m_pDropWnd 是拖放涉及的CListCtrl窗口指针
m_bDragging 标识我们正在拖放操作. 在MouseMove函数中使用以便使我们知道何时跟踪
m_pDragImage 是一个CImageList对象指针. MFC使得创建和管理拖放对象的图片变得非常容易
最后需要一个数据结构保存从一个列表拖到另一个列表 (或在同一个列表当中从一个项目到另一个项目)的信息.该结构如下:
- typedef struct {
- LVITEM* plvi;
- CString sCol2;
- } lvItem, *plv
这个结构包含一个LVITEM指针和一个字符串。我们使用LVITEM指针来实现CListCtrl的GetItem,InsertItem,及其他操作。
CString保存CListCtrl的第二列的数据。如过你有超过两列数据,只要添加更多的字符串。
捕获Drag操作的通知信息:
使用向导添加处理LVN_BEGINDRAG消息的函数。顺便说下,如果你希望可以拖动多个控件,你需要在此消息为所有列表作处理。
首先,我们希望保存我们将拖动的项目索引。所以把那值保存到一个成员变量。我们在稍后使用这个索引来获取相关信息。
m_nDragIndex = pNMListView->iItem;
然后我们创建一个我们要拖动的项目图片。在这里MFC帮了我们很多。
有两个方法来实现这部分,最简单的是让MFC来帮你处理繁琐的操作,正如第一个方法所示。第二个方法也只是稍微复杂。但他
要求你创建使用的图片资源。第二个代码代码片断显示了该方法。
第一个方法:
- 创建拖动的图片
- POINT pt;
- int nOffset = 10; //offset in pixels for drag image (up and
- //to the left)
- pt.x = nOffset;
- pt.y = nOffset;
- m_pDragImage = m_listL.CreateDragImage(m_nDragIndex, &pt);
- ASSERT(m_pDragImage); //make sure it was created
- //We will call delete later (in LButtonUp) to clean this up
- Change the cursor to the drag image
- (still must perform DragMove() in OnMouseMove() to show it moving)
- m_pDragImage->BeginDrag(0, CPoint(nOffset, nOffset));
- m_pDragImage->DragEnter(GetDesktopWindow(), pNMListView->ptAction);
第二个方法:
- 创建拖动的图片
- POINT pt;
- int nOffset = -10; //offset in pixels for drag image
- //(positive is up and to the left;
- //neg is down and to the right)
- if(m_listL.GetSelectedCount() > 1) //more than one item is selected
- pt.x = nOffset;
- pt.y = nOffset;
- m_pDragImage = m_listL.CreateDragImage(m_nDragIndex, &pt);
- ASSERT(m_pDragImage); //make sure it was created
- //We will call delete later (in LButtonUp) to clean this up
- CBitmap bitmap;
- if(m_listL.GetSelectedCount() > 1) //more than 1 item in list is selected
- //bitmap.LoadBitmap(IDB_BITMAP_MULTI);
- bitmap.LoadBitmap(IDB_BITMAP_MULTI_BOXES);
- else
- bitmap.LoadBitmap(IDB_BITMAP_BOX);
- m_pDragImage->Replace(0, &bitmap, &bitmap);
- Change the cursor to the drag image
- (still must perform DragMove() in OnMouseMove() to show it moving)
- m_pDragImage->BeginDrag(0, CPoint(nOffset, nOffset - 4));
- m_pDragImage->DragEnter(GetDesktopWindow(), pNMListView->ptAction);
- 然后我们设置一些成员变量的值。我们设置m_bDragging 为 TRUE,这样我们就知道开始了拖动
- Set dragging flag and others
- m_bDragging = TRUE; //we are in a drag and drop operation
- m_nDropIndex = -1; //we don't have a drop index yet
- m_pDragList = &m_listL; //make note of which list we are dragging from
- m_pDropWnd = &m_listL; //at present the drag list is the drop list
最后,我们调用SetCapture。这样我们保证了我们的控件(CListCtrl)能够接收所有的鼠标消息。
即便用户把项目拖离控件,甚至对话框,我们仍能接收到通知消息。直到我们释放了Capture(我们在松开右键时)。
或者直到另一个窗口Caoture了鼠标。
- Capture all mouse messages
- SetCapture ();
跟踪拖动过程:
使用类向导(ClassWizard),添加一个处理WM_MOUSEMOVE消息的函数。
只有在m_bDragging标记设置为TRUE时,才开始处理逻辑。
- if (m_bDragging)
- {
下一节我们就实际处理在屏幕移动拖动图片
DragShowNolock(false)调用允许窗口平滑显示拖动图片。如果我们不调用这个,拖动图片有时会显示它经过的画面。
- Move the drag image
- CPoint pt(point); //get our current mouse coordinates
- ClientToScreen(&pt); //convert to screen coordinates
- m_pDragImage->DragMove(pt); //move the drag image to those coordinates
- // Unlock window updates (this allows the dragging image to be shown
- // smoothly)
- m_pDragImage->DragShowNolock(false);
WindowFromPoint(pt)返回pt所在点的窗口。让我们知道鼠标指向哪里,他是否指向了CListCtrl窗口。
- // Get the CWnd pointer of the window that is under the
- // mouse cursor
- CWnd* pDropWnd = WindowFromPoint (pt);
- ASSERT(pDropWnd); //make sure we have a window
这节仅仅处理高亮显示我们拖动经过的CListCtrl项目
- If we drag outside current window we need
- // to adjust the highlights displayed
- if (pDropWnd != m_pDropWnd)
- {
- //If we drag over the CListCtrl header, turn off the
- // hover highlight
- if (m_nDropIndex != -1)
- {
- TRACE("m_nDropIndex is -1/n");
- CListCtrl* pList = (CListCtrl*)m_pDropWnd;
- VERIFY (pList->SetItemState (m_nDropIndex, 0,
- LVIS_DROPHILITED));
- // redraw item
- VERIFY (pList->RedrawItems (m_nDropIndex,
- m_nDropIndex));
- pList->UpdateWindow ();
- m_nDropIndex = -1;
- }
- else //If we drag out of the CListCtrl altogether
- {
- TRACE("m_nDropIndex is not -1/n");
- CListCtrl* pList = (CListCtrl*)m_pDropWnd;
- int i = 0;
- int nCount = pList->GetItemCount();
- for(i = 0; i < nCount; i++)
- {
- pList->SetItemState(i, 0, LVIS_DROPHILITED);
- }
- pList->RedrawItems(0, nCount);
- pList->UpdateWindow();
- }
- }
当我们移动鼠标的时候,我们需要保持跟踪我们拖动过什么窗口,这样当我们释放鼠标时我们知道把项目放到哪个控件上了。
所以我们把它保留到我们的成员变量。
- // Save current window pointer as the CListCtrl we are dropping onto
- m_pDropWnd = pDropWnd;
- // Convert from screen coordinates to drop target client coordinates
- pDropWnd->ScreenToClient(&pt);
IsKindOf(RUNTIME_CLASS(CListCtrl))是另一个常用函数,允许我们判断鼠标移动过的窗口是否是CListCtrl。这节处理鼠标移动过
的项目高亮显示。
- //If we are hovering over a CListCtrl we need to adjust
- //the highlights
- if(pDropWnd->IsKindOf(RUNTIME_CLASS (CListCtrl)))
- {
- UINT uFlags;
- CListCtrl* pList = (CListCtrl*)pDropWnd;
- // Turn off hilight for previous drop target
- pList->SetItemState (m_nDropIndex, 0, LVIS_DROPHILITED);
- // Redraw previous item
- pList->RedrawItems (m_nDropIndex, m_nDropIndex);
- // Get the item that is below cursor
- m_nDropIndex = ((CListCtrl*)pDropWnd)->HitTest(pt, &uFlags);
- // Highlight it
- pList->SetItemState(m_nDropIndex, LVIS_DROPHILITED,
- LVIS_DROPHILITED);
- // Redraw item
- pList->RedrawItems(m_nDropIndex, m_nDropIndex);
- pList->UpdateWindow();
- }
- // Lock window updates
- m_pDragImage->DragShowNolock(true);
- }
完成拖放,处理放的动作:
我们在此几乎完成了所有动作。再次使用类向导,添加一个WM_LBUTTONUP消息的处理函数。这部分很短很精致。
和上面的OnMouseMove函数一样,我们仅仅处理和拖放相关的代码
- if (m_bDragging)
- {
不要忘记释放捕捉的鼠标。
- // Release mouse capture, so that other controls
- // can get control/messages
- ReleaseCapture ();
当我们释放了物件,我们不再处理拖动处理。
- // Note that we are NOT in a drag operation
- m_bDragging = FALSE;
MFC再次给我们提供了函数组来处理拖动图片。不要忘记删除上边创建的(拖动开始时创建)那个图片。
- // End dragging image
- m_pDragImage->DragLeave (GetDesktopWindow ());
- m_pDragImage->EndDrag ();
- delete m_pDragImage;
- //must delete it because it was created at the beginning of the drag
跟我们在OnMouseMove函数中处理的那样,我们找出鼠标在哪,以及所处的窗口。然后我们检测是否是一个CListCtrl窗口。
如果是,我们处理实际的放动作。为了容易阅读。我让处理复制和移动的代码打包进DropItemOnList函数中(如下所示)
- CPoint pt (point); //Get current mouse coordinates
- ClientToScreen (&pt); //Convert to screen coordinates
- // Get the CWnd pointer of the window that is under
- //the mouse cursor
- CWnd* pDropWnd = WindowFromPoint (pt);
- ASSERT (pDropWnd); //make sure we have a window pointer
- // If window is CListCtrl, we perform the drop
- if (pDropWnd->IsKindOf (RUNTIME_CLASS (CListCtrl)))
- {
- m_pDropList = (CListCtrl*)pDropWnd;
- //Set pointer to the list we are dropping on
- DropItemOnList(m_pDragList, m_pDropList);
- //Call routine to perform the actual drop
- }
- }
然后进入最后的步骤,实际的复制/移动代码的处理。
实际复制/移动代码:
你需要手工添加一个函数来处理实际的复制/移动代码。这个函数可以是protected成员,
- void DropItemOnList(CListCtrl* pDragList, CListCtrl* pDropList);
以下是实际的函数:
- void CDragTestDlg::DropItemOnList(CListCtrl* pDragList,
- CListCtrl* pDropList)
- {
- //This routine performs the actual drop of the item dragged.
- //It simply grabs the info from the Drag list (pDragList)
- // and puts that info into the list dropped on (pDropList).
- //Send: pDragList = pointer to CListCtrl we dragged from,
- // pDropList = pointer to CListCtrl we are dropping on.
- //Return: nothing.
- Variables
- char szLabel[256];
- LVITEM lviT;
- LVITEM* plvitem;
- lvItem* pItem;
- lvItem lvi;
- // Unhilight the drop target
- pDropList->SetItemState (m_nDropIndex, 0, LVIS_DROPHILITED);
- //Set up the LV_ITEM for retrieving item from pDragList and adding the
- //new item to the pDropList
- ZeroMemory(&lviT, sizeof (LVITEM)); //allocate and clear memory space
- // for LV_ITEM
- lviT.iItem = m_nDragIndex;
- lviT.mask = LVIF_TEXT;
- lviT.pszText = szLabel;
- lviT.cchTextMax = 255;
- lvi.plvi = &lviT;
- lvi.plvi->iItem = m_nDragIndex;
- lvi.plvi->mask = LVIF_TEXT;
- lvi.plvi->pszText = szLabel;
- lvi.plvi->cchTextMax = 255;
- if(pDragList->GetSelectedCount() == 1)
- {
- // Get item that was dragged
- pDragList->GetItem (lvi.plvi);
- lvi.sCol2 = pDragList->GetItemText(lvi.plvi->iItem, 1);
- // Delete the original item (for Move operation)
- // This is optional. If you want to implement a Copy
- // operation, don't delete. This works very well though
- // for re-arranging items within a CListCtrl. It is written
- // at present such that when dragging from one list to the
- // other the item is copied, but if dragging within one list,
- // the item is moved.
- if(pDragList == pDropList)
- {
- pDragList->DeleteItem (m_nDragIndex);
- if(m_nDragIndex < m_nDropIndex) m_nDropIndex--;
- //decrement drop index to account for item
- //being deleted above it
- }
- // Insert item into pDropList
- // if m_nDropIndex == -1, iItem = GetItemCount()
- // (inserts at end of list), else iItem = m_nDropIndex
- lvi.plvi->iItem = (m_nDropIndex == -1) ?
- pDropList->GetItemCount () : m_nDropIndex;
- pDropList->InsertItem (lvi.plvi);
- pDropList->SetItemText(lvi.plvi->iItem, 1, (LPCTSTR)lvi.sCol2);
- // Select the new item we just inserted
- pDropList->SetItemState (lvi.plvi->iItem, LVIS_SELECTED,
- LVIS_SELECTED);
- }
- else //more than 1 item is being dropped
- {
- //We have to parse through all of the selected items from the
- // DragList
- //1) Retrieve the info for the items and store them in memory
- //2) If we are reordering, delete the items from the list
- //3) Insert the items into the list (either same list or
- // different list)
- CList<lvItem*, lvItem*> listItems;
- POSITION listPos;
- //Retrieve the selected items
- POSITION pos = pDragList->GetFirstSelectedItemPosition();
- //iterator for the CListCtrl
- while(pos) //so long as we have a valid POSITION, we keep
- // iterating
- {
- plvitem = new LVITEM;
- ZeroMemory(plvitem, sizeof(LVITEM));
- pItem = new lvItem;
- //ZeroMemory(pItem, sizeof(lvItem));
- //If you use ZeroMemory on the lvItem struct,
- //it creates an error when you try to set sCol2
- pItem->plvi = plvitem;
- pItem->plvi->iItem = m_nDragIndex;
- pItem->plvi->mask = LVIF_TEXT;
- pItem->plvi->pszText = new char;
- //since this is a pointer to the string, we need a
- //new pointer to a new string on the heap
- pItem->plvi->cchTextMax = 255;
- m_nDragIndex = pDragList->GetNextSelectedItem(pos);
- //Get the item
- pItem->plvi->iItem = m_nDragIndex; //set the index in
- //the drag list to the selected item
- pDragList->GetItem(pItem->plvi); //retrieve the
- // information
- pItem->sCol2 = pDragList->GetItemText(
- pItem->plvi->iItem, 1);
- //Save the pointer to the new item in our CList
- listItems.AddTail(pItem);
- } //EO while(pos) -- at this point we have deleted the moving
- // items and stored them in memory
- if(pDragList == pDropList) //we are reordering the list (moving)
- {
- //Delete the selected items
- pos = pDragList->GetFirstSelectedItemPosition();
- while(pos)
- {
- pos = pDragList->GetFirstSelectedItemPosition();
- m_nDragIndex = pDragList->GetNextSelectedItem(pos);
- pDragList->DeleteItem(m_nDragIndex);
- //since we are MOVING, delete the item
- if(m_nDragIndex < m_nDropIndex) m_nDropIndex--;
- //must decrement the drop index to account
- //for the deleted items
- } //EO while(pos)
- } //EO if(pDragList...
- //Iterate through the items stored in memory and add them
- //back into the CListCtrl at the drop index
- listPos = listItems.GetHeadPosition();
- while(listPos)
- {
- pItem = listItems.GetNext(listPos);
- m_nDropIndex = (m_nDropIndex == -1) ?
- pDropList->GetItemCount() : m_nDropIndex;
- pItem->plvi->iItem = m_nDropIndex;
- pDropList->InsertItem(pItem->plvi); //add the item
- pDropList->SetItemText(pItem->plvi->iItem, 1,
- pItem->sCol2);
- pDropList->SetItemState(pItem->plvi->iItem,
- LVIS_SELECTED, LVIS_SELECTED);
- //highlight/select the item we just added
- m_nDropIndex++;
- //increment the index we are dropping at to keep the
- //dropped items in the same order they were in in the
- //Drag List. If we dont' increment this, the items are
- //added in reverse order
- //Lastly, we need to clean up by deleting our "new"
- //variables
- delete pItem;
- } //EO while(listPos)
- }
- }
我们简单的设置LVITEM结构同时用来获取拖放来源控件的信息,以及添加该信息进拖放目标控件。
如果你的CListCtrl控件包含图标,不要忘记改变"lvi.mask=LVIF_TEXT"所在行。
所以我们设置来LVITEM结构,然后使用GetItem(&lvi)来获取我们拖动的列表项目的信息。
但你将注意到这个不会获取第二列的信息。那时我们为什么需要lvItem结构,我们使用pItem->sCol2=pDragList->GetItemText(pItem->plvi->iItem, 1);
来获取那个信息。
当我们处理拖动多项选择时,我们在lvItem对象设置一个CList,我们处理拖动来源CListCtrl并获取所有选项信息。然后如果我们处理的是Move操作,
我们删除所有来源的选项,然后通过处理CList并添加每一个项目到CListCtrl。
正如我的样例程序所实现,我检测用户是否拖动项目到其他的CListCtrl还是在同一个CListCtrl。如果用户释放一个项目到其他的CListCtrl,然后我们会
复制项目到其它控件,如果,用户仅仅拖放到同一个控件的不同点,我们将移动项目到新的索引(这是如何实现用户给一个列表按拖动来排序)。一般来说,
如果用户移动同一个控件的项目,我们会在添加回原列表前删除原来项目(获取完项目的信息后)。我们必须先删除,因为如果我们先添加的话,索引将不一致。
并且在很多实例中我们不允许将信息写到已存在的项目中。试着思考我所说的意思。在我们添加信息回列表后删除该项目,拖动列表的项目并看看发生了什么。
(译者注:最后一句我也不知道说的啥,只能猜测,所以把原文抄上: Try it to see what I mean. Move the part where we delete the item to AFTER we have added the item to the list. Drag items around in the list and see what happens.)
最后,我们添加项目到CListCtrl并设置该项目为高亮。
很简单,不是吗?