在CListCtrl之间及CListCtrl内部实现拖放

在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使得创建和管理拖放对象的图片变得非常容易

 

最后需要一个数据结构保存从一个列表拖到另一个列表 (或在同一个列表当中从一个项目到另一个项目)的信息.该结构如下:

  1. typedef struct {  
  2.     LVITEM* plvi;  
  3.     CString sCol2;  
  4. } lvItem, *plv  
 

这个结构包含一个LVITEM指针和一个字符串。我们使用LVITEM指针来实现CListCtrl的GetItem,InsertItem,及其他操作。

CString保存CListCtrl的第二列的数据。如过你有超过两列数据,只要添加更多的字符串。

 

 

捕获Drag操作的通知信息:

使用向导添加处理LVN_BEGINDRAG消息的函数。顺便说下,如果你希望可以拖动多个控件,你需要在此消息为所有列表作处理。

首先,我们希望保存我们将拖动的项目索引。所以把那值保存到一个成员变量。我们在稍后使用这个索引来获取相关信息。

m_nDragIndex = pNMListView->iItem;

 

然后我们创建一个我们要拖动的项目图片。在这里MFC帮了我们很多。

有两个方法来实现这部分,最简单的是让MFC来帮你处理繁琐的操作,正如第一个方法所示。第二个方法也只是稍微复杂。但他

要求你创建使用的图片资源。第二个代码代码片断显示了该方法。

 

第一个方法:

  1.  创建拖动的图片   
  2.     POINT pt;  
  3.     int nOffset = 10; //offset in pixels for drag image (up and    
  4.                           //to the left)   
  5.     pt.x = nOffset;  
  6.     pt.y = nOffset;  
  7.     m_pDragImage = m_listL.CreateDragImage(m_nDragIndex, &pt);  
  8.     ASSERT(m_pDragImage); //make sure it was created   
  9.     //We will call delete later (in LButtonUp) to clean this up   
  10.      Change the cursor to the drag image   
  11.         (still must perform DragMove() in OnMouseMove() to show it moving)   
  12.     m_pDragImage->BeginDrag(0, CPoint(nOffset, nOffset));  
  13.     m_pDragImage->DragEnter(GetDesktopWindow(), pNMListView->ptAction);  
 

第二个方法:

  1.  创建拖动的图片   
  2.     POINT pt;  
  3.     int nOffset = -10; //offset in pixels for drag image    
  4.                            //(positive is up and to the left;    
  5.                            //neg is down and to the right)   
  6.     if(m_listL.GetSelectedCount() > 1) //more than one item is selected   
  7.     pt.x = nOffset;  
  8.     pt.y = nOffset;  
  9.     m_pDragImage = m_listL.CreateDragImage(m_nDragIndex, &pt);  
  10.     ASSERT(m_pDragImage); //make sure it was created   
  11.     //We will call delete later (in LButtonUp) to clean this up   
  12.     CBitmap bitmap;  
  13.     if(m_listL.GetSelectedCount() > 1) //more than 1 item in list is selected   
  14.         //bitmap.LoadBitmap(IDB_BITMAP_MULTI);   
  15.         bitmap.LoadBitmap(IDB_BITMAP_MULTI_BOXES);  
  16.     else  
  17.         bitmap.LoadBitmap(IDB_BITMAP_BOX);  
  18.     m_pDragImage->Replace(0, &bitmap, &bitmap);  
  19.      Change the cursor to the drag image   
  20.         (still must perform DragMove() in OnMouseMove() to show it moving)   
  21.     m_pDragImage->BeginDrag(0, CPoint(nOffset, nOffset - 4));  
  22.     m_pDragImage->DragEnter(GetDesktopWindow(), pNMListView->ptAction);  
  23. 然后我们设置一些成员变量的值。我们设置m_bDragging 为 TRUE,这样我们就知道开始了拖动  
  24.  Set dragging flag and others   
  25.     m_bDragging = TRUE;    //we are in a drag and drop operation   
  26.     m_nDropIndex = -1;    //we don't have a drop index yet   
  27.     m_pDragList = &m_listL; //make note of which list we are dragging from   
  28.     m_pDropWnd = &m_listL;    //at present the drag list is the drop list  
 

 

最后,我们调用SetCapture。这样我们保证了我们的控件(CListCtrl)能够接收所有的鼠标消息。

即便用户把项目拖离控件,甚至对话框,我们仍能接收到通知消息。直到我们释放了Capture(我们在松开右键时)。

或者直到另一个窗口Caoture了鼠标。

 

  1.  Capture all mouse messages   
  2.     SetCapture ();  
 

 

 

跟踪拖动过程:

使用类向导(ClassWizard),添加一个处理WM_MOUSEMOVE消息的函数。

只有在m_bDragging标记设置为TRUE时,才开始处理逻辑。

  1. if (m_bDragging)  
  2.     {  
 

 

下一节我们就实际处理在屏幕移动拖动图片

DragShowNolock(false)调用允许窗口平滑显示拖动图片。如果我们不调用这个,拖动图片有时会显示它经过的画面。

  1.  Move the drag image   
  2.         CPoint pt(point);    //get our current mouse coordinates   
  3.         ClientToScreen(&pt); //convert to screen coordinates   
  4.         m_pDragImage->DragMove(pt); //move the drag image to those coordinates   
  5.         // Unlock window updates (this allows the dragging image to be shown    
  6.                 // smoothly)   
  7.         m_pDragImage->DragShowNolock(false);  
 

 

WindowFromPoint(pt)返回pt所在点的窗口。让我们知道鼠标指向哪里,他是否指向了CListCtrl窗口。

 

  1. // Get the CWnd pointer of the window that is under the    
  2.                // mouse cursor   
  3.        CWnd* pDropWnd = WindowFromPoint (pt);  
  4.        ASSERT(pDropWnd); //make sure we have a window  
 

这节仅仅处理高亮显示我们拖动经过的CListCtrl项目

 

  1.  If we drag outside current window we need    
  2.                 //   to adjust the highlights displayed   
  3.         if (pDropWnd != m_pDropWnd)  
  4.         {  
  5.                         //If we drag over the CListCtrl header, turn off the    
  6.                         // hover highlight   
  7.             if (m_nDropIndex != -1)   
  8.             {  
  9.                 TRACE("m_nDropIndex is -1/n");  
  10.                 CListCtrl* pList = (CListCtrl*)m_pDropWnd;  
  11.                 VERIFY (pList->SetItemState (m_nDropIndex, 0,   
  12.                                                              LVIS_DROPHILITED));  
  13.                 // redraw item   
  14.                 VERIFY (pList->RedrawItems (m_nDropIndex,   
  15.                                                            m_nDropIndex));  
  16.                 pList->UpdateWindow ();  
  17.                 m_nDropIndex = -1;  
  18.    }  
  19.             else //If we drag out of the CListCtrl altogether   
  20.             {  
  21.                 TRACE("m_nDropIndex is not -1/n");  
  22.                 CListCtrl* pList = (CListCtrl*)m_pDropWnd;  
  23.                 int i = 0;  
  24.                 int nCount = pList->GetItemCount();  
  25.                 for(i = 0; i < nCount; i++)  
  26.                 {  
  27.                     pList->SetItemState(i, 0, LVIS_DROPHILITED);  
  28.                 }  
  29.                 pList->RedrawItems(0, nCount);  
  30.                 pList->UpdateWindow();  
  31.             }  
  32.         }  
 

 

当我们移动鼠标的时候,我们需要保持跟踪我们拖动过什么窗口,这样当我们释放鼠标时我们知道把项目放到哪个控件上了。

所以我们把它保留到我们的成员变量。

        

  1. // Save current window pointer as the CListCtrl we are dropping onto   
  2.         m_pDropWnd = pDropWnd;  
  3.         // Convert from screen coordinates to drop target client coordinates   
  4.         pDropWnd->ScreenToClient(&pt);  
 

 

IsKindOf(RUNTIME_CLASS(CListCtrl))是另一个常用函数,允许我们判断鼠标移动过的窗口是否是CListCtrl。这节处理鼠标移动过

的项目高亮显示。

 

  1. //If we are hovering over a CListCtrl we need to adjust   
  2.         //the highlights   
  3.         if(pDropWnd->IsKindOf(RUNTIME_CLASS (CListCtrl)))  
  4.         {              
  5.             UINT uFlags;  
  6.             CListCtrl* pList = (CListCtrl*)pDropWnd;  
  7.             // Turn off hilight for previous drop target   
  8.             pList->SetItemState (m_nDropIndex, 0, LVIS_DROPHILITED);  
  9.             // Redraw previous item   
  10.             pList->RedrawItems (m_nDropIndex, m_nDropIndex);  
  11.             // Get the item that is below cursor   
  12.             m_nDropIndex = ((CListCtrl*)pDropWnd)->HitTest(pt, &uFlags);  
  13.             // Highlight it   
  14.             pList->SetItemState(m_nDropIndex, LVIS_DROPHILITED,   
  15.                                            LVIS_DROPHILITED);  
  16.             // Redraw item   
  17.             pList->RedrawItems(m_nDropIndex, m_nDropIndex);  
  18.             pList->UpdateWindow();  
  19.         }  
  20.         // Lock window updates   
  21.         m_pDragImage->DragShowNolock(true);  
  22.     }  
 

 

 

完成拖放,处理放的动作:

我们在此几乎完成了所有动作。再次使用类向导,添加一个WM_LBUTTONUP消息的处理函数。这部分很短很精致。

和上面的OnMouseMove函数一样,我们仅仅处理和拖放相关的代码

  1. if (m_bDragging)  
  2.     {  
 

不要忘记释放捕捉的鼠标。

  1. // Release mouse capture, so that other controls    
  2.                 // can get control/messages   
  3.         ReleaseCapture ();  
 

 

当我们释放了物件,我们不再处理拖动处理。 

  1. // Note that we are NOT in a drag operation   
  2.         m_bDragging = FALSE;  
 

 

 

MFC再次给我们提供了函数组来处理拖动图片。不要忘记删除上边创建的(拖动开始时创建)那个图片。

 

  1. // End dragging image   
  2.         m_pDragImage->DragLeave (GetDesktopWindow ());  
  3.         m_pDragImage->EndDrag ();  
  4.         delete m_pDragImage;   
  5.         //must delete it because it was created at the beginning of the drag  
 

 

跟我们在OnMouseMove函数中处理的那样,我们找出鼠标在哪,以及所处的窗口。然后我们检测是否是一个CListCtrl窗口。

如果是,我们处理实际的放动作。为了容易阅读。我让处理复制和移动的代码打包进DropItemOnList函数中(如下所示)

  1. CPoint pt (point); //Get current mouse coordinates   
  2.         ClientToScreen (&pt); //Convert to screen coordinates   
  3.         // Get the CWnd pointer of the window that is under   
  4.         //the mouse cursor   
  5.         CWnd* pDropWnd = WindowFromPoint (pt);  
  6.         ASSERT (pDropWnd); //make sure we have a window pointer   
  7.         // If window is CListCtrl, we perform the drop   
  8.         if (pDropWnd->IsKindOf (RUNTIME_CLASS (CListCtrl)))  
  9.         {  
  10.             m_pDropList = (CListCtrl*)pDropWnd;   
  11.                         //Set pointer to the list we are dropping on   
  12.             DropItemOnList(m_pDragList, m_pDropList);   
  13.                         //Call routine to perform the actual drop   
  14.         }  
  15.     }  
 

然后进入最后的步骤,实际的复制/移动代码的处理。

 

实际复制/移动代码:

你需要手工添加一个函数来处理实际的复制/移动代码。这个函数可以是protected成员,

 

  1. void DropItemOnList(CListCtrl* pDragList, CListCtrl* pDropList);  
 

 

以下是实际的函数:

  1. void CDragTestDlg::DropItemOnList(CListCtrl* pDragList,  
  2.      CListCtrl* pDropList)  
  3. {  
  4.     //This routine performs the actual drop of the item dragged.   
  5.     //It simply grabs the info from the Drag list (pDragList)   
  6.     // and puts that info into the list dropped on (pDropList).   
  7.     //Send:    pDragList = pointer to CListCtrl we dragged from,   
  8.     //        pDropList = pointer to CListCtrl we are dropping on.   
  9.     //Return: nothing.   
  10.     Variables   
  11.     char szLabel[256];  
  12.     LVITEM lviT;  
  13.     LVITEM* plvitem;  
  14.     lvItem* pItem;  
  15.     lvItem lvi;  
  16.     // Unhilight the drop target   
  17.     pDropList->SetItemState (m_nDropIndex, 0, LVIS_DROPHILITED);  
  18.     //Set up the LV_ITEM for retrieving item from pDragList and adding the    
  19.         //new item to the pDropList   
  20.     ZeroMemory(&lviT, sizeof (LVITEM)); //allocate and clear memory space    
  21.                                             // for LV_ITEM   
  22.     lviT.iItem        = m_nDragIndex;  
  23.     lviT.mask        = LVIF_TEXT;  
  24.     lviT.pszText        = szLabel;  
  25.     lviT.cchTextMax    = 255;  
  26.     lvi.plvi = &lviT;  
  27.     lvi.plvi->iItem = m_nDragIndex;  
  28.     lvi.plvi->mask = LVIF_TEXT;  
  29.     lvi.plvi->pszText = szLabel;  
  30.     lvi.plvi->cchTextMax = 255;  
  31.     if(pDragList->GetSelectedCount() == 1)  
  32.     {  
  33.         // Get item that was dragged   
  34.         pDragList->GetItem (lvi.plvi);  
  35.         lvi.sCol2 = pDragList->GetItemText(lvi.plvi->iItem, 1);  
  36.         // Delete the original item (for Move operation)   
  37.         // This is optional. If you want to implement a Copy    
  38.         // operation, don't delete. This works very well though    
  39.         // for re-arranging items within a CListCtrl. It is written    
  40.         // at present such that when dragging from one list to the    
  41.                 // other the item is copied, but if dragging within one list,   
  42.                 // the item is moved.   
  43.         if(pDragList == pDropList)  
  44.         {  
  45.             pDragList->DeleteItem (m_nDragIndex);  
  46.             if(m_nDragIndex < m_nDropIndex) m_nDropIndex--;   
  47.                         //decrement drop index to account for item   
  48.                         //being deleted above it   
  49.         }  
  50.         // Insert item into pDropList   
  51.         // if m_nDropIndex == -1, iItem = GetItemCount()    
  52.                 //   (inserts at end of list), else iItem = m_nDropIndex   
  53.         lvi.plvi->iItem = (m_nDropIndex == -1) ?   
  54.                                    pDropList->GetItemCount () : m_nDropIndex;  
  55.         pDropList->InsertItem (lvi.plvi);  
  56.         pDropList->SetItemText(lvi.plvi->iItem, 1, (LPCTSTR)lvi.sCol2);  
  57.         // Select the new item we just inserted   
  58.         pDropList->SetItemState (lvi.plvi->iItem, LVIS_SELECTED,   
  59.                                          LVIS_SELECTED);  
  60.     }  
  61.     else //more than 1 item is being dropped   
  62.     {  
  63.         //We have to parse through all of the selected items from the    
  64.                 // DragList   
  65.         //1) Retrieve the info for the items and store them in memory   
  66.         //2) If we are reordering, delete the items from the list   
  67.         //3) Insert the items into the list (either same list or    
  68.                 //   different list)   
  69.         CList<lvItem*, lvItem*> listItems;  
  70.         POSITION listPos;  
  71.         //Retrieve the selected items   
  72.         POSITION pos = pDragList->GetFirstSelectedItemPosition();   
  73.                                                  //iterator for the CListCtrl   
  74.         while(pos) //so long as we have a valid POSITION, we keep    
  75.                            // iterating   
  76.         {  
  77.             plvitem = new LVITEM;  
  78.             ZeroMemory(plvitem, sizeof(LVITEM));  
  79.             pItem = new lvItem;  
  80.             //ZeroMemory(pItem, sizeof(lvItem));    
  81.                         //If you use ZeroMemory on the lvItem struct,    
  82.                         //it creates an error when you try to set sCol2   
  83.             pItem->plvi = plvitem;  
  84.             pItem->plvi->iItem = m_nDragIndex;  
  85.             pItem->plvi->mask = LVIF_TEXT;  
  86.             pItem->plvi->pszText = new char;   
  87.                         //since this is a pointer to the string, we need a    
  88.                         //new pointer to a new string on the heap   
  89.             pItem->plvi->cchTextMax = 255;  
  90.             m_nDragIndex = pDragList->GetNextSelectedItem(pos);  
  91.             //Get the item   
  92.             pItem->plvi->iItem = m_nDragIndex; //set the index in    
  93.                                            //the drag list to the selected item   
  94.             pDragList->GetItem(pItem->plvi); //retrieve the    
  95.                                                          // information   
  96.             pItem->sCol2 = pDragList->GetItemText(  
  97.                                                        pItem->plvi->iItem, 1);  
  98.             //Save the pointer to the new item in our CList   
  99.             listItems.AddTail(pItem);  
  100.         } //EO while(pos) -- at this point we have deleted the moving    
  101.                   // items and stored them in memory   
  102.         if(pDragList == pDropList) //we are reordering the list (moving)   
  103.         {  
  104.             //Delete the selected items   
  105.             pos = pDragList->GetFirstSelectedItemPosition();  
  106.             while(pos)  
  107.             {  
  108.                 pos = pDragList->GetFirstSelectedItemPosition();  
  109.                 m_nDragIndex = pDragList->GetNextSelectedItem(pos);  
  110.                 pDragList->DeleteItem(m_nDragIndex);   
  111.                                 //since we are MOVING, delete the item   
  112.                 if(m_nDragIndex < m_nDropIndex) m_nDropIndex--;   
  113.                                 //must decrement the drop index to account   
  114.                                 //for the deleted items   
  115.             } //EO while(pos)   
  116.         } //EO if(pDragList...   
  117.         //Iterate through the items stored in memory and add them    
  118.                 //back into the CListCtrl at the drop index   
  119.         listPos = listItems.GetHeadPosition();  
  120.         while(listPos)  
  121.         {  
  122.             pItem = listItems.GetNext(listPos);  
  123.             m_nDropIndex = (m_nDropIndex == -1) ?   
  124.                                     pDropList->GetItemCount() : m_nDropIndex;  
  125.             pItem->plvi->iItem = m_nDropIndex;  
  126.             pDropList->InsertItem(pItem->plvi); //add the item   
  127.             pDropList->SetItemText(pItem->plvi->iItem, 1,   
  128.                                                pItem->sCol2);  
  129.             pDropList->SetItemState(pItem->plvi->iItem,   
  130.                                                 LVIS_SELECTED, LVIS_SELECTED);   
  131.                         //highlight/select the item we just added   
  132.             m_nDropIndex++;   
  133.                         //increment the index we are dropping at to keep the    
  134.                         //dropped items in the same order they were in in the    
  135.             //Drag List. If we dont' increment this, the items are     
  136.                         //added in reverse order   
  137.             //Lastly, we need to clean up by deleting our "new"    
  138.                         //variables   
  139.             delete pItem;  
  140.         } //EO while(listPos)   
  141.     }  
  142. }  
 

 

我们简单的设置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并设置该项目为高亮。

 

很简单,不是吗?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值