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
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;
}