示例代碼運行效果圖如下:
在很多情況下,我們經常需要實現樹的多態選擇,如上圖所示,當全部子節點選中的情況下,當前節點才被選中(如圖示[荊門市]節點),當子節點部分選中時,當前節點處於第三態(如圖示[湖北省]節點)當全部子節點未選中時,當前節點處於未選中的狀態(如圖示[江蘇省]節點)。本文就介紹這種三態選擇樹的具體實現方法。
在VC知識庫第十九期中河南科技大學叢雷朋友也介紹了一種實現方法,兩種方法比較,本文介紹的方法實現簡單,兼容原CTreeCtrl的全部操作,CheckBox也是采用控件本身的CheckBox,只是在狀態顯示時重畫而已。因此,本方法可以實現表示三態的情況下同時顯示節點ICON圖標,另增加了對CheckBox在某些節點是否顯示的控制,同時增加了對鍵盤空格鍵選中、取消選中的控制。具體遍歷父、子節點的方法同叢雷朋友朋友的方法類似,也是遞歸實現全部節點的遍歷,只是優化了一些,效率更高。
下面介紹具體使用方法:
步驟一:生成一個對話框工程(示例工程CMutiTree)。
步驟二:添加樹控件,按照實際需要設置所需的屬性。
步驟三:做節點圖標和三態選擇框圖標
一般情況下節點圖標采用16×16,三態選擇圖標采用13×13大小比較合適。
三態選擇圖標對應: 0->無選擇鈕 1->沒有選擇 2->部分選擇 3->全部選擇
步驟四:將兩個文件[MutiTreeCtrl.cpp ,MutiTreeCtrl.h]添加到步驟一創建的對話框
工程中,在CMutiTreeDlg類的頭文件中增加對[MutiTreeCtrl.h]的包含,此時工程中增加了CMutiTreeCtrl類。
#include "MutiTreeCtrl.h"
步驟五:用ClassWizard在CmutiTreeDlg中創建一個樹控件CTreeCtrl的對象m_TripleTree,更改該對象為上面步驟四加入的CMutiTreeCtrl類的對象。
步驟六:在CMutiTreeDlg類中定義兩個CImageList 類的對象,用於加載CMutiTreeCtrl所需要的節點圖標列表和三態選擇框圖標列表。
在CMutiTreeDlg類的頭文件中:
CImageList m_imgList;
在對話框的初始化函數中:
CImageList m_imgState;m_imgState.Create(IDB_BITMAP_STATE,13, 1, RGB(255,255,255));
完成以上六步操作後,編譯、運行,用鍵盤空格鍵或鼠標單擊CheckBox改變其狀態,您將看到不需要再增加任何代碼,已經實現了三態選擇樹的功能。如果需要隱藏某些選擇框,如根節點的選擇框,只需要設置對應的節點狀態為0即可:
m_imgList.Create(IDB_BITMAP_LIST,16, 1, RGB(255,255,255));
m_TripleTree.SetImageList(&m_imgList,TVSIL_NORMAL);
m_TripleTree.SetImageList(&m_imgState,TVSIL_STATE);m_TripleTree.SetItemState( hRoot, INDEXTOSTATEIMAGEMASK(0),
上述代碼將設置根節點不顯示三態選擇框。
TVIS_STATEIMAGEMASK );
我具體實現的思想是以Windows標准的CTreeCtrl類為基類派生一個類CMutiTreeCtrl,截獲鍵盤和鼠標點擊CheckBox的事件,在此消息響應函數中,更改CheckBox的狀態,並搜索子節點、兄弟節點和父節點,更改其狀態與上述邏輯一致。方法如下介紹:
一、 CTreeCtrl類為基類派生CMutiTreeCtrl類
class CMutiTreeCtrl : public CTreeCtrl
二、重載CTreeCtrl的SetItemState()函數,在調用了基類的SetItemState()函數修改了節點狀態以後,遍歷一遍當前節點子節點、兄弟節點、父節點,按照上述邏輯修改為相應的狀態,實現三態顯示。調用此函數有二種情況:
{
// Construction
public:
CMutiTreeCtrl();
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CMutiTreeCtrl)
//}}AFX_VIRTUAL
// Implementation
public:
BOOL SetItemState( HTREEITEM hItem, UINT nState, UINT nStateMask, BOOL bSearch=TRUE);
virtual ~CMutiTreeCtrl();
// Generated message map functions
protected:
//{{AFX_MSG(CMutiTreeCtrl)
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnStateIconClick(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnKeydown(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
private:
UINT m_uFlags;
void TravelSiblingAndParent(HTREEITEM hItem, int nState);
void TravelChild(HTREEITEM hItem,int nState);
};
①鍵盤或鼠標輸入修改節點狀態,此時要遍歷全部父、兄、子節點;
②程序根據實際情況調用修改節點狀態,因為修改節點狀態時是判斷了全部子節點的狀態後得出了狀態,所以此時僅需要遍歷全部的兄、父節點,更改其狀態符合邏輯。故在重載的函數後面加了一個缺省為TRUE的bSearch變量,當程序修改節點時請置此標志為FALSE。BOOL CMutiTreeCtrl::SetItemState(HTREEITEM hItem, UINT nState,
三、檢測鼠標單擊節點CHeckBox的事件,更改對應的節點狀態並遍歷樹的其他節點。
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;
}
void CMutiTreeCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
HTREEITEM hItem =HitTest(point, &m_uFlags);
if ( (m_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);
}
void CMutiTreeCtrl::OnStateIconClick(NMHDR* pNMHDR, LRESULT* pResult)
{
if(m_uFlags&TVHT_ONITEMSTATEICON) *pResult=1;
else *pResult = 0;
}
四、檢測鍵盤按空格鍵的事件,更改對應的節點狀態並遍歷樹的其他節點。void CMutiTreeCtrl::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);
}
五、樹的遍歷用遞歸的方法搜索當前節點的父、兄、子節點
①遞歸搜索子節點void CMutiTreeCtrl::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 CMutiTreeCtrl::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);
}
}
}
}
好了,一切就是這麼簡單,如果你還不清楚的話,那就打開工程看看吧,如你有什麼問題也不要忘記來信告訴我哦!最後祝大家學習愉快,多多交流,多多進步,一切順利!