MFC TreeControl简单应用


对于TreeControl常用操作,做如下介绍。

1. TreeControl添加节点

  1. 在界面种选择TreeControl控件,点击右键,在弹出的菜单种选择【添加变量】,在弹出的界面中输入变量名 "m_list”

  2. 点击界面右键,在弹出的菜单中选择【类向导】,选中“虚函数”选项卡,选择其中的“OnInitDialog”函数,然后点击【添加函数按钮】,然后点击确定。

  3. 将TreeControl控件中的属性“Has Buttons”、”Line at root“和“Has Line”后改为 “True”,如果相实现双击更改其值的功能,则将”Edit Labels“改为”True“

  4. 如果想要在树节点前添加图标,则需要导入*.icon的图标文件。具体方法为:
    选择资源视图,在资源视图中点击右键,在弹出的菜单中选择【添加资源】,在弹出的界面中点击【导入】按钮,然后选择.icon的文件。此图标导入后的名称为”IDI_ICON1“

  5. 在初始化函数中添加节点代码

//在.h文件中添加如下代码
CImageList m_imageList1;
//修改初始化函数
BOOL TreeTest::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	m_list.SetBkColor(RGB(230,230,230));//设置树的颜色

	m_imageList1.Create(16,16,ILC_COLOR8|ILC_MASK,0,4);//第五个参数为图标的总数
	m_imageList1.Add(AfxGetApp()->LoadIconW(IDI_ICON1));//将图标添加到imageList中

	m_list.SetImageList(&m_imageList1,TVSIL_NORMAL);//将图标进行加载

	
	HTREEITEM root = m_list.InsertItem(_T("root"));//插入根节点
	HTREEITEM hChild1 = m_list.InsertItem(_T("child1"),root);//在根节点下插入子节点
    HTREEITEM hChild2 = m_list.InsertItem(_T("child2"),root);//在根节点下插入子节点
    HTREEITEM hChild3 = m_list.InsertItem(_T("child3"),root);//在根节点下插入子节点

	m_list.SetItemImage(root,0,0);//设置根节点图标
	m_list.SetItemImage(hChild1,0,0);
    m_list.SetItemImage(hChild2,0,0);
    m_list.SetItemImage(hChild3,0,0);//设置子节点图标,注意:第一个参数为要设置的节点、第二个参数为默认状态下的图标、第三个值为选中后的图标。


	return TRUE;  // return TRUE unless you set the focus to a control

}

2. TreeControl菜单

  1. 新建一个菜单,内容为”增加“、”删除“、”重命名“
  2. 选中Tree控件,点击右键,在弹出的菜单中选择”类向导“;在弹出的界面中选择【命令】、对象下选择”IDC_TREE1“,在消息中选择右键消息”NM_RCLICK“,然后点击【添加处理程序】按钮。
  3. 选中节点后才可以加载菜单,具体代码如下:
void TreeTest::OnRclickTree1(NMHDR *pNMHDR, LRESULT *pResult)
{
	CPoint ScreenPt;
	GetCursorPos(&ScreenPt);

	CPoint pt =GetCurrentMessage()->pt;//获取当前鼠标点击消息的坐标点
	m_list.ScreenToClient(&pt);//将鼠标的屏幕坐标,转换成树控件的客户区域坐标
	UINT uFlags = 0;
	HTREEITEM hItem = m_list.HitTest(pt,&uFlags);//然后做点击测试

	/**判断右键是否在节点上**/
	if ((hItem != NULL)&&(TVHT_ONITEM & uFlags))//如果点击位置在界面位置上面
	{
		
		CMenu menu;
		menu.LoadMenuW(IDR_MENU1);//装载第一个子菜单,即我们菜单的第一列
		CMenu* pPopup = menu.GetSubMenu(0);
		pPopup->TrackPopupMenu(TPM_LEFTALIGN, ScreenPt.x, ScreenPt.y, this);//弹出菜单 2
	
	}
	*pResult = 0;
}

3. TreeControl修改节点

参考链接
实现双击修改节点名称,双击时速度需要慢一些,快速双击为展开和折叠节点

  1. 属性设置:将”Edit Labels“改为”True“
  2. 选中Tree控件,在弹出的菜单中选择【类向导】,添加消息函数”TVN_BEGINLABELEDIT“ 和 ”TVN_ENDLABELEDIT“
  3. 添加全局变量CString g_sSelectStr
  4. 修改两个消息函数,,如下:
void TreeTest::OnBeginlabeleditTree1(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMTVDISPINFO pTVDispInfo = reinterpret_cast<LPNMTVDISPINFO>(pNMHDR);

	g_sSelectStr = m_list.GetItemText(m_list.GetSelectedItem());//得到修改前的数据
	*pResult = 0;
}


void TreeTest::OnEndlabeleditTree1(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMTVDISPINFO pTVDispInfo = reinterpret_cast<LPNMTVDISPINFO>(pNMHDR);
	
	CString strName; //修改后的数据
	CString rootstr;
	m_list.GetEditControl()->GetWindowText(strName);
	if (strName.IsEmpty())
	{
		AfxMessageBox(_T("数据项不能为空,请重新输入"));
		return;
	}
	if (strName.Compare(g_sSelectStr) == 0)//名字未做修改
	{
		return;
	}
	//判断是否由重复的名字
	HTREEITEM hRoot = m_list.GetRootItem();
	HTREEITEM hFind = FindItem(hRoot,strName);
	if (hFind == NULL)//如果没有重名,则修改
	{
		CString strText;
		m_list.GetEditControl()->GetWindowText(strText);
		m_list.SetItemText(m_list.GetSelectedItem(),strText);
	}
	*pResult = 0;
}

4. TreeControl查找节点

//根据名称来查找节点
HTREEITEM TreeTest::FindItem(HTREEITEM item,CString strText)
{
	HTREEITEM hFind;
	if (item == NULL)//修改数据与根数据不同,遍历时使用
	{
		return NULL;
	}
	while(item != NULL)
	{
		if (strText.Compare(m_list.GetItemText(item))  == 0)//名字相同
		{
			return item;
		}
		if (m_list.ItemHasChildren(item))//如果有子节点,则继续判断
		{
			item = m_list.GetChildItem(item);
			hFind = FindItem(item,strText);
			if (hFind)
			{
				return hFind;
			}
			else
			{
				item = m_list.GetNextSiblingItem(m_list.GetParentItem(item));
			}
		}
		else
		{
			item = m_list.GetNextSiblingItem(item);
			return FindItem(item,strText);
		}
	}
	return item;
}

5. TreeControl折叠展开节点

//折叠所有的树节点
void TreeTest::mFoldTree(HTREEITEM hTreeItem)
{
	if(!m_list.ItemHasChildren(hTreeItem))    
	{    
		return;    
	}    
	HTREEITEM hNextItem = m_list.GetChildItem(hTreeItem);
	while (hNextItem != NULL)    
	{   
		ExpandTree(hNextItem);    
		hNextItem = m_list.GetNextItem(hNextItem, TVGN_NEXT);    
	}    
	m_list.Expand(hTreeItem,TVE_COLLAPSE); 
}

//展开所有的树节点
void TreeTest::ExpandTree(HTREEITEM hTreeItem)
{
	if(!m_list.ItemHasChildren(hTreeItem))    
	{    
		return;    
	}  
	HTREEITEM hNextItem = m_list.GetChildItem(hTreeItem);
	while (hNextItem != NULL)    
	{   
		ExpandTree(hNextItem); 
		hNextItem = m_list.GetNextItem(hNextItem, TVGN_NEXT);    
	} 
	HTREEITEM hchild = m_list.GetChildItem(hTreeItem);
	CString NodeData,NodeName;
	NodeName = m_list.GetItemText(hchild);
	if (NodeData.Compare(_T("2")) == 0 )
	{
		return;
	}
	m_list.Expand(hTreeItem,TVE_EXPAND); 
}

//展开单独的树节点
void TreeTest::ExpandTree2(HTREEITEM hTreeItem)
{
	if(!m_list.ItemHasChildren(hTreeItem))    
	{    
		return;    
	}  
	HTREEITEM hNextItem = m_list.GetChildItem(hTreeItem);
	while (hNextItem != NULL)    
	{   
		ExpandTree(hNextItem); 
		hNextItem = m_list.GetNextItem(hNextItem, TVGN_NEXT);    
	} 
	m_list.Expand(hTreeItem,TVE_EXPAND); 
}


void TreeTest::OnClickTree1(NMHDR *pNMHDR, LRESULT *pResult)
{
	hTreeItem = m_list.GetSelectedItem();
	*pResult = 0;
}

//折叠树按钮
void TreeTest::OnBnClickedButton1()
{
	HTREEITEM hItem = m_list.GetSelectedItem();
	if (hItem == NULL)
	{
		HTREEITEM hroot = m_list.GetRootItem();
		mFoldTree(hroot);	  
	}
	else
	{
		mFoldTree(hItem);
	}
}

//展开树按钮
void TreeTest::OnBnClickedButton7()
{
	HTREEITEM hItem = m_list.GetSelectedItem();
	if (hItem == NULL)
	{
		HTREEITEM hroot = m_list.GetRootItem();
		ExpandTree(hroot);
	}
	else
	{
		ExpandTree2(hItem);
	}
}

6. TreeControl拖动树

参考网址1
参考网址2
参考网址3
参考网址4

创建树的时候,使用继承类TreeControl进行创建。例如:
注意:此方法目前具有一定的局限性,即节点下必须有子节点,才可以完成拖动。后期进行优化

CXtreeCtrl m_list;
HTREEITEM root =	m_list.InsertItem(_T("root"));
HTREEITEM parent1 = m_list.InsertItem(_T("1"),root);
m_list.InsertItem(_T("11"),parent1);

HTREEITEM parent2 = m_list.InsertItem(_T("2"),root);
m_list.InsertItem(_T("21"),parent2);
	
HTREEITEM parent3 = m_list.InsertItem(_T("3"),root);
m_list.InsertItem(_T("31"),parent3);

HTREEITEM parent4 = m_list.InsertItem(_T("4"),root);
m_list.InsertItem(_T("41"),parent4);

7. 继承类TreeControl

XTreeCtrl.cpp

// XtreeCtrl.cpp : implementation file
//

#include "stdafx.h"
#include "Drag.h"
#include "XtreeCtrl.h"
#define DRAG_DELAY 60

// CXtreeCtrl

IMPLEMENT_DYNAMIC(CXtreeCtrl, CTreeCtrl)

CXtreeCtrl::CXtreeCtrl()
{
	m_bDragging = FALSE;
	//m_hSelectItem = NULL;
	m_hItemDragS = NULL;
	m_nHoverTimerID = 0;
	m_nScrollTimerID = 0;
	m_bInsertAbove = FALSE;
	m_hItemDragD = NULL;

}

CXtreeCtrl::~CXtreeCtrl()
{
}


BEGIN_MESSAGE_MAP(CXtreeCtrl, CTreeCtrl)
	ON_NOTIFY_REFLECT(TVN_BEGINDRAG, &CXtreeCtrl::OnTvnBegindrag)
	ON_WM_LBUTTONUP()
	ON_WM_MOUSEMOVE()
	ON_WM_TIMER()
	ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()



// CXtreeCtrl message handlers



void CXtreeCtrl::OnTvnBegindrag(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
	// TODO: Add your control notification handler code here
	
	*pResult = 0;
	if( (GetTickCount() - m_dwDragStart) < DRAG_DELAY )
		return;
	
	m_hItemDragS = pNMTreeView->itemNew.hItem;
	if (!ItemHasChildren(m_hItemDragS))				//子节点不能被拖拽,注释后可以随意拖拽
	{
		return;
	}
	m_hItemDragD = NULL;
	m_bDragging = true;
	m_nScrollTimerID = SetTimer( 2,40,NULL );
}
HTREEITEM CXtreeCtrl::GetDragTarget(HTREEITEM hItem, POINT point, BOOL& bInsertAbove)
{
	ASSERT(hItem != NULL);
	HTREEITEM hDragTarget;
	CRect rectItem;
	GetItemRect(hItem, &rectItem, FALSE);
	if (point.y < rectItem.CenterPoint().y)
	{
		hDragTarget = hItem;
		bInsertAbove = TRUE;
	}
	else
	{
		if (ItemHasChildren(hItem) && (GetItemState(hItem, TVIS_EXPANDED) & TVIS_EXPANDED))
		{
			hDragTarget = GetChildItem(hItem);    
			bInsertAbove = TRUE;
		}
		else
		{
			hDragTarget = hItem;
			bInsertAbove = FALSE;
		}
	}
	ASSERT(hDragTarget != NULL);
	return hDragTarget;
}

void CXtreeCtrl::OnMouseMove(UINT nFlags, CPoint point)
{
	// TODO: Add your message handler code here and/or call default

	HTREEITEM hItem;
	UINT      flags;
	if (m_nHoverTimerID > 0)
	{
		KillTimer(m_nHoverTimerID);
		m_nHoverTimerID = 0;
	}	
	if( m_bDragging )
	{
		m_nHoverTimerID = SetTimer(1, 800, NULL);
		m_HoverPoint = point;
		//鼠标经过时高亮显示
		CImageList::DragShowNolock( false ); //避免鼠标经过时留下难看的痕迹
		HTREEITEM m_hNextDragTarget;	
		BOOL m_bNextInsertAbove;		
		if( (hItem = HitTest(point,&flags)) != NULL )
		{
			m_hNextDragTarget = GetDragTarget(hItem, point, m_bNextInsertAbove);
			if ((m_hNextDragTarget != m_hItemDragD) || (m_bInsertAbove != m_bNextInsertAbove))
			{
				SelectDropTarget(m_hNextDragTarget);
				m_hItemDragD = m_hNextDragTarget;
				m_bInsertAbove = m_bNextInsertAbove;
				EnsureVisible(m_hItemDragD);
				RedrawWindow();
			}
		}
		if ((GetScrollPos(SB_HORZ) > 0) && (GetScrollLimit(SB_VERT) > 0))
		{
			Invalidate();
		}
	}
	CTreeCtrl::OnMouseMove(nFlags, point);
}
HTREEITEM CXtreeCtrl::CopyItem(HTREEITEM hItem1, HTREEITEM htiNewParent, HTREEITEM htiAfter) //拷贝条目
{
	TV_ITEM tvSrc;
	tvSrc.mask  = TVIF_HANDLE | TVIF_PARAM | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_STATE;
	tvSrc.hItem = hItem1;
	if (!GetItem(&tvSrc)) 
		return	htiNewParent;

	tvSrc.hItem = htiNewParent;
	SetItem(&tvSrc);
	SetItemText(htiNewParent,GetItemText(hItem1));
	SetCheck(htiNewParent,GetCheck   (hItem1));

	if (tvSrc.state & TVIS_EXPANDED) 
		Expand(htiNewParent,TVE_EXPAND);
}
HTREEITEM CXtreeCtrl::CopyBranch(HTREEITEM htiBranch, HTREEITEM htiNewParent, HTREEITEM htiAfter) //拷贝分支
{
	ASSERT((htiNewParent != NULL) && (htiBranch != NULL));
	HTREEITEM hChild;
	hChild = GetChildItem( htiBranch );
	while ( hChild != NULL )
	{
		HTREEITEM hChildDest = InsertItem(_T("dest child"), htiNewParent);
		CopyBranch( hChild,hChildDest,htiAfter );
		CopyItem(hChild,hChildDest,htiAfter);
		hChild = GetNextSiblingItem( hChild );
	}
	return htiNewParent;
}

HTREEITEM CXtreeCtrl::InsertItemAndSubtree(HTREEITEM hParent)
{
	if ((m_hItemDragD == NULL) || (m_hItemDragS == NULL)) 
	{
		ASSERT(FALSE);
		return NULL;
	}
	HTREEITEM hRoot = GetRootItem();
	if (!ItemHasChildren(m_hItemDragD))		//如果注释可以随意拖拽父节点到子节点(自己看效果)
	{
		return	NULL;
	}
	
	else if (hRoot == m_hItemDragD)			//如果注释可以随意拖拽父节点至根节点(自己看效果)
	{
		return	NULL;
	}
	if (hParent == NULL)
		hParent = GetParentItem(m_hItemDragD);
	if (hParent == NULL)
		hParent = TVI_ROOT;

	HTREEITEM hInsertAfter;

	if (m_bInsertAbove)
		hInsertAfter = GetPrevSiblingItem(m_hItemDragD);
	else
		hInsertAfter = m_hItemDragD;

	if ((hInsertAfter == hParent) || (hInsertAfter == NULL))
		hInsertAfter = TVI_FIRST;

	HTREEITEM hNew = InsertItem(_T("dummy"), hParent, hInsertAfter);
	if (hNew == NULL)
		return NULL;

	CopyBranch(m_hItemDragS,hNew,m_hItemDragD);
	CopyItem(m_hItemDragS,hNew,m_hItemDragD);

	return hNew;
}

void CXtreeCtrl::OnLButtonUp(UINT nFlags, CPoint point)
{
	// TODO: Add your message handler code here and/or call default
	CTreeCtrl::OnLButtonUp(nFlags, point);
	if(m_bDragging)
	{
		m_bLeftBtnUp = TRUE;
		KillTimer(m_nScrollTimerID);
		m_nScrollTimerID = 0;
		KillTimer(m_nHoverTimerID);
		m_nHoverTimerID = 0;
		ReleaseCapture();
		ShowCursor(true);
		m_bDragging = false;
		SelectDropTarget(NULL);
		if (m_bLeftBtnUp)
		{
			if (m_hItemDragD != NULL)
				m_bGoingOK = TRUE;
			HTREEITEM m_hParentNew = m_hItemDragD;
			while (m_hParentNew != NULL)
			{

				if(m_hParentNew == m_hItemDragS)
				{
					//AfxMessageBox(_T("禁止此操作!"), MB_ICONEXCLAMATION);
					m_bGoingOK = FALSE;
					break;
				}
				m_hParentNew = GetParentItem(m_hParentNew);
			}
			HTREEITEM m_hParent;
			if (m_bGoingOK)
			{
				SetRedraw(FALSE);            
				m_hParent = GetParentItem(m_hItemDragD);
				if (m_hParent == NULL)
					m_hParent = TVI_ROOT;		
				if (m_bGoingOK)
				{
					// We're finally ready to insert the actual item.
					HTREEITEM hNew = InsertItemAndSubtree(m_hParent);
					if (hNew != NULL) 
					{
						SelectItem(hNew);
						DeleteItem(m_hItemDragS);
					}
				}
			}
			// Regardless of what happens, we need to reset the tree.
			SetRedraw(TRUE);            
			Invalidate();
		}
		else
		{
			// The User chose to abort the drag
			EnsureVisible(m_hItemDragS);
			// Refresh the screen to get rid of any remnants of the drag drawing
			Invalidate();
		}

	}
	CTreeCtrl::OnLButtonUp(nFlags, point);
}

void CXtreeCtrl::OnTimer(UINT_PTR nIDEvent)
{
	// TODO: Add your message handler code here and/or call default
	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);
}

void CXtreeCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
	// TODO: Add your message handler code here and/or call default
	//m_dwDragStart = GetTickCount();	
	//m_hSelectItem = GetSelectedItem();
	//UINT nHitFlags = 0;
	//HTREEITEM hClickedItem = HitTest( point, &nHitFlags );
	CTreeCtrl::OnLButtonDown(nFlags, point);
}

XTreeCtrl.h

#pragma once


// CXtreeCtrl

class CXtreeCtrl : public CTreeCtrl
{
	DECLARE_DYNAMIC(CXtreeCtrl)

public:
	CXtreeCtrl();
	virtual ~CXtreeCtrl();

protected:
	DECLARE_MESSAGE_MAP()


public:
	afx_msg void OnTvnBegindrag(NMHDR *pNMHDR, LRESULT *pResult);
	afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
	afx_msg void OnMouseMove(UINT nFlags, CPoint point);
	afx_msg void OnTimer(UINT_PTR nIDEvent);
	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
protected:
	DWORD		m_dwDragStart;
	UINT        m_nScrollTimerID;
	UINT        m_nHoverTimerID;
	UINT		m_TimerTicks;
	POINT       m_HoverPoint;
	BOOL        m_bDragging;
	BOOL        m_bInsertAbove;
	BOOL		m_bLeftBtnUp;
	BOOL		m_bGoingOK;
	CImageList* m_pDragImage;
	HTREEITEM   m_hItemDragS;
	HTREEITEM   m_hItemDragD;
	HTREEITEM	m_hSelectItem;
	HTREEITEM CopyBranch(HTREEITEM htiBranch,HTREEITEM htiNewParent,HTREEITEM htiAfter);
	HTREEITEM CopyItem(HTREEITEM hItem,HTREEITEM htiNewParent,HTREEITEM htiAfter);
	HTREEITEM GetDragTarget(HTREEITEM hItem, POINT point, BOOL& bInsertAbove);
	HTREEITEM InsertItemAndSubtree(HTREEITEM hParent = NULL);
};

原文地址:https://www.cnblogs.com/gaozihan/p/10835764.html