实现CTreeCtrl父子节点的联动选择
本文实现了下面的功能:
- 当选中父节点时, 其所有子节点全部选中.
- 当取消选中父节点时, 其所以子节点全部取消选中.
- 点击子节点时, 根据子节点与其兄弟节点的选中状态, 自动设置父节点的选中状态.
通过继承CTreeCtrl实现, 代码如下(注释已经写得很清楚了):
CustomTreeCtrl.h
#pragma once
// CCustomTreeCtrl
class CCustomTreeCtrl : public CTreeCtrl
{
DECLARE_DYNAMIC(CCustomTreeCtrl)
public:
CCustomTreeCtrl();
virtual ~CCustomTreeCtrl();
private:
// 设置子结点选中状态
void SetChildCheck(HTREEITEM hItem, BOOL bCheck);
// 设置父结点选中状态
void SetParentCheck(HTREEITEM hItem);
// 自定义消息处理
afx_msg LRESULT OnUserMsg(WPARAM wParam, LPARAM lParam);
protected:
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnNMClick(NMHDR *pNMHDR, LRESULT *pResult);
};
CustomTreeCtrl.cpp
// CustomTreeCtrl.cpp : implementation file
//
#include "stdafx.h"
#include "CustomTreeCtrl.h"
#define UM_CHANGE_PARENT_CHECK_STATUS (WM_USER+1011)
// CCustomTreeCtrl
IMPLEMENT_DYNAMIC(CCustomTreeCtrl, CTreeCtrl)
CCustomTreeCtrl::CCustomTreeCtrl()
{
}
CCustomTreeCtrl::~CCustomTreeCtrl()
{
}
BEGIN_MESSAGE_MAP(CCustomTreeCtrl, CTreeCtrl)
ON_NOTIFY_REFLECT(NM_CLICK, &CCustomTreeCtrl::OnNMClick)
ON_MESSAGE(UM_CHANGE_PARENT_CHECK_STATUS, &CCustomTreeCtrl::OnUserMsg)
END_MESSAGE_MAP()
// CCustomTreeCtrl message handlers
void CCustomTreeCtrl::OnNMClick(NMHDR *pNMHDR, LRESULT *pResult)
{
// 获取点击坐标
CPoint pt;
::GetCursorPos(&pt);
::ScreenToClient(m_hWnd, &pt);
UINT nFlags = 0;
HTREEITEM hItem = HitTest(pt, &nFlags);
if (hItem)
{
// 选中当前结点
this->SelectItem(hItem);
// 如果是点击的CheckBox
if (TVHT_ONITEMSTATEICON == (nFlags & TVHT_ONITEMSTATEICON))
{
// 获取当前选中状态(点击完成后的状态相反)
BOOL bChecked = GetCheck(hItem);
// 设置子结点选中状态
SetChildCheck(hItem, !bChecked);
// 设置父结点的状态
// 由于是当前点击的状态,而不是点击后的状态, 还未更新, 所以通过消息来设置
::PostMessage(m_hWnd, UM_CHANGE_PARENT_CHECK_STATUS, (WPARAM)hItem, 0);
}
}
*pResult = 0;
}
// 设置子结点选中状态
void CCustomTreeCtrl::SetChildCheck(HTREEITEM hItem, BOOL bCheck)
{
// 得到子结点
HTREEITEM hChildItem = GetChildItem(hItem);
while (hChildItem != NULL)
{
// 设置子结点选中状态
SetCheck(hChildItem, bCheck);
// 递归遍历孩子节点
SetChildCheck(hChildItem, bCheck);
// 对兄弟结点进行遍历
hChildItem = GetNextItem(hChildItem, TVGN_NEXT);
}
}
// 设置父结点选中状态
void CCustomTreeCtrl::SetParentCheck(HTREEITEM hItem)
{
// 得到父节点
HTREEITEM hParent = GetParentItem(hItem);
if (NULL != hParent)
{
// 记录父结点的状态
BOOL bParentIsChecked = TRUE;
// 检查父结点的所有子结点
HTREEITEM hChild = GetNextItem(hParent, TVGN_CHILD);
while (hChild)
{
// 如有任一子结点为非选中状态, 父节点不满足全选条件,置为unchecked
if (!GetCheck(hChild))
{
bParentIsChecked = FALSE;
break;
}
hChild = GetNextSiblingItem(hChild);
}
// 设置父结点的状态
SetCheck(hParent, bParentIsChecked);
// 递归检查父节点
SetParentCheck(hParent);
}
}
// 自定义消息处理
LRESULT CCustomTreeCtrl::OnUserMsg(WPARAM wParam, LPARAM lParam)
{
HTREEITEM hItem = (HTREEITEM)(wParam);
if (hItem)
{
SetParentCheck(hItem);
}
return 0L;
}
使用例子
// 定义
CCustomTreeCtrl m_wndTreeCtrl;
BOOL CTreeCtrlTest::OnInitDialog()
{
CDialogEx::OnInitDialog();
CRect rc;
GetClientRect(&rc);
// 在父/子结点之间绘制连线, 在根/子结点之间绘制连线, 在失去焦点时也显示当前选中的结点, 状态选择框, 在每一个结点前添加一个按钮用于表示当前结点是否已被展开
m_wndTreeCtrl.Create(WS_CHILD | WS_VISIBLE | TVS_HASLINES | TVS_LINESATROOT | TVS_SHOWSELALWAYS | TVS_CHECKBOXES | TVS_HASBUTTONS, rc, this, 123333);
for (size_t i = 0; i < 3; i++)
{
CString strRoot;
if (0 == i)
{
strRoot = _T("AA");
}
else if (1 == i)
{
strRoot = _T("BB");
}
else
{
strRoot = _T("CC");
}
HTREEITEM hRoot = m_wndTreeCtrl.InsertItem(strRoot);
for (int j = 0; j < 5; j++)
{
CString strChild;
strChild.Format(_T("%s_%02d"), strRoot.GetBuffer(), j);
HTREEITEM hChild = m_wndTreeCtrl.InsertItem(strChild, hRoot);
for (int k = 200; k < 210; k++)
{
CString strCSubChild;
strCSubChild.Format(_T("%s_%03d"), strChild.GetBuffer(), k);
HTREEITEM hSubChild = m_wndTreeCtrl.InsertItem(strCSubChild, hChild);
}
}
}
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}