CTreeCtrl树控件:如何给MFC中的CTreeCtrl树控件添加复选框及选中状态

1、效果如下:
CTreeCtrl树控件:如何给MFC中的CTreeCtrl树控件添加复选框及选中状态_第1张图片
2、首先需要重写树控件类
CCheckTreeCtrl.h

#pragma once


typedef enum TREE_STATE 
{
	STATE_NONE,
	STATE_UNCHECKED,
	STATE_CHECKED,
	STATE_INTERMEDIATE,
	STATE_DISABLED
};

//当树中某项的选中状态被改变时,会触发NM_CHECKSTATECHANGED消息,数据类型为TREEINFO
//ON_NOTIFY(NM_CHECKSTATECHANGED, IDC_TREE_CURRES, OnCheckStateChangeTreeCurres)
#define NM_CHECKSTATECHANGED WM_USER+0x6D5

//当树中某项被点击时,会触发NM_MYCLICK消息(代替NM_CLICK)
//ON_NOTIFY(NM_MYCLICK, IDC_TREE_CURRES, OnClickTreeCurres)
#define NM_MYCLICK (NM_FIRST-1533)

// CCheckTreeCtrl
class CCheckTreeCtrl : public CTreeCtrl
{
	DECLARE_DYNAMIC(CCheckTreeCtrl)

public:
	CCheckTreeCtrl();
	virtual ~CCheckTreeCtrl();

	int GetItemCheck(HTREEITEM hItem) const;
	BOOL SetItemCheck(HTREEITEM hItem, int nState, BOOL bRecursion = FALSE);
	
public:
	virtual BOOL PreTranslateMessage(MSG* pMsg);
	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
	afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point);

protected:
	virtual void PreSubclassWindow();
	BOOL ToggleCheckState(HTREEITEM hItem, UINT uFlags);
	void SetChildrenItemCheck(HTREEITEM hItem);
	void SetParentItemCheck(HTREEITEM hItem);
protected:
	CImageList m_ilStateImages;

	DECLARE_MESSAGE_MAP()
};

CCheckTreeCtrl.cpp

// CheckTreeCtrl.cpp : 实现文件
//

#include "stdafx.h"
#include "CheckTreeCtrl.h"
#include "resource.h"

// CCheckTreeCtrl

IMPLEMENT_DYNAMIC(CCheckTreeCtrl, CTreeCtrl)

CCheckTreeCtrl::CCheckTreeCtrl()
{
}

CCheckTreeCtrl::~CCheckTreeCtrl()
{
}


BEGIN_MESSAGE_MAP(CCheckTreeCtrl, CTreeCtrl)
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONDBLCLK()
	ON_WM_CHAR()
END_MESSAGE_MAP()



// CCheckTreeCtrl 消息处理程序
void CCheckTreeCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
	TVHITTESTINFO tvHitTest;
	tvHitTest.pt = point;

	HTREEITEM hItem = HitTest(&tvHitTest);
	if (hItem != NULL)
	{
		if (STATE_DISABLED == GetItemCheck(hItem))
			return;

		if (ToggleCheckState(hItem, tvHitTest.flags))
		{
			return;
		}
	}

	CTreeCtrl::OnLButtonDown(nFlags, point);
}

void CCheckTreeCtrl::OnLButtonDblClk(UINT nFlags, CPoint point)
{
	OnLButtonDown(nFlags, point);
}

BOOL CCheckTreeCtrl::ToggleCheckState(HTREEITEM hItem, UINT uFlags)
{
	BOOL bOnStateIcon = ((uFlags & TVHT_ONITEMSTATEICON) == TVHT_ONITEMSTATEICON);

	if ((bOnStateIcon == FALSE) || (hItem == NULL)) {
		return FALSE;
	}

	int nState = GetItemCheck(hItem) + 1;

	if (nState > STATE_CHECKED/*STATE_INTERMEDIATE*/)
		nState = STATE_UNCHECKED;

	SelectItem(hItem);
	
	BOOL bRet = SetItemCheck(hItem, nState);
	if (bRet)
	{
		SetChildrenItemCheck(hItem);
		SetParentItemCheck(hItem);

		// 发送消息,通知状态改变
		if(GetParent())
		{
			CWnd* pWnd1 = GetParent();
			if (pWnd1 != NULL)
			{
				CWnd* pWnd2 = GetParent()->GetParent();
			}
			
			GetParent()->SendMessage(NM_CHECKSTATECHANGED, (WPARAM)hItem,(LPARAM)(nState == STATE_CHECKED ? 1 : 0));
		}
	}

	return bRet;
}

void CCheckTreeCtrl::SetChildrenItemCheck(HTREEITEM hItem)
{
	int nState = GetItemCheck(hItem);
	HTREEITEM hChildItem = GetChildItem(hItem);
	
	while (NULL != hChildItem)
	{
		SetItemCheck(hChildItem, nState);
		SetChildrenItemCheck(hChildItem);

		hChildItem = GetNextSiblingItem(hChildItem);
	}
}

void CCheckTreeCtrl::SetParentItemCheck(HTREEITEM hItem)
{
	HTREEITEM hParentItem = GetParentItem(hItem);
	if (NULL == hParentItem)
		return;

	HTREEITEM hSiblingItem = GetChildItem(hParentItem);
	int nState = GetItemCheck(hSiblingItem);

	hSiblingItem = GetNextSiblingItem(hSiblingItem);
	while (NULL != hSiblingItem)
	{
		if (nState != GetItemCheck(hSiblingItem))
		{
			nState = STATE_INTERMEDIATE;
			break;
		}

		hSiblingItem = GetNextSiblingItem(hSiblingItem);
	}

	SetItemCheck(hParentItem, nState);
	SetParentItemCheck(hParentItem);
}

int CCheckTreeCtrl::GetItemCheck(HTREEITEM hItem) const
{
	return (hItem == NULL)? STATE_NONE: (GetItemState(hItem, TVIS_STATEIMAGEMASK)>>12);
}

BOOL CCheckTreeCtrl::SetItemCheck(HTREEITEM hItem, int nState, BOOL bRecursion/* = FALSE*/)
{
	BOOL bRet = SetItemState(hItem, INDEXTOSTATEIMAGEMASK(nState), TVIS_STATEIMAGEMASK);
	if (!bRecursion || !bRet)
		return bRet;

	SetChildrenItemCheck(hItem);
	return TRUE;
}

void CCheckTreeCtrl::PreSubclassWindow()
{
	CTreeCtrl::PreSubclassWindow();

	m_ilStateImages.Create(IDB_STATE_IMAGES, 13, 1, RGB(255,255,255));
	SetImageList(&m_ilStateImages, TVSIL_STATE);
}

BOOL CCheckTreeCtrl::PreTranslateMessage(MSG* pMsg)
{
	if ((pMsg->message == WM_KEYDOWN) && (VK_SPACE == pMsg->wParam))
	{
		HTREEITEM hItem = GetSelectedItem();
		if (NULL != hItem)
		{
			int nState = GetItemCheck(hItem) + 1;

			if (nState > STATE_CHECKED/*STATE_INTERMEDIATE*/)
				nState = STATE_UNCHECKED;

			if (SetItemCheck(hItem, nState))
			{
				SetChildrenItemCheck(hItem);
				SetParentItemCheck(hItem);
			}
		}

		return TRUE;
	}

	return CTreeCtrl::PreTranslateMessage(pMsg);
}

上述会用到.bmp图片资源:IDB_STATE_IMAGES
CTreeCtrl树控件:如何给MFC中的CTreeCtrl树控件添加复选框及选中状态_第2张图片

3.控件的使用

//声明及初始化
CCheckTreeCtrl m_treeProLayers;

CRect treeViewRect;
GetDlgItem(IDC_TREE_LAYERS)->GetWindowRect(treeViewRect);   
ScreenToClient(treeViewRect);
m_treeProLayers.MoveWindow(treeViewRect);
m_treeProLayers.ShowWindow(SW_SHOW);

// 图层信息结构体
struct LAYER_INFO
{
	LAYER_INFO(){};
	~LAYER_INFO(){};
	LAYER_INFO& operator = (const LAYER_INFO& src)
	{
		strLayerName = src.strLayerName;
		strProjName = src.strProjName;
		strProjCode = src.strProjCode;
		strPath = src.strPath;
		subVecLayerInfo = src.subVecLayerInfo;

		return* this;
	};

	CString strLayerName;
	CString strProjName;
	CString strProjCode;
	CString strPath;

	std::vector<LAYER_INFO> subVecLayerInfo; // 子图层数据
};

typedef std::vector<LAYER_INFO> vecProjLayers;

// 树控件内容填充,为节点绑定数据
void CDatabaseDlg::SetTreeCtrlContent()
{
	// TODO: 在此添加控件通知处理程序代码
	CString strProjName;
	m_combProNames.GetWindowText(strProjName);
	m_sDataProName = strProjName;

	// 清空树节点
	m_treeProLayers.SetRedraw(FALSE);
	m_treeProLayers.DeleteAllItems();
	m_treeProLayers.SetRedraw(TRUE);
	m_treeProLayers.RedrawWindow();

	std::vector<LAYER_INFO> vecLayers;

	for (auto proItor = m_mapProjLayers.begin(); proItor != m_mapProjLayers.end(); ++proItor)
	{
		if (proItor->first.CompareNoCase(strProjName) == 0)
		{
			vecLayers = proItor->second;
			break;
		}
	}

	// 插入根节点 
	HTREEITEM hRoot = m_treeProLayers.InsertItem(strProjName,TVI_ROOT, TVI_FIRST);
	for (int i = 0; i < vecLayers.size();i++)
	{
		LAYER_INFO& pInfo = vecLayers.at(i);
		HTREEITEM hChildItem = m_treeProLayers.InsertItem(pInfo.strLayerName,hRoot);
		LAYER_INFO* player = new LAYER_INFO;

		player->strLayerName = pInfo.strLayerName;
		player->strPath = pInfo.strPath;
		player->strProjName = pInfo.strProjName;
		player->strProjCode = pInfo.strProjCode;
		m_treeProLayers.SetItemData(hChildItem,(DWORD)player);

		for (int j = 0; j < pInfo.subVecLayerInfo.size();j++)
		{
			AddTreeItemData(pInfo.subVecLayerInfo.at(j),hChildItem);
		}
	}

	// 展开所有树节点
	ExpandAllTreeItem(hRoot);
	m_treeProLayers.SetFocus();
}

// 增加叶子节点及信息
void CDatabaseDlg::AddTreeItemData(const LAYER_INFO& stuLayer,HTREEITEM& pParent)
{
	CString strLayerName = stuLayer.strLayerName;
	HTREEITEM hChildItem = m_treeProLayers.InsertItem(strLayerName,pParent);

	LAYER_INFO* player = new LAYER_INFO;
	player->strLayerName = strLayerName;
	player->strPath = stuLayer.strPath;
	player->strProjName = stuLayer.strProjName;
	player->strProjCode = stuLayer.strProjCode;
	m_treeProLayers.SetItemData(hChildItem,(DWORD)player);

	// 递归获取
	if (stuLayer.subVecLayerInfo.size() > 0)
	{
		for (int i = 0; i < stuLayer.subVecLayerInfo.size(); i++)
		{
			AddTreeItemData(stuLayer.subVecLayerInfo[i], hChildItem);
		}
	}
}
// 展开所有叶子节点
void CDatabaseDlg::ExpandAllTreeItem(HTREEITEM hTreeItem)
{
	if(!m_treeProLayers.ItemHasChildren(hTreeItem))
	{
		return;
	}

	//若树控件的根节点有子节点则获取根节点的子节点
	HTREEITEM hNextItem = m_treeProLayers.GetChildItem(hTreeItem);

	while (hNextItem)
	{
		//递归,展开子节点下的所有子节点
		ExpandAllTreeItem(hNextItem);
		hNextItem = m_treeProLayers.GetNextItem(hNextItem, TVGN_NEXT);
	}

	m_treeProLayers.Expand(hTreeItem,TVE_EXPAND);
}

// 如何获取选中的叶子节点对象及获取附加信息
void CDatabaseDlg::OnGetSelectTreeNode()
{
	// TODO: 在此添加控件通知处理程序代码
	std::vector<LAYER_INFO> vecLayerInfo;
	HTREEITEM hRootItem = m_treeProLayers.GetRootItem();
	
	// 如果根节点被选中,则只获取根节点自己的child节点,不再向下查找
	if (m_treeProLayers.GetItemCheck(hRootItem) == STATE_CHECKED)
	{
		HTREEITEM hSubItem = m_treeProLayers.GetChildItem(hRootItem);
		while(hSubItem)
		{
			LAYER_INFO* pLayerInfo = (LAYER_INFO *) m_treeProLayers.GetItemData(hSubItem);
			LAYER_INFO layerInfo = *pLayerInfo;
			vecLayerInfo.push_back(layerInfo);

			hSubItem = m_treeProLayers.GetNextSiblingItem(hSubItem);
		}
	}
	else if (m_treeProLayers.GetItemCheck(hRootItem) == STATE_INTERMEDIATE)
	{
		GetSelectTreeNodes(hRootItem,vecLayerInfo);
	}
	
	if (vecLayerInfo.size() == 0)
	{
		AfxMessageBox(_T("未选中任何图层树节点!"), MB_OK | MB_ICONINFORMATION);
		return;
	}
}

void CDatabaseDlg::GetSelectTreeNodes(HTREEITEM& hItem,std::vector<LAYER_INFO>& vecLayerInfo)
{
	while (hItem)
	{
		int nStatus = m_treeProLayers.GetItemCheck(hItem);
		// 子节点部分被选中
		if (nStatus == STATE_INTERMEDIATE)
		{
			HTREEITEM hSubItem = m_treeProLayers.GetChildItem(hItem);
			if (!hSubItem)
			{
				if (m_treeProLayers.GetCheck(hItem))
				{
					LAYER_INFO* pLayerInfo = (LAYER_INFO *) m_treeProLayers.GetItemData(hItem);
					LAYER_INFO layerInfo = *pLayerInfo;
					vecLayerInfo.push_back(layerInfo);
				}
			}

			// 递归
			while (hSubItem)
			{
				GetSelectTreeNodes(hSubItem,vecLayerInfo);
			}
		}
		// 子节点全部被选中,不再向下查找child节点
		else if(nStatus == STATE_CHECKED)
		{
			LAYER_INFO* pLayerInfo = (LAYER_INFO *) m_treeProLayers.GetItemData(hItem);
			LAYER_INFO layerInfo = *pLayerInfo;
			vecLayerInfo.push_back(layerInfo);
		}

		hItem = m_treeProLayers.GetNextSiblingItem(hItem);
	}
}

4、如果对树控件的叶子节点使用了SetItemData(),最后一定要释放掉内存,释放方式如下:

afx_msg void OnDeleteItem(NMHDR* pNMHDR, LRESULT* pResult);

BEGIN_MESSAGE_MAP(CPublicDatabaseDlg, CDialog)
	ON_WM_CLOSE()
	ON_NOTIFY(TVN_DELETEITEM, IDC_TREE_LAYERS, &CDatabaseDlg::OnDeleteItem)
END_MESSAGE_MAP()

void CDatabaseDlg::OnDeleteItem(NMHDR* pNMHDR, LRESULT* pResult)
{
	//reinterpret_cast 强制把 NMHDR类型的指针转换为LPNMTREEVIEW类型的指针。
	LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
	
	// TODO: 在此添加控件通知处理程序代码
	TVITEM& item = pNMTreeView->itemOld;
	if (item.lParam != 0) 
	{
		delete (struct LAYER_INFO*)item.lParam;
	}
	*pResult = 0;
}

你可能感兴趣的:(MFC,CTreeCtrl,树控件)