控件"树"中多选拖放功能的实现

概述:
控件“树”(tree)能够清晰地显示所包含数据的继承关系,是一个
强有力的控件。但是真正掌握并能运用CTreeCtrl类的众多特性并不是一
件容易的事情。在Visual C++ Developer杂志的9月份、10月份这两期中,
Stephen介绍了两个类CBitmapTree和CCheckableTree的用法,这两个类可
以扩展和简化类CTreeCtrl。在这篇文章里,将主要介绍类CBitmapTree的
升级版,它可以简化往"树"控件中添加拖放功能的过程,而且还可以实现
多选拖放功能。
在CTreeCtrl类中,拖放特性是很难掌握和应用的特性之一。由于该
特性能给拖放操作带来许多特有的灵活性,所以MFC组专门指派程序员负
责这方面函数的编写工作,这将使得用户在开发的应用程序中实现控件
“树”的拖放功能不再“望而生畏”。这里将介绍类CBitmapTree中扩充
的有关成员函数,以便简化控件"树"的拖放功能的实现,同时还将该特
性扩展到多选拖放。
可拖动的项和释放的目标位置(Draggable items and drop targets)
拖放操作主要包含两个行为对象:被拖动的项和释放的目标位置项。
为了支持可反复进行的拖放操作功能,需要能够识别出树控件类中哪些项
是可拖动的以及它们的释放位置如何,而不是去识别父窗口类中的项。通
过一个成员变量m_draggable中某一特定位值的设置可以设置某项的拖动
特性,就象一个树的图象可以设置成"可扩充"特性一样。函数
SetDraggable()、IsDraggable()、SetDropTarget()和IsDropTarget()中
就使用了该成员变量m_draggable。下面是函数SetDraggable()的代码:
void CBitmapTree::SetDraggable(int base)
{
 ASSERT(base < (sizeof(m_draggable)*8 -1));
 m_draggable|= (1UL << base);
}
熟悉类CBitmapTree的读者可能注意到了该函数与SetExpandable()函
数极其相似。变量base指明了所选的图象在"树"控件TVISL_NORMAL图象列
表中的索引号。该索引号将转换成一个单位值,并且存储在成员变量
m_draggable中。m_draggable成员变量类型为长整型,占4个字节32位,
所以变量base指定的是位于图象列表中前32个图象中的某一个图象。而成
员函数IsDraggable()可以用来查询变量m_draggable的值。成员函数
IsDraggable()和IsDropTarget()都定义为虚函数,所以在需要对可拖动
特性和可释放特性进行更为复杂的判断时,用户可以在类CBitmapTree的
派生类中超越(overriding)该函数。网址www.pinpub.com/vcd
Subscriber Downloads部分中所包含的程序示例中,所有的文档节点都是
可拖动的,所有的文件夹节点都可以作为拖动后释放的目标位置项。
准备拖动(Getting ready to drag)
当成功地执行完一次拖放操作后,函数SetDrag()就会建立一个回调
函数来处理树项的移动或复制。笔者曾经在前面的文章里提供了一个比较
简单的回调函数MoveTreeItemCB(),它可以完成树项在“树”控件中从一
个位置移动到另一个位置。注意:这个函数本身的局限性很强,如果想完
成某些该函数不具备的其它方面的功能时,就需要用户根据实际需要编写
自己的回调函数。(例如,当使用宏LPSTR_TEXTCALLBACK来替代表示树项
的文本时,函数MoveTreeItemCB()就不能正常运行。)
另外,在拖动树项时,可以提供给函数SetDrag()一个位图的资源标
识符(并不是必需的)。该位图可以作为一种提示图标。当拖动超出了有
效范围的边界时,则显示第一个图象;当拖动项只有一项时,则显示第二
个图象;当拖动项包含多项时,则显示第三个图象。如果在程序中并没有
提供图象给函数SetDrag(),那么将显示一个缺省的拖动图象,即当前所
选项的灰色显示。
开始拖动(Beginning a drag)
拖动操作开始时,“树”控件的父窗口将接收到一个通知消息
TVN_BEGINDRAG。您可以向CBitmapTree类的消息映象中发送一通知消息
ON_NOTIFY_REFLECT_EX,以此来截获通知消息TVN_BEGINDRAG,并将其同
时发送给父窗口和“树”控件,下面就是TVN_BEGINDRAG的消息处理函数:
BOOL CBitmapTree::OnBeginDrag(NMHDR* pNMHDR,
         LRESULT* pResult)
{
 NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
 HTREEITEM h= pNMTreeView->itemNew.hItem;
 int img, simg;
 CPoint point;
 if ((m_dragFunction == NULL) ||
  (h == NULL))
  return(FALSE);
 ASSERT(GetNormalImageCount());
 if (m_multiSelected)
 {
  // 关闭不可拖动项的选择操作
  DoForAllSelected(PrepareDrag, NULL, 0);
 }
 else
 {
  // 如果已经开始拖动一个"非选择项",
  // 则需要在拖动前改变该项的拖动特性
  SelectItem(h);
 }
 // 能拖动此项吗?
 GetItemImage(h, img, simg);
 if (!(IsDraggable(h, img)))
  return(FALSE);
 // 设置拖动图象列表
 if (m_ilDrag.m_hImageList != NULL)
  m_dragList= &m_ilDrag;
 else
 {
  m_dragList= CreateDragImage(h);
  if (m_dragList == NULL)
   return(FALSE);
 }
 if (m_multiSelected)
  m_dragList->BeginDrag(m_multiDrag, CPoint(0,0));
 else
  m_dragList->BeginDrag(m_docDrag, CPoint(0,0));
 MapWindowPoints(NULL, &pNMTreeView->ptDrag, 1);
 m_dragList->DragEnter(NULL, pNMTreeView->ptDrag);
 ShowCursor(FALSE);
 SetCapture();
// 允许边拖动边滚动"树"控件窗口
SetTimer(EVENT_DRAG_SCROLL, 250, NULL);
 *pResult= 0;
 return(FALSE);
}
当调用OnBeginDrag()函数时,首先判断是否为多节点拖动,当
m_multiSelected值为真,即为多节点拖动时,将调用PrepareDrag()函数
来关闭那些未声明为"可拖动"项的选择操作。然后再调用函数
IsDraggable()检验一下当前所选的项是否都是可拖动项,如果有不可拖
动项,则函数返回FALSE,否则,继续下面的语句,初始化拖动操作过程
中显示的图象列表,调用函数BeginDrag()。MSDN文档中解释说函数
BeginDrag()中的第二个参量为“起始拖动位置的坐标(典型情况下为光标
位置)”。文档中建议第二个参量使用光标的当前位置坐标,并且将其存
储在pNMTreeView->ptDrag中,事实上,这也正是程序示例MFCTREE中所采
用的方法。但是,最近的MSDN应用程序示例CMNCTRLS和TREESCR中使用的
是(0,0),上面的代码段中也使用(0,0)。OnBeginDrag()函数的后
一部分调用了函数DragEnter()来初始化拖动操作。在调用完函数
DragEnter()以后,如果想刷新控件"树",那么就需要首先调用DragLeave()
函数。
拖动控件"树"中的项(Dragging a tree item)
在执行拖动操作的过程中,拖动图象根据操作的不同,显示上也有些
变化。下面的处理程序OnMouseMove()就能够完成这些显示上的变化:
void CBitmapTree::OnMouseMove(UINT nFlags,
        CPoint point)
{
 if (m_dragList)
 {
  BOOL target_set = FALSE;
  BOOL has_drag_images
     = (m_ilDrag.m_hImageList != NULL);
  HTREEITEM target;
  CPoint screen= point;
  MapWindowPoints(NULL, &screen, 1);
  // 鼠标所在的位置是哪一项?
  if ((target= GetItemUnderMouse()) != NULL)
  {
   int img, simg;
   if (has_drag_images)
   {
    if (m_multiSelected)
     m_dragList->SetDragCursorImage(m_multiDrag, CPoint(0,0));
    else
     m_dragList->SetDragCursorImage(m_docDrag, CPoint(0,0));
   }
   while (target)
   {
    GetItemImage(target, img, simg);
    if (IsDropTarget(target, img))
    {
     m_dragList->DragLeave(NULL);
     SelectDropTarget(target);
     m_dragList->DragEnter(NULL, screen);
     target_set= TRUE;
     break;
    }
    target= GetParentItem(target);
   }
  }
  else
  {
   // 超出有效区域
   if (has_drag_images)
    m_dragList->SetDragCursorImage(m_xDrag, CPoint(0,0));
  }
  if (!(target_set))
  {
   m_dragList->DragLeave(NULL);
   SelectDropTarget(NULL);
   m_dragList->DragEnter(NULL, screen);
  }
 }
 CTreeCtrl::OnMouseMove(nFlags, point);
}
首先,如果在拖放操作的有效区域之外(如控件“树”窗口外或控件
“树”窗口内的空白区)释放了鼠标左键,那么显示的拖动图象将表明
“无任何操作”。当鼠标移动到拖放操作的有效区域内,拖动图象要恢复
为正常状态。这些拖动图象的改变是通过把相应的图象传递给函数
CImageList::SetDragCursorImage()来完成的。如果调用函数SetDrag()
时没有图象传递给该函数,则has_drag_images值为假,拖动图象就不会
有任何改变。
在执行拖动操作的过程中,相应的释放目标项为突出显示。如果当前
鼠标位置所在的项为释放的目标位置项,则通过调用
CTreeCtrl::SelectDropTarget()函数就可以使该项突出显示,否则,最
近的父文件夹突出显示,如果父文件夹并不是释放的目标文件夹,那么任
何项都不突出显示。
被拖动项含有一个点线画成的边框,释放的目标文件夹则反相显示。
边拖边滚
当拖动操作位于“树”控件的有效边界时,如果能够实现“树”控件
窗口的自动上滚或下滚以显示其余的树项,那将是件很漂亮的工作。使用
OnTimer()消息处理函数就可以完成这项工作。函数OnBeginDrag()中调用
了SetTimer()函数,该函数中设置了定时器事件(timer event)
EVENT_DRAG_SCROLL,该定时器事件只有在调用OnLButtonUp消息处理函数
时才被释放。
OnTimer()消息处理函数可以实现当鼠标移到控件“树”窗口边界时,
控件“树”窗口的上滚或下滚操作。在MSDN的程序示例TREESCR中,应用
了此函数,它可以调整窗口相对与鼠标位置的滚动速度。
拖动结束(When the drag is over)
当释放鼠标左键时,拖动操作结束。下面的OnLButtonUp()消息处理
函数可以完成拖动结束时的各种事后处理工作:
void CBitmapTree::OnLButtonUp(UINT nFlags,
        CPoint point)
{
 if (m_dragList)
 {
  HTREEITEM target;
  KillTimer(EVENT_DRAG_SCROLL);
  ReleaseCapture();
  m_dragList->EndDrag();
  ShowCursor(TRUE);
  m_dragList= NULL;
  if ((target= GetDropHilightItem()) != NULL)
  {
    DoForAllSelected(m_dragFunction, NULL,
           (LPARAM) target);
   SelectDropTarget(NULL);
   SelectItem(target);
  }

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值