Chinaunix首页 | 论坛 | 博客
  • 博客访问: 588334
  • 博文数量: 752
  • 博客积分: 40000
  • 博客等级: 大将
  • 技术积分: 5005
  • 用 户 组: 普通用户
  • 注册时间: 2008-10-13 14:47
文章分类

全部博文(752)

文章存档

2011年(1)

2008年(751)

我的朋友

分类:

2008-10-13 16:53:44

树型控件拖动的完美实现
作者:



??树型控件用来显示具有一定层次结构的数据项时方便、直观,被广泛地应用在各种软件中,如资源管理器中的磁盘目录就用的是树型
控件,我们在编程中也会经常用到,但 MFC 中提供的 CTreeCtrl 类并不直接支持拖动节点等高级特性,这使我们程序员编程时有很大限
制,又给软件用户带来了一些不便。下面就让我们自己动手来解决这个问题,实现树型控件中节点的拖动。

??我们从 CTreeCtrl 中派生了一个类 CXTreeCtrl ,它具有如下的特点:
??⑴ 基本拖动的实现。
??⑵ 处理无意拖动。
  ⑶ 能处理拖动过程中的滚动问题。
??⑷ 拖动过程中节点会智能展开。
??图 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 消息用于完成拖动操作,在给消息中,我们需要完成结束拖动图想的显示、删除拖动图象、释
放鼠标、节点的拷贝/删除等操作。在节点的拷贝/删除操作中,如果是父节点拖到子节点上,我们可以先将父节点拷到根结点下的临时节点
中,再从临时结点处拷到子节点,然后将根结点下的临时节点删除,这样做的目的是防止产生异常。

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 ; i
      通过上面的代码我们就实现了树型控件的拖动操作。
示例程序在 VC6+Windows2000 上调试通过。
--------------------next---------------------

楼主您好,我看了您的程序,试着做了一下,没有用您的类,我声明了一个CTreeCtrl的对象, 在做mousemove 的时候 CTreeCtrl::OnMouseMove(nFlags, point); 程序有错误:CWnd::OnMouseMove” : 无法访问 protected 成员(在“CWnd”类中声明) 怎么解决啊! 哪位朋友要是知道,告诉我一下LixueStudy@163.com, 10分感谢! ( LixueStudy 发表于 2008-7-28 11:35:00)
 
发现拖动图标在拖动时闪烁,修改OnMouseMove中的代码:
CImageList::DragShowNolock( false );  //避免鼠标经过时留下难看的痕迹
if( (hItem = HitTest(point,&flags)) != NULL )
{
SelectDropTarget( hItem );
m_hItemDragD = hItem;
}
CImageList::DragShowNolock( true );

为:
  //避免鼠标经过时留下难看的痕迹
if( (hItem = HitTest(point,&flags)) != NULL && hItem != m_hItemDragD)
{
CImageList::DragShowNolock( false );
SelectDropTarget( hItem );
m_hItemDragD = hItem;
CImageList::DragShowNolock( true );
}

效果好一些了
( tmman 发表于 2006-3-2 22:00:00)
 
to zwvista:
这篇文章我是 2001 年写的,当时开始学编程不久,我根本就不知道。不过代码都是从网上或者从书上零零碎碎看来的,没有提到别人确实不对。我现在已经好久都没有用过 C++,一直在用 Win32ASM 写一些小工具。我的主页 欢迎大家逛逛,不过更新太慢!:) ( 一块三毛钱 发表于 2004-12-20 21:07:00)
 
程序刚刚运行的时候,选中“唐诗”,在靠下方选中“元曲”(越靠下越好),然后慢慢往下拖……看见了什么?为什么所有介绍Tree的DragDrop的文章都没有注意到这个问题,真的不重要吗?sigh…… ( finalvictory 发表于 2003-12-14 2:42:00)
 
to zwvista:
也不是完全的转载,还是作者自己综合了一下的,鼓励一下作者! ( webdigest 发表于 2003-6-22 22:26:00)
 
如果想要不允许从上层往下层拖动,就是只允许同层间拖动,要怎么做呢? ( 学习者 发表于 2003-3-20 18:36:00)
 
所有的代码都取自于codeguru.com,却羞于或者不屑承认这一点,用“我们“来误导别人,作者一块三毛钱你怎么不感到脸红呢?
( zwvista 发表于 2003-1-26 2:18:00)
 
可以学习学习 ( D 发表于 2002-11-13 9:16:00)
 
is there a control fully equals treeview as explore?
too trouble if all these must be done by myself!
VC is not as good as Delphi! ( fxniao 发表于 2002-8-10 9:42:00)
 
要是你的作品可以象windows 资源管理器那样, 就好了, 可以 浏览你的文件夹中的文章或 图片 ( y1h2b3xy 发表于 2002-7-10 15:46:00)
 
.......................................................

--------------------next---------------------

阅读(290) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~