程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 三態選擇樹實現終結者

三態選擇樹實現終結者

編輯:關於VC++

示例代碼運行效果圖如下:

在很多情況下,我們經常需要實現樹的多態選擇,如上圖所示,當全部子節點選中的情況下,當前節點才被選中(如圖示[荊門市]節點),當子節點部分選中時,當前節點處於第三態(如圖示[湖北省]節點)當全部子節點未選中時,當前節點處於未選中的狀態(如圖示[江蘇省]節點)。本文就介紹這種三態選擇樹的具體實現方法。

在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
{
// 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);
};
二、重載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);
       }
     }
   }  
}

好了,一切就是這麼簡單,如果你還不清楚的話,那就打開工程看看吧,如你有什麼問題也不要忘記來信告訴我哦!最後祝大家學習愉快,多多交流,多多進步,一切順利!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved