65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 OLE 拖放列表框项

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.36/5 (8投票s)

2005年12月9日

CPOL

5分钟阅读

viewsIcon

74046

downloadIcon

2796

使用 OLE 拖放重新排列列表框项。

Drag and Drop Listbox

引言

OLE 使拖放操作变得轻而易举。它允许两个不相关的应用程序通过剪贴板格式以它们都能理解的格式交换数据。使用 OLE 进行拖放是一个相对简单的任务。使用 MFC,实际上要做的不多,只需创建适当的对象并调用它们的适当方法。有三个 MFC 类参与拖放操作。这些类是 COleDataSourceCOleDropSourceCOleDropTarget

背景

以下是对每个类功能的简要解释。COleDataSource 是持有将在源到目标之间传输的数据的类。COleDropSource 是一个相对较小的类,可在拖放操作期间提供视觉反馈。最后,COleDropTarget 是处理目标端所有事情的类。实际上,大多数人只会使用 COleDataSourceCOleDropTarget,因为大多数人不需要更改 COleDropSource 的行为,而且它是由 COleDataSource 自动创建的。

拖放操作包括源对象创建一个 COleDataSource,向其附加一些数据,并调用 COleDataSource 对象的 DoDragDrop 方法。目标对象需要实现一个 COleDropTarget 类。COleDropTarget 类有一些在操作期间会被调用的虚拟函数:OnDragEnterOnDragOverOnDragLeaveOnDrop 是最重要的。必须重写这些方法,以便我们能够告诉系统该做什么。

让我们看一分钟源端。在这里,我们将创建一个 COleDataSource 并向其附加一些文本。

void CMyDragAndDropWnd::StartDrag()
{
   //create the COleDataSource, and attach the data to it
   COleDataSource DataSource;
 
   //create a chunck of memory that will hold "This is a test" 15 chars
   HGLOBAL hData = GlobalAlloc(GMEM_MOVEABLE,15);
   char *pChar = (char *)GlobalLock(hData);
   strcpy(pChar,"This is a test");
   GlobalUnlock(hData);
 
   if (hData)
   {
      //attach the data to the COleDataSource Object
      DataSource.CacheGlobalData(CF_TEXT,hData);
 
      //allow the user to drag it.
      DROPEFFECT DropEffect = 
         DataSource.DoDragDrop(GetDragItemEffects(m_DraggedIndex));
 
      //Once DoDragDrop returns we can check the return value stored in DropEffect
      //to see what kind of dropping happened. Like move, copy or shortcut
   }
}

“放置”端的事情比拖动部分要复杂一些。我们必须继承自 COleDropTarget 并重写一些方法。希望接收放置的窗口需要有一个继承类的对象,并使用 Register 方法向其注册。让我们看看继承自 COleDropTarget 的简单类是什么样的。

//this method is called when the Drag operation first enters the window
//attached to this COleDropTarget object
DROPEFFECT CMyDropTarget::OnDragEnter(CWnd* pWnd, 
           COleDataObject* pDataObject, 
           DWORD dwKeyState,CPoint point)
{
   //if the data is the kind we want
   if (pDataObject->IsDataAvailable(CF_TEXT))
   {
      //here we can take bring window to top
      //since what is attached to the COleDropTarget object is
      //most likely a child window, you would probably have to 
      //send it a message so that it can activate it's parent 
      //or something.
      SendMessage(m_hWnd,...,...,...);

      //we can handle copy and move of this data
      return DROPEFFECT_COPY|DROPEFFECT_MOVE;
   }
   //we can't handle this type of data
   return DROPEFFECT_NONE;
}

DROPEFFECT CMyDropTarget::OnDragOver(CWnd* pWnd, 
           COleDataObject* pDataObject, 
           DWORD dwKeyState,CPoint point)
{
   //if the data is the kind we want
   if (pDataObject->IsDataAvailable(CF_TEXT))
   {
      //we can handle copy and move of this data
      return DROPEFFECT_COPY|DROPEFFECT_MOVE;
   }
   
   //we can't handle this type of data
   return DROPEFFECT_NONE;
}

void CMyDropTarget::OnDragLeave(CWnd* pWnd)
{
   //we can use this method to do any kind of clean up
   //in this case we don't have anything
}

BOOL CMyDropTarget::OnDrop(CWnd* pWnd, 
                    COleDataObject* pDataObject, 
                    DROPEFFECT dropEffect,CPoint point)
{
   BOOL Ret = FALSE;
   
   //if it is a format that we handle
   if (pDataObject->IsDataAvailable(CF_TEXT))
   {
      //grab the data 
      HGLOBAL hGlobal = pDataObject->GetGlobalData(m_cfFormat);
   
      //Do something with the data
      //here you would normally send a message back to the 
      //window registered to the COleDropTarget to tell it
      //to do something with the data
      SendMessage(m_hWnd,WM_DOSOMETHING,hGlobal,0);
   
      Ret = TRUE;
   }
   
   return Ret;
}

就是这样。现在,让我们来看看我的拖放列表框示例。

工作原理

这个列表框类演示了如何重新排列列表框项,以及利用 OLE 的拖放功能在两个列表框之间拖放项。概念非常简单:获取用户想要拖动的项,创建一个 COleDataSource 对象,向其附加数据,然后调用 COleDataSource.DoDragDrop()。在接收端,通过重写 COleDropTargetOnDragOver 方法来指示当用户拖动项时放置的位置,最后在用户释放鼠标按钮(调用 COleDropTargetOnDrop 方法)时将项插入其新位置。

为了完成这项任务,我创建了一个新类 COleDragAndDropListBox,它继承自 CListBoxCOleDropTarget。我更喜欢这种方法而不是组合方法,因为这样,派生自 COleDropTarget 的类不必向派生自 CListBox 的类发送消息来获取信息和通知放置操作。(多重继承部分不允许在 VC++ 6.0 及更早版本中使用,因此这仅在编译使用 VC++ 7.0 编译器时才有效。)

让我们从任务的拖动部分开始。我们需要捕获您的列表框窗口的三个消息来实现此目的,分别是 WM_LBUTTONDOWNWM_MOUSEMOVEWM_LBUTTONUP

WM_LBUTTONDOWN 处理程序方法简单地确定用户选择了哪个项。

void COLEDragAndDropListBox::OnLButtonDown(UINT nFlags, CPoint point)
{
   __super::OnLButtonDown(nFlags, point);

   //keep track of the item that was clicked on
   //WM_MOUSEMOVE message is going to use that to 
   //create the COleDataSource
   m_Interval = 0;
   m_DropIndex = LB_ERR;
   m_DraggedIndex = LB_ERR;

   BOOL Outside;
   int Index = ItemFromPoint(point,Outside);
   if (Index != LB_ERR && !Outside)
   {
      m_DraggedIndex = Index;
      SetCurSel(Index);
   }
}

WM_WMMOUSEMOVE 处理程序的唯一任务是创建一个 COleDataSource,向其附加数据,并调用 DoDragDrop。由于并非所有人都会简单地将文本从一个列表框传输到另一个列表框,因此 CDragAndDropListBox 类定义了一些虚拟函数来获取可用的放置模式、获取数据以及放置项。当发生需要某项内容的情况时,将使用这些虚拟函数中的一个来获取所需信息。OnMouseMove 是第一个使用这些虚拟函数的方法。它调用 GetData() 来获取应附加到 COleDataSource 的数据,之后调用 DoDragDrop(),如果操作是移动操作,它会调用 RemoveItem() 来删除该项。

void COLEDragAndDropListBox::OnMouseMove(UINT nFlags, CPoint point)
{
   if (m_DraggedIndex != LB_ERR && (nFlags & MK_LBUTTON))
   {
      //create the COleDataSource, and attach the data to it
      COleDataSource DataSource;
      HGLOBAL hData = GetData(m_DraggedIndex);
      if (hData)
      {
         //attach the data
         DataSource.CacheGlobalData(m_cfFormat,hData);
   
         //allow the user to drag it.
         DROPEFFECT DropEffect = 
                DataSource.DoDragDrop(GetDragItemEffects(m_DraggedIndex));
   
         //if the user wanted to move the item then delete it
         //Only do this if it was dragged to another window
         //OnDrop handles deleting a moved item within the same window
         if (DropEffect & DROPEFFECT_MOVE && m_DraggedIndex != LB_ERR)
         {
            RemoveItem(m_DraggedIndex);
         }
         m_DraggedIndex = LB_ERR;
         GetParent()->SendMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(), 
                                  LBN_SELCHANGE),(LPARAM)CListBox::m_hWnd);
      }
   }
   
   __super::OnMouseMove(nFlags, point);
}

WM_LBUTTONUP 消息处理程序的任务非常简单。它只需将所有变量重置为其初始状态,以供下一次拖动操作使用。

void COLEDragAndDropListBox::OnLButtonUp(UINT nFlags, CPoint point)
{
   //cancel everything
   KillTimer(TID_SCROLLDOWN);
   KillTimer(TID_SCROLLUP);
   m_Interval = 0;
   m_DropIndex = LB_ERR;
   Invalidate();

   __super::OnLButtonUp(nFlags, point);
}

COLEDragAndDropListBox 继承的 COleDropTarget 类帮助我们处理放置端的事宜。此类有四个我们感兴趣的虚拟函数:OnDragEnterOnDragOverOnDragLeaveOnDrop。我们对 OnDragEnterOnDragOver 的实现几乎相同。唯一的区别是 OnDragEnter 通过调用 ActivateWindow 虚拟方法为类提供了激活窗口的机会。

DROPEFFECT COLEDragAndDropListBox::OnDragEnter(CWnd* pWnd, 
                COleDataObject* pDataObject,DWORD dwKeyState, CPoint point)
{
   if (DragOriginateInSameWindow() && !GetCanInternalDrop())
   {
      return DROPEFFECT_NONE;
   }
   //if the data is the kind we want
   if (pDataObject->IsDataAvailable(m_cfFormat))
   {
      //bring window to top
      ActivateWindow();
      //draw the line where it would be inserted
      DrawTheLines(GetItemAt(point));
      //scroll if needed
      DoTheScrolling(point);
      //return how the user can drop the item
      return GetDropItemEffects(pDataObject,dwKeyState);
   }
   return DROPEFFECT_NONE;
}

DROPEFFECT COLEDragAndDropListBox::OnDragOver(CWnd* pWnd, 
           COleDataObject* pDataObject,DWORD dwKeyState,CPoint point)
{
   if (DragOriginateInSameWindow() && !GetCanInternalDrop())
   {
      return DROPEFFECT_NONE;
   }
   //if the data is the kind we want
   if (pDataObject->IsDataAvailable(m_cfFormat))
   {
      //draw the line where it would be inserted
      DrawTheLines(GetItemAt(point));
      //scroll if needed
      DoTheScrolling(point);
      //return how the user can drop the item
      return GetDropItemEffects(pDataObject,dwKeyState);
   }
   return DROPEFFECT_NONE;
}

OnDragLeave 方法甚至更简单。它所做的只是进行一些清理工作。由于 OnDragOver 方法在列表框上绘制了一条线来指示插入点,因此在鼠标在拖动操作期间离开窗口的情况下,OnDragLeave 将负责擦除这条线。

void COLEDragAndDropListBox::OnDragLeave(CWnd* pWnd)
{
   //cancel everything
   KillTimer(TID_SCROLLDOWN);
   KillTimer(TID_SCROLLUP);
   //Clear The line that was drawn
   CDC *pDC = GetDC(); 
   ClearOldLine(pDC,m_DropIndex);
   ReleaseDC(pDC);
   m_Interval = 0;
   m_DropIndex = LB_ERR;
}

OnDrop 方法是此类中最 umfangreiche 的方法。顾名思义,当用户在窗口上释放鼠标按钮时会调用它。通常,这会非常简单,但由于用户可以在源拖放操作的同一窗口内放置,因此我们必须处理一些特殊情况。特殊情况是用户想将项从一个索引移动到另一个索引。这可能很棘手,因为当我们向列表框添加或删除项时,索引会发生变化,我们很容易丢失需要插入或删除项的索引。

BOOL COLEDragAndDropListBox::OnDrop(CWnd* pWnd, 
          COleDataObject* pDataObject,DROPEFFECT dropEffect,CPoint point)
{
   BOOL Ret = FALSE;
   if (DragOriginateInSameWindow() && !GetCanInternalDrop())
   {
      return FALSE;
   }
   //if it is a format that we handle
   if (pDataObject->IsDataAvailable(m_cfFormat))
   {
      //get the index of where the user want's to insert item
      int Index = GetItemAt(point);
      HGLOBAL hGlobal = pDataObject->GetGlobalData(m_cfFormat);

      //if the Drag was initiated in the same window as the drop and
      //user wants to move the item then we have to handle the delete here
      //because if the original item was at a higher index than the dropped index
      //we would have to delete the dragged item first before we
      //insert the new item
      if (DragOriginateInSameWindow() && dropEffect == DROPEFFECT_MOVE)
      {
         if (m_DraggedIndex < Index)
         {
            Ret = DroppedAt(Index,hGlobal);
            RemoveItem(m_DraggedIndex);
            m_DraggedIndex = LB_ERR;
            SetCurSel(Index-1);
         }
         else if (m_DraggedIndex > Index)
         {
            RemoveItem(m_DraggedIndex);
            m_DraggedIndex = LB_ERR;
            Ret = DroppedAt(Index,hGlobal);
         }
      }
      else //simply drop the item in the desired index
      {
         Ret = DroppedAt(Index,hGlobal);
         GetParent()->SendMessage(WM_COMMAND, 
               MAKEWPARAM(GetDlgCtrlID(),LBN_SELCHANGE),
               (LPARAM)CListBox::m_hWnd);
      }
   }
   m_DropIndex = LB_ERR;
   return Ret;
}

使用代码

COLEDragAndDropListBox 类有两种使用方式。如果您只想将文本从一个列表框移动到另一个列表框,那么请将您的变量类型从 CListBox 更改为 COLEDragAndDropListBox

// CDragDropListBoxSampleDlg dialog
class CDragDropListBoxSampleDlg : public CDialog
{
    ......

    // Implementation
    protected:
       COLEDragAndDropListBox  m_ListBox;
    
    ......
    };

但是,如果您想发送比纯文本更多信息,那么您将需要创建一个继承自 COLEDragAndDropListBox 的类,并重写几个虚拟方法来为其提供数据并处理项的插入和删除。

// CDragDropListBoxSampleDlg dialog
class CMyDragAndDropListBox : public COLEDragAndDropListBox
{
   ......
protected: //overridables

   //override to supply data to attach to COleDataSource
   virtual HGLOBAL GetData(int ForIndex);

   //override to handle the item insertion
   virtual BOOL DroppedAt(int InsertBefore,HGLOBAL hGlobal);

   //override to so that you can specify 
   //if the DROPPED item can be moved or copied or both
   virtual DROPEFFECT GetDropItemEffects(COleDataObject* 
                          pDataObject,DWORD dwKeyState);

   //override to so that you can specify 
   //if the DRAGGED item can be moved or copied or both
   virtual DROPEFFECT GetDragItemEffects(int Index);

   //override to handle item deleting when an item is moved
   virtual void RemoveItem(int Index);

   //you can override this method to return 
   //the index of the item that is under the point
   //the default implementation depends 
   //on the Outside variable passed to ItemFromPoint
   //but that does not work in all cases
   virtual int GetItemAt(CPoint Point);

   //this function gets called from the OnDragOver method.
   //the default implementation tries to active the parent frame first,
   //if there is no parent frame then it activates the parent window
   virtual void ActivateWindow();

   ......
};
© . All rights reserved.