三態選擇樹實現終結者 作者:南京銀山網路有限公司 王佳豪 下載本文樣本原始碼 範例程式碼運行如下:
在很多情況下,我們經常需要實現樹的多態選擇,如所示,當全部子節點選中的情況下,當前節點才被選中(示[荊門市]節點),當子節點部分選中時,當前節點處於第三態(示[湖北省]節點)當全部子節點未選中時,當前節點處於未選中的狀態(示[江蘇省]節點)。本文就介紹這種三態選擇樹的具體實現方法。 在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));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); 完成以上六步操作後,編譯、運行,用鍵盤空格鍵或按一下滑鼠CheckBox改變其狀態,您將看到不需要再增加任何代碼,已經實現了三態選擇樹的功能。如果需要隱藏某些選擇框,如根節點的選擇框,只需要設定對應的節點狀態為0即可: m_TripleTree.SetItemState( hRoot, INDEXTOSTATEIMAGEMASK(0), TVIS_STATEIMAGEMASK ); 上述代碼將設定根節點不顯示三態選擇框。 我具體實現的思想是以Windows標準的CTreeCtrl類為基類派生一個類CMutiTreeCtrl,截獲鍵盤和滑鼠點擊CheckBox的事件,在此訊息響應函數中,更改CheckBox的狀態,並搜尋子節點、兄弟節點和父節點,更改其狀態與上述邏輯一致。方法如下介紹: 一、 CTreeCtrl類為基類派生CMutiTreeCtrl類 class CMutiTreeCtrl : public CTreeCtrl{// Constructionpublic:CMutiTreeCtrl();// Attributespublic:// Operationspublic:// Overrides// ClassWizard generated virtual function overrides//{{AFX_VIRTUAL(CMutiTreeCtrl)//}}AFX_VIRTUAL// Implementationpublic:BOOL SetItemState( HTREEITEM hItem, UINT nState, UINT nStateMask, BOOL bSearch=TRUE);virtual ~CMutiTreeCtrl();// Generated message map functionsprotected://{{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_MSGDECLARE_MESSAGE_MAP()private:UINT m_uFlags;void TravelSiblingAndParent(HTREEITEM hItem, int nState);void TravelChild(HTREEITEM hItem,int nState);}; 二、重載CTreeCtrl的SetItemState()函數,在調用了基類的SetItemState()函數修改了節點狀態以後,遍曆一遍當前節點子節點、兄弟節點、父節點,按照上述邏輯修改為相應的狀態,實現三態顯示。調用此函數有二種情況: ①鍵盤或滑鼠輸入修改節點狀態,此時要遍曆全部父、兄、子節點; ②程式根據實際情況調用修改節點狀態,因為修改節點狀態時是判斷了全部子節點的狀態後得出了狀態,所以此時僅需要遍曆全部的兄、父節點,更改其狀態符合邏輯。故在重載的函數後面加了一個預設為TRUE的bSearch變數,當程式修改節點時請置此標誌為FALSE。 BOOL CMutiTreeCtrl::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;} 三、檢測按一下滑鼠節點CHeckBox的事件,更改對應的節點狀態並遍曆樹的其他節點。 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);}}}}好了,一切就是這麼簡單,如果你還不清楚的話,那就開啟工程看看吧,如你有什麼問題也不要忘記來信告訴我哦!最後祝大家學習愉快,多多交流,多多進步,一切順利! |