樹形控件可以用于樹形的結(jié)構(gòu),其中有一個根接點(Root)然后下面有許多子結(jié)點,而每個子結(jié)點上有允許有一個或多個或沒有子結(jié)點。MFC中使用CTreeCtrl類來封裝樹形控件的各種操作。通過調(diào)用BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );創(chuàng)建一個窗口,dwStyle中可以使用以下一些樹形控件的專用風(fēng)格:
TVS_HASLINES 在父/子結(jié)點之間繪制連線
TVS_LINESATROOT 在根/子結(jié)點之間繪制連線
TVS_HASBUTTONS 在每一個結(jié)點前添加一個按鈕,用于表示當(dāng)前結(jié)點是否已被展開
TVS_EDITLABELS 結(jié)點的顯示字符可以被編輯
TVS_SHOWSELALWAYS 在失去焦點時也顯示當(dāng)前選中的結(jié)點
TVS_DISABLEDRAGDROP 不允許Drag/Drop
TVS_NOTOOLTIPS 不使用ToolTip顯示結(jié)點的顯示字符 在樹形控件中每一個結(jié)點都有一個句柄(HTREEITEM),同時添加結(jié)點時必須提供的參數(shù)是該結(jié)點的父結(jié)點句柄,(其中根Root結(jié)點只有一個,既不可以添加也不可以刪除)利用HTREEITEM InsertItem( LPCTSTR lpszItem, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST );可以添加一個結(jié)點,pszItem為顯示的字符,hParent代表父結(jié)點的句柄,當(dāng)前添加的結(jié)點會排在hInsertAfter表示的結(jié)點的后面,返回值為當(dāng)前創(chuàng)建的結(jié)點的句柄。下面的代碼會建立一個如下形式的樹形結(jié)構(gòu): +--- Parent1 +--- Child1_1 +--- Child1_2 +--- Child1_3 +--- Parent2 +--- Parent3 /*假設(shè)m_tree為一個CTreeCtrl對象,而且該窗口已經(jīng)創(chuàng)建*/ HTREEITEM hItem,hSubItem; hItem = m_tree.InsertItem("Parent1",TVI_ROOT); 在根結(jié)點上添加Parent1 hSubItem = m_tree.InsertItem("Child1_1",hItem); //在Parent1上添加一個子結(jié)點 hSubItem = m_tree.InsertItem("Child1_2",hItem,hSubItem);//在Parent1上添加一個子結(jié)點,排在Child1_1后面 hSubItem = m_tree.InsertItem("Child1_3",hItem,hSubItem); hItem = m_tree.InsertItem("Parent2",TVI_ROOT,hItem); hItem = m_tree.InsertItem("Parent3",TVI_ROOT,hItem); 如果你希望在每個結(jié)點前添加一個小圖標(biāo),就必需先調(diào)用CImageList* SetImageList( CImageList * pImageList, int nImageListType );指明當(dāng)前所使用的ImageList,nImageListType為TVSIL_NORMAL。在調(diào)用完成后控件中使用圖片以設(shè)置的ImageList中圖片為準(zhǔn)。然后調(diào)用
HTREEITEM InsertItem( LPCTSTR lpszItem, int nImage, int nSelectedImage, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST);添加結(jié)點,nImage為結(jié)點沒被選中時所使用圖片序號,nSelectedImage為結(jié)點被選中時所使用圖片序號。下面的代碼演示了ImageList的設(shè)置。 /*m_list 為CImageList對象 IDB_TREE 為16*(16*4)的位圖,每個圖片為16*16共4個圖標(biāo)*/ m_list.Create(IDB_TREE,16,4,RGB(0,0,0)); m_tree.SetImageList(&m_list,TVSIL_NORMAL); m_tree.InsertItem("Parent1",0,1);//添加, 選中時顯示圖標(biāo)1,未選中時顯示圖標(biāo)0
此外CTreeCtrl還提供了一些函數(shù)用于得到/修改控件的狀態(tài)。
HTREEITEM GetSelectedItem( );將返回當(dāng)前選中的結(jié)點的句柄。BOOL SelectItem( HTREEITEM hItem );將選中指明結(jié)點。
BOOL GetItemImage( HTREEITEM hItem, int& nImage, int& nSelectedImage ) / BOOL SetItemImage( HTREEITEM hItem, int nImage, int nSelectedImage )用于得到/修改某結(jié)點所使用圖標(biāo)索引。
CString GetItemText( HTREEITEM hItem ) /BOOL SetItemText( HTREEITEM hItem, LPCTSTR lpszItem );用于得到/修改某一結(jié)點的顯示字符。
BOOL DeleteItem( HTREEITEM hItem );用于刪除某一結(jié)點,BOOL DeleteAllItems( );將刪除所有結(jié)點。
此外如果想遍歷樹可以使用下面的函數(shù):
HTREEITEM GetRootItem( );得到根結(jié)點。
HTREEITEM GetChildItem( HTREEITEM hItem );得到子結(jié)點。
HTREEITEM GetPrevSiblingItem/GetNextSiblingItem( HTREEITEM hItem );得到指明結(jié)點的上/下一個兄弟結(jié)點。
HTREEITEM GetParentItem( HTREEITEM hItem );得到父結(jié)點。
樹形控件的消息映射使用ON_NOTIFY宏,形式如同:ON_NOTIFY( wNotifyCode, id, memberFxn ),wNotifyCode為通知代碼,id為產(chǎn)生該消息的窗口ID,memberFxn為處理函數(shù),函數(shù)的原型如同void OnXXXTree(NMHDR* pNMHDR, LRESULT* pResult),其中pNMHDR為一數(shù)據(jù)結(jié)構(gòu),在具體使用時需要轉(zhuǎn)換成其他類型的結(jié)構(gòu)。對于樹形控件可能取值和對應(yīng)的數(shù)據(jù)結(jié)構(gòu)為:
TVN_SELCHANGED 在所選中的結(jié)點發(fā)生改變后發(fā)送,所用結(jié)構(gòu):NMTREEVIEW
TVN_ITEMEXPANDED 在某結(jié)點被展開后發(fā)送,所用結(jié)構(gòu):NMTREEVIEW
TVN_BEGINLABELEDIT 在開始編輯結(jié)點字符時發(fā)送,所用結(jié)構(gòu):NMTVDISPINFO
TVN_ENDLABELEDIT 在結(jié)束編輯結(jié)點字符時發(fā)送,所用結(jié)構(gòu):NMTVDISPINFO
TVN_GETDISPINFO 在需要得到某結(jié)點信息時發(fā)送,(如得到結(jié)點的顯示字符)所用結(jié)構(gòu):NMTVDISPINFO 關(guān)于ON_NOTIFY有很多內(nèi)容,將在以后的內(nèi)容中進行詳細講解。
關(guān)于動態(tài)提供結(jié)點所顯示的字符:首先你在添加結(jié)點時需要指明lpszItem參數(shù)為:LPSTR_TEXTCALLBACK。在控件顯示該結(jié)點時會通過發(fā)送TVN_GETDISPINFO來取得所需要的字符,在處理該消息時先將參數(shù)pNMHDR轉(zhuǎn)換為LPNMTVDISPINFO,然后填充其中item.pszText。但是我們通過什么來知道該結(jié)點所對應(yīng)的信息呢,我的做法是在添加結(jié)點后設(shè)置其lParam參數(shù),然后在提供信息時利用該參數(shù)來查找所對應(yīng)的信息。下面的代碼說明了這種方法: char szOut[8][3]={"No.1","No.2","No.3"}; //添加結(jié)點 HTREEITEM hItem = m_tree.InsertItem(LPSTR_TEXTCALLBACK,...) m_tree.SetItemData(hItem, 0 ); hItem = m_tree.InsertItem(LPSTR_TEXTCALLBACK,...) m_tree.SetItemData(hItem, 1 ); //處理消息 void CParentWnd::OnGetDispInfoTree(NMHDR* pNMHDR, LRESULT* pResult) { TV_DISPINFO* pTVDI = (TV_DISPINFO*)pNMHDR; pTVDI->item.pszText=szOut[pTVDI->item.lParam];//通過lParam得到 需要顯示的字符在數(shù)組中的位置 *pResult = 0; }
關(guān)于編輯結(jié)點的顯示字符:首先需要設(shè)置樹形控件的TVS_EDITLABELS風(fēng)格,在開始編輯時該控件將會發(fā)送TVN_BEGINLABELEDIT,你可以通過在處理函數(shù)中返回TRUE來取消接下來的編輯,在編輯完成后會發(fā)送TVN_ENDLABELEDIT,在處理該消息時需要將參數(shù)pNMHDR轉(zhuǎn)換為LPNMTVDISPINFO,然后通過其中的item.pszText得到編輯后的字符,并重置顯示字符。如果編輯在中途中取消該變量為NULL。下面的代碼說明如何處理這些消息: //處理消息 TVN_BEGINLABELEDIT void CParentWnd::OnBeginEditTree(NMHDR* pNMHDR, LRESULT* pResult) { TV_DISPINFO* pTVDI = (TV_DISPINFO*)pNMHDR; if(pTVDI->item.lParam==0);//判斷是否取消該操作 *pResult = 1; else *pResult = 0; } //處理消息 TVN_BEGINLABELEDIT void CParentWnd::OnBeginEditTree(NMHDR* pNMHDR, LRESULT* pResult) { TV_DISPINFO* pTVDI = (TV_DISPINFO*)pNMHDR; if(pTVDI->item.pszText==NULL);//判斷是否已經(jīng)取消取消編輯 m_tree.SetItemText(pTVDI->item.hItem,pTVDI->pszText);//重置顯示字符 *pResult = 0; } 上面講述的方法所進行的消息映射必須在父窗口中進行(同樣WM_NOTIFY的所有消息都需要在父窗口中處理)。
TVS_HASLINES 在父/子結(jié)點之間繪制連線
TVS_LINESATROOT 在根/子結(jié)點之間繪制連線
TVS_HASBUTTONS 在每一個結(jié)點前添加一個按鈕,用于表示當(dāng)前結(jié)點是否已被展開
TVS_EDITLABELS 結(jié)點的顯示字符可以被編輯
TVS_SHOWSELALWAYS 在失去焦點時也顯示當(dāng)前選中的結(jié)點
TVS_DISABLEDRAGDROP 不允許Drag/Drop
TVS_NOTOOLTIPS 不使用ToolTip顯示結(jié)點的顯示字符 在樹形控件中每一個結(jié)點都有一個句柄(HTREEITEM),同時添加結(jié)點時必須提供的參數(shù)是該結(jié)點的父結(jié)點句柄,(其中根Root結(jié)點只有一個,既不可以添加也不可以刪除)利用HTREEITEM InsertItem( LPCTSTR lpszItem, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST );可以添加一個結(jié)點,pszItem為顯示的字符,hParent代表父結(jié)點的句柄,當(dāng)前添加的結(jié)點會排在hInsertAfter表示的結(jié)點的后面,返回值為當(dāng)前創(chuàng)建的結(jié)點的句柄。下面的代碼會建立一個如下形式的樹形結(jié)構(gòu): +--- Parent1 +--- Child1_1 +--- Child1_2 +--- Child1_3 +--- Parent2 +--- Parent3 /*假設(shè)m_tree為一個CTreeCtrl對象,而且該窗口已經(jīng)創(chuàng)建*/ HTREEITEM hItem,hSubItem; hItem = m_tree.InsertItem("Parent1",TVI_ROOT); 在根結(jié)點上添加Parent1 hSubItem = m_tree.InsertItem("Child1_1",hItem); //在Parent1上添加一個子結(jié)點 hSubItem = m_tree.InsertItem("Child1_2",hItem,hSubItem);//在Parent1上添加一個子結(jié)點,排在Child1_1后面 hSubItem = m_tree.InsertItem("Child1_3",hItem,hSubItem); hItem = m_tree.InsertItem("Parent2",TVI_ROOT,hItem); hItem = m_tree.InsertItem("Parent3",TVI_ROOT,hItem); 如果你希望在每個結(jié)點前添加一個小圖標(biāo),就必需先調(diào)用CImageList* SetImageList( CImageList * pImageList, int nImageListType );指明當(dāng)前所使用的ImageList,nImageListType為TVSIL_NORMAL。在調(diào)用完成后控件中使用圖片以設(shè)置的ImageList中圖片為準(zhǔn)。然后調(diào)用
HTREEITEM InsertItem( LPCTSTR lpszItem, int nImage, int nSelectedImage, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST);添加結(jié)點,nImage為結(jié)點沒被選中時所使用圖片序號,nSelectedImage為結(jié)點被選中時所使用圖片序號。下面的代碼演示了ImageList的設(shè)置。 /*m_list 為CImageList對象 IDB_TREE 為16*(16*4)的位圖,每個圖片為16*16共4個圖標(biāo)*/ m_list.Create(IDB_TREE,16,4,RGB(0,0,0)); m_tree.SetImageList(&m_list,TVSIL_NORMAL); m_tree.InsertItem("Parent1",0,1);//添加, 選中時顯示圖標(biāo)1,未選中時顯示圖標(biāo)0
此外CTreeCtrl還提供了一些函數(shù)用于得到/修改控件的狀態(tài)。
HTREEITEM GetSelectedItem( );將返回當(dāng)前選中的結(jié)點的句柄。BOOL SelectItem( HTREEITEM hItem );將選中指明結(jié)點。
BOOL GetItemImage( HTREEITEM hItem, int& nImage, int& nSelectedImage ) / BOOL SetItemImage( HTREEITEM hItem, int nImage, int nSelectedImage )用于得到/修改某結(jié)點所使用圖標(biāo)索引。
CString GetItemText( HTREEITEM hItem ) /BOOL SetItemText( HTREEITEM hItem, LPCTSTR lpszItem );用于得到/修改某一結(jié)點的顯示字符。
BOOL DeleteItem( HTREEITEM hItem );用于刪除某一結(jié)點,BOOL DeleteAllItems( );將刪除所有結(jié)點。
此外如果想遍歷樹可以使用下面的函數(shù):
HTREEITEM GetRootItem( );得到根結(jié)點。
HTREEITEM GetChildItem( HTREEITEM hItem );得到子結(jié)點。
HTREEITEM GetPrevSiblingItem/GetNextSiblingItem( HTREEITEM hItem );得到指明結(jié)點的上/下一個兄弟結(jié)點。
HTREEITEM GetParentItem( HTREEITEM hItem );得到父結(jié)點。
樹形控件的消息映射使用ON_NOTIFY宏,形式如同:ON_NOTIFY( wNotifyCode, id, memberFxn ),wNotifyCode為通知代碼,id為產(chǎn)生該消息的窗口ID,memberFxn為處理函數(shù),函數(shù)的原型如同void OnXXXTree(NMHDR* pNMHDR, LRESULT* pResult),其中pNMHDR為一數(shù)據(jù)結(jié)構(gòu),在具體使用時需要轉(zhuǎn)換成其他類型的結(jié)構(gòu)。對于樹形控件可能取值和對應(yīng)的數(shù)據(jù)結(jié)構(gòu)為:
TVN_SELCHANGED 在所選中的結(jié)點發(fā)生改變后發(fā)送,所用結(jié)構(gòu):NMTREEVIEW
TVN_ITEMEXPANDED 在某結(jié)點被展開后發(fā)送,所用結(jié)構(gòu):NMTREEVIEW
TVN_BEGINLABELEDIT 在開始編輯結(jié)點字符時發(fā)送,所用結(jié)構(gòu):NMTVDISPINFO
TVN_ENDLABELEDIT 在結(jié)束編輯結(jié)點字符時發(fā)送,所用結(jié)構(gòu):NMTVDISPINFO
TVN_GETDISPINFO 在需要得到某結(jié)點信息時發(fā)送,(如得到結(jié)點的顯示字符)所用結(jié)構(gòu):NMTVDISPINFO 關(guān)于ON_NOTIFY有很多內(nèi)容,將在以后的內(nèi)容中進行詳細講解。
關(guān)于動態(tài)提供結(jié)點所顯示的字符:首先你在添加結(jié)點時需要指明lpszItem參數(shù)為:LPSTR_TEXTCALLBACK。在控件顯示該結(jié)點時會通過發(fā)送TVN_GETDISPINFO來取得所需要的字符,在處理該消息時先將參數(shù)pNMHDR轉(zhuǎn)換為LPNMTVDISPINFO,然后填充其中item.pszText。但是我們通過什么來知道該結(jié)點所對應(yīng)的信息呢,我的做法是在添加結(jié)點后設(shè)置其lParam參數(shù),然后在提供信息時利用該參數(shù)來查找所對應(yīng)的信息。下面的代碼說明了這種方法: char szOut[8][3]={"No.1","No.2","No.3"}; //添加結(jié)點 HTREEITEM hItem = m_tree.InsertItem(LPSTR_TEXTCALLBACK,...) m_tree.SetItemData(hItem, 0 ); hItem = m_tree.InsertItem(LPSTR_TEXTCALLBACK,...) m_tree.SetItemData(hItem, 1 ); //處理消息 void CParentWnd::OnGetDispInfoTree(NMHDR* pNMHDR, LRESULT* pResult) { TV_DISPINFO* pTVDI = (TV_DISPINFO*)pNMHDR; pTVDI->item.pszText=szOut[pTVDI->item.lParam];//通過lParam得到 需要顯示的字符在數(shù)組中的位置 *pResult = 0; }
關(guān)于編輯結(jié)點的顯示字符:首先需要設(shè)置樹形控件的TVS_EDITLABELS風(fēng)格,在開始編輯時該控件將會發(fā)送TVN_BEGINLABELEDIT,你可以通過在處理函數(shù)中返回TRUE來取消接下來的編輯,在編輯完成后會發(fā)送TVN_ENDLABELEDIT,在處理該消息時需要將參數(shù)pNMHDR轉(zhuǎn)換為LPNMTVDISPINFO,然后通過其中的item.pszText得到編輯后的字符,并重置顯示字符。如果編輯在中途中取消該變量為NULL。下面的代碼說明如何處理這些消息: //處理消息 TVN_BEGINLABELEDIT void CParentWnd::OnBeginEditTree(NMHDR* pNMHDR, LRESULT* pResult) { TV_DISPINFO* pTVDI = (TV_DISPINFO*)pNMHDR; if(pTVDI->item.lParam==0);//判斷是否取消該操作 *pResult = 1; else *pResult = 0; } //處理消息 TVN_BEGINLABELEDIT void CParentWnd::OnBeginEditTree(NMHDR* pNMHDR, LRESULT* pResult) { TV_DISPINFO* pTVDI = (TV_DISPINFO*)pNMHDR; if(pTVDI->item.pszText==NULL);//判斷是否已經(jīng)取消取消編輯 m_tree.SetItemText(pTVDI->item.hItem,pTVDI->pszText);//重置顯示字符 *pResult = 0; } 上面講述的方法所進行的消息映射必須在父窗口中進行(同樣WM_NOTIFY的所有消息都需要在父窗口中處理)。