示例代码运行效果图如下:
在很多情况下,我们经常需要实现树的多态选择,如上图所示,当全部子节点选中的情况下,当前节点才被选中(如图示[荆门市]节点),当子节点部分选中时,当前节点处于第三态(如图示[湖北省]节点)当全部子节点未选中时,当前节点处于未选中的状态(如图示[江苏省]节点)。本文就介绍这种三态选择树的具体实现方法。
在VC知识库第十九期中河南科技大学丛雷朋友也介绍了一种实现方法,两种方法比较,本文介绍的方法实现简单,兼容原CTreeCtrl的全部操作,CheckBox也是采用控件本身的CheckBox,只是在状态显示时重画而已。因此,本方法可以实现表示三态的情况下同时显示节点ICON图标,另增加了对CheckBox在某些节点是否显示的控制,同时增加了对键盘空格键选中、取消选中的控制。具体遍历父、子节点的方法同丛雷朋友朋友的方法类似,也是递归实现全部节点的遍历,只是优化了一些,效率更高。
下面介绍具体使用方法:
步骤一:生成一个对话框工程(示例工程CMutiTree)。
步骤二:添加树控件,按照实际需要设置所需的属性。
步骤三:做节点图标和三态选择框图标
一般情况下节点图标采用16×16,三态选择图标采用13×13大小比较合适。
三态选择图标对应: 0->无选择钮 1->没有选择 2->部分选择 3->全部选择
步骤四:将两个文件[MutiTreeCtrl.cpp ,MutiTreeCtrl.h]添加到步骤一创建的对话框
工程中,在CMutiTreeDlg类的头文件中增加对[MutiTreeCtrl.h]的包含,此时工程中增加了CMutiTreeCtrl类。
1.
#include "MutiTreeCtrl.h"
步骤五:用ClassWizard在CmutiTreeDlg中创建一个树控件CTreeCtrl的对象m_TripleTree,更改该对象为上面步骤四加入的CMutiTreeCtrl类的对象。
步骤六:在CMutiTreeDlg类中定义两个CImageList 类的对象,用于加载CMutiTreeCtrl所需要的节点图标列表和三态选择框图标列表。
在CMutiTreeDlg类的头文件中:
1.
CImageList m_imgList;
2.
CImageList m_imgState;
在对话框的初始化函数中:
1.
m_imgState.Create(IDB_BITMAP_STATE,13, 1, RGB(255,255,255));
2.
m_imgList.Create(IDB_BITMAP_LIST,16, 1, RGB(255,255,255));
3.
4.
m_TripleTree.SetImageList(&m_imgList,TVSIL_NORMAL);
5.
m_TripleTree.SetImageList(&m_imgState,TVSIL_STATE);
完成以上六步操作后,编译、运行,用键盘空格键或鼠标单击CheckBox改变其状态,您将看到不需要再增加任何代码,已经实现了三态选择树的功能。如果需要隐藏某些选择框,如根节点的选择框,只需要设置对应的节点状态为0即可:
1.
m_TripleTree.SetItemState( hRoot, INDEXTOSTATEIMAGEMASK(0),
2.
TVIS_STATEIMAGEMASK );
上述代码将设置根节点不显示三态选择框。
我具体实现的思想是以Windows标准的CTreeCtrl类为基类派生一个类CMutiTreeCtrl,截获键盘和鼠标点击CheckBox的事件,在此消息响应函数中,更改CheckBox的状态,并搜索子节点、兄弟节点和父节点,更改其状态与上述逻辑一致。方法如下介绍:
一、 CTreeCtrl类为基类派生CMutiTreeCtrl类
01.
class
CMutiTreeCtrl :
public
CTreeCtrl
02.
{
03.
// Construction
04.
public
:
05.
CMutiTreeCtrl();
06.
// Attributes
07.
public
:
08.
// Operations
09.
public
:
10.
// Overrides
11.
// ClassWizard generated virtual function overrides
12.
//{{AFX_VIRTUAL(CMutiTreeCtrl)
13.
//}}AFX_VIRTUAL
14.
// Implementation
15.
public
:
16.
BOOL
SetItemState( HTREEITEM hItem,
UINT
nState,
UINT
nStateMask,
BOOL
bSearch=TRUE);
17.
virtual
~CMutiTreeCtrl();
18.
// Generated message map functions
19.
protected
:
20.
//{{AFX_MSG(CMutiTreeCtrl)
21.
afx_msg
void
OnLButtonDown(
UINT
nFlags, CPoint point);
22.
afx_msg
void
OnStateIconClick(NMHDR* pNMHDR,
LRESULT
* pResult);
23.
afx_msg
void
OnKeydown(NMHDR* pNMHDR,
LRESULT
* pResult);
24.
afx_msg
void
OnKeyDown(
UINT
nChar,
UINT
nRepCnt,
UINT
nFlags);
25.
//}}AFX_MSG
26.
DECLARE_MESSAGE_MAP()
27.
private
:
28.
UINT
m_uFlags;
29.
void
TravelSiblingAndParent(HTREEITEM hItem,
int
nState);
30.
void
TravelChild(HTREEITEM hItem,
int
nState);
31.
};
二、重载CTreeCtrl的SetItemState()函数,在调用了基类的SetItemState()函数修改了节点状态以后,遍历一遍当前节点子节点、兄弟节点、父节点,按照上述逻辑修改为相应的状态,实现三态显示。调用此函数有二种情况:
①键盘或鼠标输入修改节点状态,此时要遍历全部父、兄、子节点;
②程序根据实际情况调用修改节点状态,因为修改节点状态时是判断了全部子节点的状态后得出了状态,所以此时仅需要遍历全部的兄、父节点,更改其状态符合逻辑。故在重载的函数后面加了一个缺省为TRUE的bSearch变量,当程序修改节点时请置此标志为FALSE。
01.
BOOL
CMutiTreeCtrl::SetItemState(HTREEITEM hItem,
UINT
nState,
02.
UINT
nStateMask,
BOOL
bSearch)
03.
{
04.
BOOL
bReturn=CTreeCtrl::SetItemState( hItem, nState, nStateMask );
05.
06.
UINT
iState = nState >> 12;
07.
if
(iState!=0)
08.
{
09.
if
(bSearch) TravelChild(hItem, iState);
10.
TravelSiblingAndParent(hItem,iState);
11.
}
12.
return
bReturn;
13.
}
三、检测鼠标单击节点CHeckBox的事件,更改对应的节点状态并遍历树的其他节点。
01.
void
CMutiTreeCtrl::OnLButtonDown(
UINT
nFlags, CPoint point)
02.
{
03.
HTREEITEM hItem =HitTest(point, &m_uFlags);
04.
if
( (m_uFlags&TVHT_ONITEMSTATEICON ))
05.
{
06.
//nState: 0->无选择钮 1->没有选择 2->部分选择 3->全部选择
07.
UINT
nState = GetItemState( hItem, TVIS_STATEIMAGEMASK ) >> 12;
08.
nState=(nState==3)?1:3;
09.
SetItemState(hItem,INDEXTOSTATEIMAGEMASK(nState),TVIS_STATEIMAGEMASK);
10.
}
11.
12.
CTreeCtrl::OnLButtonDown(nFlags, point);
13.
}
14.
void
CMutiTreeCtrl::OnStateIconClick(NMHDR* pNMHDR,
LRESULT
* pResult)
15.
{
16.
if
(m_uFlags&TVHT_ONITEMSTATEICON) *pResult=1;
17.
else
*pResult = 0;
18.
}
四、检测键盘按空格键的事件,更改对应的节点状态并遍历树的其他节点。
01.
void
CMutiTreeCtrl::OnKeyDown(
UINT
nChar,
UINT
nRepCnt,
UINT
nFlags)
02.
{
03.
//处理空格键
04.
if
(nChar==0x20)
05.
{
06.
HTREEITEM hItem =GetSelectedItem();
07.
UINT
nState = GetItemState( hItem, TVIS_STATEIMAGEMASK ) >> 12;
08.
if
(nState!=0)
09.
{
10.
nState=(nState==3)?1:3;
11.
SetItemState( hItem, INDEXTOSTATEIMAGEMASK(nState),
12.
TVIS_STATEIMAGEMASK );
13.
}
14.
}
15.
else
CTreeCtrl::OnKeyDown(nChar, nRepCnt, nFlags);
16.
}
五、树的遍历用递归的方法搜索当前节点的父、兄、子节点
①递归搜索子节点
01.
void
CMutiTreeCtrl::TravelChild(HTREEITEM hItem,
int
nState)
02.
{
03.
HTREEITEM hChildItem,hBrotherItem;
04.
05.
//查找子节点,没有就结束
06.
hChildItem=GetChildItem(hItem);
07.
if
(hChildItem!=NULL)
08.
{
09.
//设置子节点的状态与当前节点的状态一致
10.
CTreeCtrl::SetItemState(hChildItem,INDEXTOSTATEIMAGEMASK(nState),
11.
TVIS_STATEIMAGEMASK );
12.
//再递归处理子节点的子节点和兄弟节点
13.
TravelChild(hChildItem, nState);
14.
15.
//处理子节点的兄弟节点和其子节点
16.
hBrotherItem=GetNextSiblingItem(hChildItem);
17.
while
(hBrotherItem)
18.
{
19.
//设置子节点的兄弟节点状态与当前节点的状态一致
20.
int
nState1 = GetItemState( hBrotherItem, TVIS_STATEIMAGEMASK ) >> 12;
21.
if
(nState1!=0)
22.
{
23.
CTreeCtrl::SetItemState( hBrotherItem,
24.
INDEXTOSTATEIMAGEMASK(nState),TVIS_STATEIMAGEMASK );
25.
}
26.
//再递归处理子节点的兄弟节点的子节点和兄弟节点
27.
TravelChild(hBrotherItem, nState);
28.
hBrotherItem=GetNextSiblingItem(hBrotherItem);
29.
}
30.
}
31.
}
②递归搜索兄、父节点
01.
void
CMutiTreeCtrl::TravelSiblingAndParent(HTREEITEM hItem,
int
nState)
02.
{
03.
HTREEITEM hNextSiblingItem,hPrevSiblingItem,hParentItem;
04.
05.
//查找父节点,没有就结束
06.
hParentItem=GetParentItem(hItem);
07.
if
(hParentItem!=NULL)
08.
{
09.
int
nState1=nState;
//设初始值,防止没有兄弟节点时出错
10.
11.
//查找当前节点下面的兄弟节点的状态
12.
hNextSiblingItem=GetNextSiblingItem(hItem);
13.
while
(hNextSiblingItem!=NULL)
14.
{
15.
nState1 = GetItemState( hNextSiblingItem, TVIS_STATEIMAGEMASK ) >> 12;
16.
if
(nState1!=nState && nState1!=0)
break
;
17.
else
hNextSiblingItem=GetNextSiblingItem(hNextSiblingItem);
18.
}
19.
20.
if
(nState1==nState)
21.
{
22.
//查找当前节点上面的兄弟节点的状态
23.
hPrevSiblingItem=GetPrevSiblingItem(hItem);
24.
while
(hPrevSiblingItem!=NULL)
25.
{
26.
nState1 = GetItemState(hPrevSiblingItem,TVIS_STATEIMAGEMASK)>> 12;
27.
if
(nState1!=nState && nState1!=0)
break
;
28.
else
hPrevSiblingItem=GetPrevSiblingItem(hPrevSiblingItem);
29.
}
30.
}
31.
32.
if
(nState1==nState || nState1==0)
33.
{
34.
nState1 = GetItemState( hParentItem, TVIS_STATEIMAGEMASK ) >> 12;
35.
if
(nState1!=0)
36.
{
37.
//如果状态一致,则父节点的状态与当前节点的状态一致
38.
CTreeCtrl::SetItemState( hParentItem,
39.
INDEXTOSTATEIMAGEMASK(nState), TVIS_STATEIMAGEMASK );
40.
}
41.
//再递归处理父节点的兄弟节点和其父节点
42.
TravelSiblingAndParent(hParentItem,nState);
43.
}
44.
else
45.
{
46.
//状态不一致,则当前节点的父节点、父节点的父节点……状态均为第三态
47.
hParentItem=GetParentItem(hItem);
48.
while
(hParentItem!=NULL)
49.
{
50.
nState1 = GetItemState( hParentItem, TVIS_STATEIMAGEMASK ) >> 12;
51.
if
(nState1!=0)
52.
{
53.
CTreeCtrl::SetItemState( hParentItem,
54.
INDEXTOSTATEIMAGEMASK(2), TVIS_STATEIMAGEMASK );
55.
}
56.
hParentItem=GetParentItem(hParentItem);
57.
}
58.
}
59.
}
60.
}
好了,一切就是这么简单,如果你还不清楚的话,那就打开工程看看吧,如你有什么问题也不要忘记来信告诉我哦!最后祝大家学习愉快,多多交流,多多进步,一切顺利!
此文章来自于【http://www.vckbase.com/index.php/wv/466】