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、 添加代码,编译运行程序。
三、程序代码
#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
}