vc 树节点拖动功能

vc 树节点拖动功能(转载)
原谅转载自: http://tech.ddvip.com/2008-11/122662837992492.html

Visual C++中提供的MFC类CtreeCtrl(树型控件)用来显示具有一定层次结构的数据项时方便、直观,所以它已经被广泛地应用在各种软件中,如资源管理器中的磁盘目录就用的是树型控件,我们在编程中也会经常用到这个控件,但是这个控件也有缺陷,那就是它并不直接支持拖动节点等高级特性,这使得程序员在编程时使用它受到了很大限制,同时又给软件用户带来了一些不便。为此,本实例通过从 CTreeCtrl 中派生了一个类 CXTreeCtrl ,实现树型控件中节点的拖动。这个类具有如下的功能:⑴ 基本项目条拖动的实现;⑵ 处理项目条的无意拖动;⑶ 能处理项目条拖动过程中的滚动问题;⑷ 拖动过程中节点会智能展开。程序编译运行后的效果如图所示:

570)?'570px':'auto'; }" src="http://img.ddvip.com/2008_11_14/1226628379_ddvip_9835.png">

  图一:树型控件节点拖动示例

  一、实现方法

  我们针对上述自定义类的实现功能,介绍实现思路和方法。

  (1)基本项目条拖动的实现

  当我们要拖动树型控件的一个项目条时,树型控件会给它的父窗口发送一个TVN_BEGINDRAG通知消息,所以可以在此消息的响应函数中,调用 CTreeCtrl ::CreateDragImage ()函数创建表示当前项目条正处在拖动操作中的图象,该函数创建的图象由项目条的图象和标签文本组成。创建了拖动图象后,调用CImageList::BeginDrag()函数指定拖动图象的热点位置,然后调用CImageList::DragEnter()函数显示拖动图象。接下来处理 WM_MOUSEMOVE 消息用于更新拖动图象,我们想让移动中的图象经过某些项目时高亮度显示,这可以调用 CTreeCtrl ::SelectDropTarget() 来实现。在调用 SelectDropTarget()函数之前,需要先调用CImageList::DragShowNolock ( false )函数来隐藏图象列表,然后再调用CImageList::DragShowNolock ( true ) 函数来恢复图象列表的显示,这样就不会在拖动过程中留下难看的轨迹。最后我们处理 WM_LBUTTONUP 消息用于完成拖动操作,在该消息响应函数中,我们需要完成结束拖动图象的显示、删除拖动图象、释放鼠标、节点的拷贝/删除等操作。在节点的拷贝/删除操作中,如果是父节点拖到子节点上,我们可以先将父节点拷到根结点下的临时节点中,再从临时结点处拷到子节点,然后将根结点下的临时节点删除,这样做的目的是防止产生异常。

  (2)处理项目条的无意拖动

  牐犎绻在鼠标按下时不小心移动了鼠标,这时系统就认为产生了一个移动操作,这就产生了误操作。解决这个问题的方法是设置时间延迟,也就是说当用户按下鼠标后必须在原位置停留一段时间,才能激活拖动操作。

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

  当我们拖动树型控件的项目条时,如果目的节点不可见,则需要拖动滚动条或收拢其它一些节点以使得目的节点显示出来,无疑,这会给我们带来很大的不便。为此就要给树型控件添加自动滚动支持。首先设置一个定时器,在 WM_TIMER 消息中检测鼠标的位置,如果靠近树型控件的下边缘,则使得控件向下滚动。靠近上边缘则向上滚动。滚动速度根据鼠标的位置确定。

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

  为了实现在拖动过程中鼠标停留在某个节点上一段时间后,该节点会自动展开的功能。设置一个定时器,当鼠标在拖动过程中停止在某个节点上时,定时器被启动,再设置一变量保存当前的鼠标位置。

  二、编程步骤

  1、 新建一对话框工程DragTree,编辑资源,在对话框中加入一树型控件IDC_TREE ,属性设置为:Has Buttons、Has Lines、Lines at root、Edit Labels、Border;

  2、 使用Class Wizard给该控件添加一个成员变量 m_wndTree ,在代码部分将该控件的类型修改为CXTreeCtrl。

  3、 在对话框的OnInitDialog()函数中添加代码,初始化树型控件的项目条;

  4、 制作一个图像资源(ID为IDB_TREEIMAGE),其中包含两个小图标,用来作为树型控件项目条的显示图标;

  5、 添加代码,编译运行程序。

  三、程序代码

//  XTreeCtrl.h : header file 
#if  !defined(AFX_XTREECTRL_H__3EF12526_EF66_4FD9_A572_59476441D79A__INCLUDED_) 
#define  AFX_XTREECTRL_H__3EF12526_EF66_4FD9_A572_59476441D79A__INCLUDED_ 
#if  _MSC_VER > 1000 
#pragma once 
#endif   //  _MSC_VER > 1000 
class  CXTreeCtrl :  public  CTreeCtrl 

 
//  Construction 
  public
  CXTreeCtrl(); 
  
//  Attributes 
  public
  
//  Operations 
  public
  
//  Overrides 
  
//  ClassWizard generated virtual function overrides 
  
// {{AFX_VIRTUAL(CXTreeCtrl) 
  
// }}AFX_VIRTUAL 
  
//  Implementation 
  public
  
virtual   ~ CXTreeCtrl(); 
  
//  Generated message map functions 
  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;  // 接受拖动的标签 
  
// {{AFX_MSG(CXTreeCtrl) 
   afx_msg  void  OnBegindrag(NMHDR *  pNMHDR, LRESULT *  pResult); 
   afx_msg 
void  OnMouseMove(UINT nFlags, CPoint point); 
   afx_msg 
void  OnLButtonUp(UINT nFlags, CPoint point); 
   afx_msg 
void  OnLButtonDown(UINT nFlags, CPoint point); 
   afx_msg 
void  OnTimer(UINT nIDEvent); 
  
// }}AFX_MSG 
  DECLARE_MESSAGE_MAP() 
 
private
  HTREEITEM CopyBranch(HTREEITEM htiBranch,HTREEITEM htiNewParent,HTREEITEM htiAfter); 
  HTREEITEM CopyItem(HTREEITEM hItem,HTREEITEM htiNewParent,HTREEITEM htiAfter); 
}; 
#endif  
////////////////////////////////////////////////////////////  CXTreeCtrl 
#include  " stdafx.h "  
#include 
" DragTree.h "  
#include 
" XTreeCtrl.h "  
#ifdef _DEBUG 
#define  new DEBUG_NEW 
#undef  THIS_FILE 
static   char  THIS_FILE[]  =  __FILE__; 
#endif  
#define  DRAG_DELAY 60 
CXTreeCtrl::CXTreeCtrl() 

 m_bDragging 
=   false

CXTreeCtrl::
~ CXTreeCtrl() 
{} 
BEGIN_MESSAGE_MAP(CXTreeCtrl, CTreeCtrl) 
// {{AFX_MSG_MAP(CXTreeCtrl) 
 ON_NOTIFY_REFLECT(TVN_BEGINDRAG, OnBegindrag) 
 ON_WM_MOUSEMOVE() 
 ON_WM_LBUTTONUP() 
 ON_WM_LBUTTONDOWN() 
 ON_WM_TIMER() 
// }}AFX_MSG_MAP 
END_MESSAGE_MAP() 
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  ; i < nCount - 1  ; i ++  ) 
     hItem 
=  GetNextVisibleItem( hItem ); 
     
if ( hItem ) 
      SelectDropTarget( hItem ); 
     m_hItemDragD 
=  hItem; 
     CImageList::DragShowNolock ( 
true  ); 
   } 
  } 
 } 
 
else
  CTreeCtrl::OnTimer(nIDEvent); 

////////////////////////////////////////////////////////////  
BOOL CDragTreeDlg::OnInitDialog() 

 CDialog::OnInitDialog(); 
 …………………….
// 此处代码省略 
 
//  TODO: Add extra initialization here 
 m_image.Create ( IDB_TREEIMAGE, 16 , 1 ,RGB( 255 , 255 , 255 ) ); 
 m_wndTree.SetImageList ( 
& m_image,TVSIL_NORMAL ); 
 HTREEITEM hti1 
=  m_wndTree.InsertItem ( _T( " 唐詩 " ), 0 , 1  ); 
 HTREEITEM hti2 
=  m_wndTree.InsertItem ( _T( " 宋詞 " ), 0 , 1  ); 
 HTREEITEM hti3 
=  m_wndTree.InsertItem ( _T( " 元曲 " ), 0 , 1  ); 
 HTREEITEM hti4 
=  m_wndTree.InsertItem ( _T( " 李白 " ), 0 , 1 ,hti1 ); 
 m_wndTree.InsertItem ( _T(
" 靜夜思(床前明月光) " ), 0 , 1 ,hti4 ); 
 m_wndTree.InsertItem ( _T(
" 將進酒(君不見黃河之水天上來) " ), 0 , 1 ,hti4 ); 
 m_wndTree.InsertItem ( _T(
" 望廬山瀑布(日照香爐生紫煙) " ), 0 , 1 ,hti4 ); 
 m_wndTree.InsertItem ( _T(
" 蜀道難(噫吁戲,危乎高哉) " ), 0 , 1 ,hti4 ); 
 HTREEITEM hti5 
=  m_wndTree.InsertItem ( _T( " 杜甫 " ), 0 , 1 ,hti1 ); 
 m_wndTree.InsertItem ( _T(
" 蜀相(丞相祠堂何處尋) " ), 0 , 1 ,hti5 ); 
 m_wndTree.InsertItem ( _T(
" 春望(國破山河在) " ), 0 , 1 ,hti5 ); 
 m_wndTree.InsertItem ( _T(
" 茅屋為秋風所破歌(八月秋高風怒號) " ), 0 , 1 ,hti5 ); 
 HTREEITEM hti6 
=  m_wndTree.InsertItem ( _T( " 白居易 " ), 0 , 1 ,hti1 ); 
 m_wndTree.InsertItem ( _T(
" 長恨歌(漢皇重色思傾國) " ), 0 , 1 ,hti6 ); 
 m_wndTree.InsertItem ( _T(
" 琵琶行並序(潯陽江頭夜送客) " ), 0 , 1 ,hti6 ); 
 m_wndTree.InsertItem ( _T(
" 李清照 " ), 0 , 1 ,hti2 ); 
 m_wndTree.InsertItem ( _T(
" 柳永 " ), 0 , 1 ,hti2 ); 
 
return  TRUE;  //  return TRUE unless you set the focus to a control 
}

 

你可能感兴趣的:(vc 树节点拖动功能)