实现CTreeCtrl父子节点的联动选择

本文实现了下面的功能:

  1. 当选中父节点时, 其所有子节点全部选中.
  2. 当取消选中父节点时, 其所以子节点全部取消选中.
  3. 点击子节点时, 根据子节点与其兄弟节点的选中状态, 自动设置父节点的选中状态.

通过继承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
}


posted on 2018-05-31 14:34  zxsh  阅读(1040)  评论(0编辑  收藏  举报

导航