三态树节点有三种状态,分别对应:子节点全部选中、子节点部分选中、没有一个子节点被选中。
一.消息响应函数
这里需要对三个消息进行响应,分别是:NM_CLICK、WM_LBUTTONDOWN、WM_KEYDOWN,对应的响应函数分别是OnStateIconClick、OnLButtonDown和OnKeyDown。
1.OnLButtonDown
void Three_State_Tree_Ctrl::OnLButtonDown(UINT nFlags, CPoint point)
{
HTREEITEM hItem =HitTest(point, &uFlags_);
if ( (uFlags_&TVHT_ONITEMSTATEICON ))
{
//nState: 0->无选择钮 1->没有选择 2->部分选择 3->全部选择
UINT nState = GetItemState( hItem, TVIS_STATEIMAGEMASK ) >> 12;
nState=(nState==3)?1:3;
SetItemState( hItem, INDEXTOSTATEIMAGEMASK(nState), TVIS_STATEIMAGEMASK );
}
CTreeCtrl::OnLButtonDown(nFlags, point);
}
解释:
int HitTest( CPoint pt, UINT* pFlags = NULL ) const;第二个参数msdn的解释是:Variable that receives information about the results of a hit test.(接收函数返回的信息)。LVHT_ONITEMSTATEICON:The position is over the state image of a list-view item.(位置处于状态图片上时就会得到LVHT_ONITEMSTATEICON)。
GetItemState函数的返回值。首先,每个Item对应一个LVITEM结构,这个结构是如下定义的:
typedef struct _LVITEM { UINT mask; int iItem; int iSubItem; UINT state; UINT stateMask; LPTSTR pszText; int cchTextMax; int iImage; LPARAM lParam; #if (_WIN32_IE >= 0x0300) int iIndent; #endif #if (_WIN32_IE >= 0x560) int iGroupId; UINT cColumns; // tile view columns PUINT puColumns; #endif } LVITEM, *LPLVITEM;
只需关注里面的state变量就行了,GetItemState函数正是返回某Item的state参数值。关于state参数值,msdn有注解:Bits 12
through 15 of this member specify the state image index. If these bits are zero, the item has no state image. To
isolate these bits, use the LVIS_STATEIMAGEMASK mask. To set the state image index, use the INDEXTOSTATEIMAGEMASK
macro. The state image index specifies the index of the image in the state image list that should be drawn.
大意是:从12位到15位是留给状态图标索引(在状态图片list中,每个索引对应于一个图片)用的。要想设置或者得到状态信息,使用
LVIS_STATEIMAGEMASK,设置状态时,需要使用INDEXTOSTATEIMAGEMASK 宏。如果这些为为0的话,该item没有状态图片。
2.OnStateIconClick
void Three_State_Tree_Ctrl::OnStateIconClick(NMHDR* pNMHDR, LRESULT* pResult)
{
if(uFlags_&TVHT_ONITEMSTATEICON) *pResult=1;
else *pResult = 0;
}
关于NM_CLICK,当LRESULT为0时,表示单击treectrl时,执行系统默认的处理(默认处理是什么,我还不晓得);1代表不执行。
所以这段代码是说,当鼠标在treectrl的state image上左键单击的话,不执行默认处理(因为该做的,我们在OnLButtonDown里已
经做了)。
3.OnKeyDown
void Three_State_Tree_Ctrl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
//处理空格键
if(nChar==0x20)
{
HTREEITEM hItem =GetSelectedItem();
UINT nState = GetItemState( hItem, TVIS_STATEIMAGEMASK ) >> 12;
if(nState!=0)
{
nState=(nState==3)?1:3;
SetItemState( hItem, INDEXTOSTATEIMAGEMASK(nState), TVIS_STATEIMAGEMASK );
}
}
else CTreeCtrl::OnKeyDown(nChar, nRepCnt, nFlags);
}
这段代码就是当空格键被按下的时候,改变state image。和OnLButtonDown差不多。
二. SetItemState函数
这个函数被重写了。代码如下:
BOOL Three_State_Tree_Ctrl::SetItemState(HTREEITEM hItem, UINT nState, UINT nStateMask, BOOL bSearch)
{
BOOL bReturn=CTreeCtrl::SetItemState( hItem, nState, nStateMask );
UINT iState = nState >> 12;
if(iState!=0)
{
if(bSearch) TravelChild(hItem, iState);
TravelSiblingAndParent(hItem,iState);
}
return bReturn;
}
TravelChild函数做的是:当一个节点被选中的时候,当然要把它的子节点全部设为选中了,太天经地义了。TravelSiblingAndParent
函数做的是:一个节点被选中,就需要检查它的兄弟节点,判断是否大家都被选中了,都选中了,就需要设置它们的父节点被选中,介个也是
天经地义的。
三. TravelChild和TravelSiblingAndParent函数
由于代码注释得已经够清楚,给出代码足矣。
void Three_State_Tree_Ctrl::TravelChild(HTREEITEM hItem, int nState)
{
HTREEITEM hChildItem,hBrotherItem;
//查找子节点,没有就结束
hChildItem=GetChildItem(hItem);
if(hChildItem!=NULL)
{
//设置子节点的状态与当前节点的状态一致
CTreeCtrl::SetItemState( hChildItem, INDEXTOSTATEIMAGEMASK(nState), TVIS_STATEIMAGEMASK );
TravelChild(hChildItem, nState);
//处理子节点的兄弟节点
hBrotherItem=GetNextSiblingItem(hChildItem);
while (hBrotherItem)
{
//设置子节点的兄弟节点状态与当前节点的状态一致
int nState1 = GetItemState( hBrotherItem, TVIS_STATEIMAGEMASK ) >> 12;
if(nState1!=0)
{
CTreeCtrl::SetItemState( hBrotherItem, INDEXTOSTATEIMAGEMASK(nState), TVIS_STATEIMAGEMASK );
}
TravelChild(hBrotherItem, nState);
hBrotherItem=GetNextSiblingItem(hBrotherItem);
}
}
}
void Three_State_Tree_Ctrl::TravelSiblingAndParent(HTREEITEM hItem, int nState)
{
HTREEITEM hNextSiblingItem,hPrevSiblingItem,hParentItem;
//查找父节点,没有就结束
hParentItem=GetParentItem(hItem);
if(hParentItem!=NULL)
{
int nState1=nState;//设初始值,防止没有兄弟节点时出错
//查找当前节点下面的兄弟节点的状态
hNextSiblingItem=GetNextSiblingItem(hItem);
while(hNextSiblingItem!=NULL)
{
nState1 = GetItemState( hNextSiblingItem, TVIS_STATEIMAGEMASK ) >> 12;
if(nState1!=nState && nState1!=0) break;
else hNextSiblingItem=GetNextSiblingItem(hNextSiblingItem);
}
if(nState1==nState)
{
//查找当前节点上面的兄弟节点的状态
hPrevSiblingItem=GetPrevSiblingItem(hItem);
while(hPrevSiblingItem!=NULL)
{
nState1 = GetItemState( hPrevSiblingItem, TVIS_STATEIMAGEMASK ) >> 12;
if(nState1!=nState && nState1!=0) break;
else hPrevSiblingItem=GetPrevSiblingItem(hPrevSiblingItem);
}
}
if(nState1==nState || nState1==0)
{
nState1 = GetItemState( hParentItem, TVIS_STATEIMAGEMASK ) >> 12;
if(nState1!=0)
{
//如果状态一致,则父节点的状态与当前节点的状态一致
CTreeCtrl::SetItemState( hParentItem, INDEXTOSTATEIMAGEMASK(nState), TVIS_STATEIMAGEMASK );
}
//再递归处理父节点的兄弟节点和其父节点
TravelSiblingAndParent(hParentItem,nState);
}
else
{
//状态不一致,则当前节点的父节点、父节点的父节点……状态均为第三态
hParentItem=GetParentItem(hItem);
while(hParentItem!=NULL)
{
nState1 = GetItemState( hParentItem, TVIS_STATEIMAGEMASK ) >> 12;
if(nState1!=0)
{
CTreeCtrl::SetItemState( hParentItem, INDEXTOSTATEIMAGEMASK(2), TVIS_STATEIMAGEMASK );
}
hParentItem=GetParentItem(hParentItem);
}
}
}
}
四. 声明
代码基本是看做项目时直接从网上找的,并非本人自己所写,觉得有必要总结一下,顾写篇笔记而已。