MFC的Tree控件,可以说并不是特别友好,我们可以在程序中对其进行初始化;程序执行后,我们仅可以查看,并不能随心所欲的进行节点的拖拽。(废话!否则也不会写这篇博客)
下面首先介绍下Tree Control的基本使用,再展开介绍可拖拽TreeControl的自写类。
1.准备工作:各级节点图标的加载
CImageList m_ImageList; //声明一个图像列表类的对象,用于存放加载的一系列图像,后面需要进行创建
CTreeCtrl m_TreeCtrl; //声明一个CTreeCtrl对象
HICON hIcon[3]; //图标句柄数组
hIcon[0] = AfxGetApp()->LoadIcon(IDI_ICON1); //加载ico文件并将返回句柄存入数组中
hIcon[1] = AfxGetApp()->LoadIcon(IDI_ICON2);
hIcon[2] = AfxGetApp()->LoadIcon(IDI_ICON3);
2.各节点(根/子节点)的初始化
HTREEITEM hRoot; //根节点的句柄
HTREEITEM hChild; //一级子节点的句柄
HTREEITEM hCChild; //二级子节点的句柄
hRoot = m_TreeCtrl.InsertItem(_T("根节点0"),0,0); //插入根节点0
hChild = m_TreeCtrl.InsertItem(_T("一级子节点"), 1, 1, hRoot, TVI_LAST); //插入一级子节点
m_TreeCtrl.InsertItem(_T("二级子节点"), 2, 2, hChild, TVI_LAST); //插入二级子节点
m_TreeCtrl.Expand(hRoot, TVE_EXPAND); //展开根节点
hRoot = m_TreeCtrl.InsertItem(_T("根节点1"), 0, 0); //插入根节点1
hChild = m_TreeCtrl.InsertItem(_T("一级子节点"), 1, 1, hRoot, TVI_LAST); //插入一级子节点
m_TreeCtrl.InsertItem(_T("二级子节点"), 2, 2, hChild, TVI_LAST); //插入二级子节点
m_TreeCtrl.Expand(hRoot, TVE_EXPAND); //展开根节点
主要函数:
HTREEITEM InsertItem(
LPCTSTR lpszItem,
int nImage,
int nSelectedImage,
HTREEITEM hParent = TVI_ROOT,
HTREEITEM hInsertAfter = TVI_LAST
);
lpszItem
包含Item文本的字符串的地址。
nImageItem图像在ImageList中的索引
nSelectedImageItem选中状态下图像在ImageList中的索引
插入项的父节点的句柄。
hInsertAfter插入位置(前一个节点的句柄)
以上,Tree Control的全部初始化工作完成!
头文件:
HTREEITEM m_hBeginDrag; //记录拖动起点的Item
CImageList* m_pDragImages; //拖动的图像列表
BOOL m_bDrag; //用来判断是否进行拖动
void CopyNodes(HTREEITEM hDesItem, HTREEITEM hSrcItem);//自写的复制节点函数
消息映射:
afx_msg void OnBegindrag(NMHDR* pNMHDR, LRESULT* pResult); //拖动开始的触发函数
afx_msg void OnMouseMove(UINT nFlags, CPoint point); //鼠标移动的触发函数
afx_msg void OnLButtonUp(UINT nFlags, CPoint point); //鼠标点击后抬起的触发函数
BEGIN_MESSAGE_MAP(CDragTreeCtrl, CTreeCtrl)
ON_NOTIFY_REFLECT(TVN_BEGINDRAG, OnBegindrag)
ON_WM_MOUSEMOVE()
ON_WM_LBUTTONUP()
END_MESSAGE_MAP()
具体触发函数下的内容:
void CDragTreeCtrl::OnBegindrag(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
HTREEITEM hItem = pNMTreeView->itemNew.hItem; //获取开始拖动的Item的句柄!!!!!!!
if (hItem == GetRootItem()) //不允许拖动根节点
{
*pResult = 0;
return;
}
m_hBeginDrag = hItem; //记录拖动的起点项目
m_pDragImages = CreateDragImage(hItem); //创建拖动的图像列表
CPoint dragPt; //记录拖动起始点
dragPt.x = pNMTreeView->ptDrag.x;
dragPt.y = pNMTreeView->ptDrag.y;
if (m_pDragImages != NULL)
{
m_pDragImages->BeginDrag(0,CPoint(8,8)); //开始拖动图像 (1)
ClientToScreen(&dragPt); //转换客户坐标到屏幕坐标
m_pDragImages->DragEnter(this, dragPt); //在拖动的过程中显示拖动的图像 (2)
SetCapture(); //开始鼠标捕捉
m_bDrag = TRUE; //这里表示当前状态是拖动操作
}
*pResult = 0;
}
void CDragTreeCtrl::OnMouseMove(UINT nFlags, CPoint point)
{
if (m_bDrag) //处于拖动状态
{
HTREEITEM hItem;
UINT nHitFlags;
CRect clientRc;
GetClientRect(&clientRc); //获取客户区
m_pDragImages->DragMove(point); //设置拖动的图像位置为鼠标位置!!!
//设置鼠标经过时高亮显示
if ((hItem = HitTest(point, &nHitFlags)) != NULL)
{
CImageList::DragShowNolock(FALSE); //隐藏拖动的图像
SelectDropTarget(hItem); //设置选中的项目
CImageList::DragShowNolock(TRUE); //显示拖动的图像
}
}
else
CTreeCtrl::OnMouseMove(nFlags, point);//如果不在拖动状态,就触发普通的mouse move
}
最后,鼠标抬起后,完成一系列节点的重新插入,结束item的拖动状态。
void CDragTreeCtrl::OnLButtonUp(UINT nFlags, CPoint point)
{
if (m_bDrag == TRUE) //处于拖动状态,否则不进行操作
{
m_bDrag = FALSE; //结束拖动状态
CImageList::DragLeave(this); //隐藏拖动图像,允许窗口进行更新 (3)
CImageList::EndDrag(); //结束图像的拖动 (4)
ReleaseCapture(); //释放鼠标捕捉
delete m_pDragImages; //释放图像列表
m_pDragImages = NULL;
CRect winRC;
GetWindowRect(&winRC); //获取窗口区域
HTREEITEM hItem; //用于获取拖入处的Item
if ((hItem = HitTest(point, &nFlags)) != NULL) //拖入处有Item
{
//确定拖放后的 item的拖放实现过程
if ( (hItem !=m_hBeginDrag)&&(hItem!=GetParentItem(m_hBeginDrag)))
//如果目标项目与开始拖动的项目相同或者目标项目仍是开始项目的父节点,不进行处理
{
CopyNodes(hItem,m_hBeginDrag); //完成节点的复制
DeleteItem(m_hBeginDrag); //删除原来地方的Item
}
Invalidate(); //刷新窗口(重绘)
SelectDropTarget(NULL); //取消移入Item的选中状态
m_hBeginDrag = NULL;
}
}
}
上述中的(1)(2)(3)(4)是拖拽过程最为重要的四个标志函数了!注意他们的位置及顺序!
以上,就是可拖拽 Tree Control的全部实现过程了!一定有同学问这里的CopyNodes()是什么函数啊,当然是自己写的呀,下面附上。
//复制节点,将hSrcItem及其子项目复制到下hDesItem节点下
void CDragTreeCtrl::CopyNodes(HTREEITEM hDesItem, HTREEITEM hSrcItem)
{
if (hDesItem == NULL || hSrcItem == NULL) //Item为空则不进行操作
{
return;
}
TVITEM tvItem; //定义项目信息结构体
tvItem.mask = TVIF_TEXT | TVIF_IMAGE; //设置返回标记
tvItem.hItem = hSrcItem;
wchar_t chText[MAX_PATH] = { 0 };
tvItem.pszText = chText;
tvItem.cchTextMax = MAX_PATH;
GetItem(&tvItem); //获取项目信息
TVINSERTSTRUCT tvInsert; //定义插入操作的数据结构
tvInsert.hParent = hDesItem; //定义插入的父节点
tvInsert.item = tvItem; //定义插入的子节点
HTREEITEM hInsert = InsertItem(&tvInsert); //插入Item,并获取插入完之后的Item新的句柄
//接下来将Item里的所有子Item也插过来
HTREEITEM hChild = GetChildItem(hSrcItem); //获取子节点
while (hChild != NULL)
{
CopyNodes(hInsert, hChild); //递归调用 厉害了!!!
hChild = GetNextSiblingItem(hChild); //获取下一个兄弟节点
}
}
以上。