VC树型控件拖动的完美实现

2016-02-19 21:42 64 1 收藏

岁数大了,QQ也不闪了,微信也不响了,电话也不来了,但是图老师依旧坚持为大家推荐最精彩的内容,下面为大家精心准备的VC树型控件拖动的完美实现,希望大家看完后能赶快学习起来。

【 tulaoshi.com - 编程语言 】

  树型控件用来显示具有一定层次结构的数据项时方便、直观,被广泛地应用在各种软件中,如资源管理器中的磁盘目录就用的是树型

  控件,我们在编程中也会经常用到,但 MFC 中提供的 CTreeCtrl 类并不直接支持拖动节点等高级特性,这使我们程序员编程时有很大限制,又给软件用户带来了一些不便。下面就让我们自己动手来解决这个问题,实现树型控件中节点的拖动。

我们从 CTreeCtrl 中派生了一个类 CXTreeCtrl ,它具有如下的特点:

⑴ 基本拖动的实现。

(本文来源于图老师网站,更多请访问http://www.tulaoshi.com/bianchengyuyan/)

⑵ 处理无意拖动。

  ⑶ 能处理拖动过程中的滚动问题。

⑷ 拖动过程中节点会智能展开。

图 1 为示例程序的运行界面。

(图 1)

好,我们来一步一步实现上述功能。

新建一对话框工程,编辑资源,在对话框中加入一树型控件 IDC_TREE ,属性设置如图 2,给该控件添加一个成员变量 m_wndTree ,

类型改为CXTreeCtrl。从 CTreeCtrl 中派生一个类 CXTreeCtrl 。

(图 2)

  1、基本拖动的实现

当我们要拖动一个项目时,树型视图控件会给它的父窗口发送TVN_BEGINDRAG通知消息。可以在此处创建表示项目处在拖动操作中

的图象,调用 CreateDragImage 函数产生一副缺省的图象,该函数创建的图象由条目图象和标签文本组成。创建了拖动图象后,调用

BeginDrag 函数指定拖动图象的热点位置,然后调用 DragEnter 函数显示拖动图象。接下来处理 WM_MOUSEMOVE 消息用于更新拖动图

  象,我们想让移动中的图象经过某些项目时高亮度显示,这可以调用 SelectDropTarget 来实现。在调用 SelectDropTarget 前,我们先调用

  DragShowNolock ( false ) 来隐藏图象列表,之后再调用 DragShowNolock ( true ) 来恢复图象列表的显示,这样就不会在拖动过程中留下难

  看的轨迹。最后我们处理 WM_LBUTTONUP 消息用于完成拖动操作,在给消息中,我们需要完成结束拖动图想的显示、删除拖动图象、释

  放鼠标、节点的拷贝/删除等操作。在节点的拷贝/删除操作中,如果是父节点拖到子节点上,我们可以先将父节点拷到根结点下的临时节点

(本文来源于图老师网站,更多请访问http://www.tulaoshi.com/bianchengyuyan/)

  中,再从临时结点处拷到子节点,然后将根结点下的临时节点删除,这样做的目的是防止产生异常。本文发表于http://bianceng.cn(编程入门网)

  2、处理无意拖动

  大家可能都有过这样的经历:在鼠标按下时不小心移动了鼠标,这时系统就认为产生了一个移动操作。如果我们不针对这种情况加以解

  决的话,就很容易产生误操作。下面我们就提出一个解决方法:设置时间延迟。也就是说当用户按下鼠标后必须在原位置停留一段时间,才

  能激活拖动操作。

  3、处理拖动过程中的滚动问题

  当我们进行拖动时,如果目的节点不可见,则需要拖动滚动条或收拢其它一些节点以使得目的节点显示出来,无疑,这会给我们带来很

  大的不便。下面我们就来给树型控件添加自动滚动支持。

  设置一个定时器,在 WM_TIMER 消息中检测鼠标的位置,如果靠近树型控件的下边缘,则使得控件向下滚动。靠近上边缘则向上滚动。

  滚动速度根据鼠标的位置确定。

  4、拖动过程中节点的智能展开

  这一步我们要实现的功能是在拖动过程中当鼠标停留在某个节点上一段时间后,该节点会自动展开。

  设置一个定时器,当鼠标在拖动过程中停止在某个节点上时,定时器被启动,再设置一变量保存当前的鼠标位置。

  下面是实现的源代码

// XTreeCtrl.h
……
protected:
   UINT     m_TimerTicks;   //处理滚动的定时器所经过的时间
   UINT     m_nScrollTimerID; //处理滚动的定时器
   CPoint    m_HoverPoint;   //鼠标位置
   UINT     m_nHoverTimerID;  //鼠标敏感定时器
   DWORD     m_dwDragStart;   //按下鼠标左键那一刻的时间
   BOOL     m_bDragging;    //标识是否正在拖动过程中
   CImageList*  m_pDragImage;   //拖动时显示的图象列表
   HTREEITEM   m_hItemDragS;   //被拖动的标签
   HTREEITEM   m_hItemDragD;   //接受拖动的标签
……
// XTreeCtrl.cpp
……
#define  DRAG_DELAY  60
……
void CXTreeCtrl::OnBegindrag(NMHDR* pNMHDR, LRESULT* pResult)
{
   NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
   *pResult = 0;
   //如果是无意拖动,则放弃操作
   if( (GetTickCount() - m_dwDragStart) DRAG_DELAY )
     return;
   m_hItemDragS = pNMTreeView-itemNew.hItem;
   m_hItemDragD = NULL;
   //得到用于拖动时显示的图象列表
   m_pDragImage = CreateDragImage( m_hItemDragS );
   if( !m_pDragImage )
     return;
   m_bDragging = true;
   m_pDragImage-BeginDrag ( 0,CPoint(8,8) );
   CPoint pt = pNMTreeView-ptDrag;
   ClientToScreen( &pt );
   m_pDragImage-DragEnter ( this,pt ); //"this"将拖动操作限制在该窗口
   SetCapture();
   m_nScrollTimerID = SetTimer( 2,40,NULL );
}
void CXTreeCtrl::OnMouseMove(UINT nFlags, CPoint point)
{
   HTREEITEM hItem;
   UINT    flags;
   //检测鼠标敏感定时器是否存在,如果存在则删除,删除后再定时
   if( m_nHoverTimerID )
   {
     KillTimer( m_nHoverTimerID );
     m_nHoverTimerID = 0;
   }
   m_nHoverTimerID = SetTimer( 1,800,NULL ); //定时为 0.8 秒则自动展开
   m_HoverPoint = point;
   if( m_bDragging )
   {
     CPoint pt = point;
     CImageList::DragMove( pt );
     //鼠标经过时高亮显示
     CImageList::DragShowNolock( false ); //避免鼠标经过时留下难看的痕迹
     if( (hItem = HitTest(point,&flags)) != NULL )
     {
       SelectDropTarget( hItem );
       m_hItemDragD = hItem;
     }
     CImageList::DragShowNolock( true );
     //当条目被拖曳到左边缘时,将条目放在根下
     CRect rect;
     GetClientRect( &rect );
     if( point.x rect.left + 20 )
       m_hItemDragD = NULL;
   }
   CTreeCtrl::OnMouseMove(nFlags, point);
}
void CXTreeCtrl::OnLButtonUp(UINT nFlags, CPoint point)
{
   CTreeCtrl::OnLButtonUp(nFlags, point);
   if( m_bDragging )
   {
     m_bDragging = FALSE;
     CImageList::DragLeave( this );
     CImageList::EndDrag();
     ReleaseCapture();
     delete m_pDragImage;
     SelectDropTarget( NULL );
    
     if( m_hItemDragS == m_hItemDragD )
     {
       KillTimer( m_nScrollTimerID );
       return;
     }
     Expand( m_hItemDragD,TVE_EXPAND );
     HTREEITEM htiParent = m_hItemDragD;
     //如果是由父节点拖向子节点
     while( (htiParent = GetParentItem(htiParent)) != NULL )
     {
       if( htiParent == m_hItemDragS )
       {
         //建立一个临时节点以完成操作
         HTREEITEM htiNewTemp = CopyBranch( m_hItemDragS,NULL,TVI_LAST );
         HTREEITEM htiNew = CopyBranch( htiNewTemp,m_hItemDragD,TVI_LAST );
         DeleteItem( htiNewTemp );
         SelectItem( htiNew );
         KillTimer( m_nScrollTimerID );
         return;
       }
     }
     HTREEITEM htiNew = CopyBranch( m_hItemDragS,m_hItemDragD,TVI_LAST );
     DeleteItem( m_hItemDragS );
     SelectItem( htiNew );
     KillTimer( m_nScrollTimerID );
   }
}

//拷贝条目
HTREEITEM CXTreeCtrl::CopyItem(HTREEITEM hItem, HTREEITEM htiNewParent, HTREEITEM htiAfter)
{
   TV_INSERTSTRUCT tvstruct;
   HTREEITEM    hNewItem;
   CString     sText;
   //得到源条目的信息
   tvstruct.item.hItem = hItem;
   tvstruct.item.mask = TVIF_CHILDREN|TVIF_HANDLE|TVIF_IMAGE|TVIF_SELECTEDIMAGE;
   GetItem( &tvstruct.item );
   sText = GetItemText( hItem );
   tvstruct.item.cchTextMax = sText.GetLength ();
   tvstruct.item.pszText  = sText.LockBuffer ();
   //将条目插入到合适的位置
   tvstruct.hParent     = htiNewParent;
   tvstruct.hInsertAfter  = htiAfter;
   tvstruct.item.mask    = TVIF_IMAGE|TVIF_SELECTEDIMAGE|TVIF_TEXT;
   hNewItem = InsertItem( &tvstruct );
   sText.ReleaseBuffer ();
   //限制拷贝条目数据和条目状态
   SetItemData( hNewItem,GetItemData(hItem) );
   SetItemState( hNewItem,GetItemState(hItem,TVIS_STATEIMAGEMASK),TVIS_STATEIMAGEMASK);
   return hNewItem;
}
//拷贝分支
HTREEITEM CXTreeCtrl::CopyBranch(HTREEITEM htiBranch, HTREEITEM htiNewParent, HTREEITEM htiAfter)
{
   HTREEITEM hChild;
   HTREEITEM hNewItem = CopyItem( htiBranch,htiNewParent,htiAfter );
   hChild = GetChildItem( htiBranch );
   while( hChild != NULL )
   {
     CopyBranch( hChild,hNewItem,htiAfter );
     hChild = GetNextSiblingItem( hChild );
   }
   return hNewItem;
}
void CXTreeCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
   //处理无意拖曳
   m_dwDragStart = GetTickCount();
  
   CTreeCtrl::OnLButtonDown(nFlags, point);
}
void CXTreeCtrl::OnTimer(UINT nIDEvent)
{
   //鼠标敏感节点
   if( nIDEvent == m_nHoverTimerID )
   {
     KillTimer( m_nHoverTimerID );
     m_nHoverTimerID = 0;
     HTREEITEM trItem = 0;
     UINT uFlag = 0;
     trItem = HitTest( m_HoverPoint,&uFlag );
     if( trItem && m_bDragging )
     {
       SelectItem( trItem );
       Expand( trItem,TVE_EXPAND );
     }
   }
   //处理拖曳过程中的滚动问题
   else if( nIDEvent == m_nScrollTimerID )
   {
     m_TimerTicks++;
     CPoint pt;
     GetCursorPos( &pt );
     CRect rect;
     GetClientRect( &rect );
     ClientToScreen( &rect );
     HTREEITEM hItem = GetFirstVisibleItem();
    
     if( pt.y rect.top +10 )
     {
       //向上滚动
       int slowscroll = 6 - (rect.top + 10 - pt.y )/20;
       if( 0 == (m_TimerTicks % ((slowscroll 0) ? slowscroll : 1)) )
       {
         CImageList::DragShowNolock ( false );
         SendMessage( WM_VSCROLL,SB_LINEUP );
         SelectDropTarget( hItem );
         m_hItemDragD = hItem;
         CImageList::DragShowNolock ( true );
       }
     }
     else if( pt.y rect.bottom - 10 )
     {
       //向下滚动
       int slowscroll = 6 - (pt.y - rect.bottom + 10)/20;
       if( 0 == (m_TimerTicks % ((slowscroll 0) ? slowscroll : 1)) )
       {
         CImageList::DragShowNolock ( false );
         SendMessage( WM_VSCROLL,SB_LINEDOWN );
         int nCount = GetVisibleCount();
         for( int i=0 ; inCount-1 ; i++ )
           hItem = GetNextVisibleItem( hItem );
         if( hItem )
           SelectDropTarget( hItem );
         m_hItemDragD = hItem;
         CImageList::DragShowNolock ( true );
       }
     }
   }
   else
     CTreeCtrl::OnTimer(nIDEvent);
}

  通过上面的代码我们就实现了树型控件的拖动操作。

  示例程序在 VC6+Windows2000 上调试通过。

来源:http://www.tulaoshi.com/n/20160219/1626907.html

延伸阅读
标签: Java JAVA基础
  实现论坛树型结构的算法很多,我现在的JSP论坛采用的也是当中的一种:不用递归实现树型结构的算法,现在我将论坛树型结构的具体算法和大家介绍一下,和大家一起交流。   1、演示表的结构: 表名:mybbslist 字段 数据类型 说明 BBSID 自动编号 RootID Int ...
标签: PHP
无限分类与树型论坛的实现方法 ――浮点型字段排序法 Joe Teng 2005.6.12 在此我不想讨论其他实现方法的利与弊。 既然是使用字段排序,那么我们便设一个名为order的字段。问题是,在这里是使用整数还是使用浮点数类型呢?考虑到会有在两个连续order值中间插入新值的可能,自然是需要使用浮点类型了。 建一个menus表,我们还需...
首先,在对话框中创建通信控件,若Control工具栏中缺少该控件,可通过菜单Project -- Add to Project -- Components and Control插入即可,再将该控件从工具箱中拉到对话框中。此时,你只需要关心控件提供的对 Windows 通讯驱动程序的 API 函数的接口。 !-- frame contents --!-- /frame contents -- 换句话说,只需要设置和监视...
树控件的双击响应 吉林大学 李健 下载本文示例代码 ClassWizard为CTreeCtrl(树控件)添加的NM_DBLCLK(双击)消息的响应函数中带有一个NMHDR * 型指针形参: OnDblClkTree(NMHDR* pNMHDR, LRESULT* pResult) 这个陌生的指针类型常使初用树控件的编程者陷入迷茫之中。实际上我们完全...
引言 工具条作为大多数标准的Windows应用程序的一个重要组成部分,使其成为促进人机界面友好的一个重要工具。通过工具条极大方便了用户对程序的操作,但是在由Microsoft Visual C++开发环境所创建的应用程序框架中的工具条只是一个简单的按钮的集合,在功能上也仅仅是起到了菜单快捷方式的作用,而没有做到象VC、Word等软件的工具条那样,...

经验教程

39

收藏

21
微博分享 QQ分享 QQ空间 手机页面 收藏网站 回到头部