樹形控件可以用于樹形的結構,其中有一個根接點(Root)然後下面有許多子結點,而每個子結點上有允許有一個或多個或沒有子結點。MFC中使用CTreeCtrl類來封裝樹形控件的各種操作。通過調用BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );建立一個視窗,dwStyle中可以使用以下一些樹形控件的專用風格:
TVS_HASLINES 在父/子結點之間繪制連線
TVS_LINESATROOT 在根/子結點之間繪制連線
TVS_HASBUTTONS 在每一個結點前添加一個按鈕,用于表示目前結點是否已被展開
TVS_EDITLABELS 結點的顯示字元可以被編輯
TVS_SHOWSELALWAYS 在失去焦點時也顯示目前選中的結點
TVS_DISABLEDRAGDROP 不允許Drag/Drop
TVS_NOTOOLTIPS 不使用ToolTip顯示結點的顯示字元
在樹形控件中每一個結點都有一個句柄(HTREEITEM),同時添加結點時必須提供的參數是該結點的父結點句柄,(其中根Root結點隻有一個,既不可以添加也不可以删除)利用
HTREEITEM InsertItem( LPCTSTR lpszItem, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST );可以添加一個結點,pszItem為顯示的字元,hParent代表父結點的句柄,目前添加的結點會排在hInsertAfter表示的結點的後面,傳回值為目前建立的結點的句柄。
下面的代碼會建立一個如下形式的樹形結構:
+--- Parent1
+--- Child1_1
+--- Child1_2
+--- Child1_3
+--- Parent2
+--- Parent3
HTREEITEM hItem,hSubItem;
hItem = m_tree.InsertItem("Parent1",TVI_ROOT);在根結點上添加Parent1
hSubItem = m_tree.InsertItem("Child1_1",hItem);//在Parent1上添加一個子結點
hSubItem = m_tree.InsertItem("Child1_2",hItem,hSubItem);//在Parent1上添加一個子結點,排在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);
如果你希望在每個結點前添加一個小圖示,就必需先調用CImageList* SetImageList( CImageList * pImageList, int nImageListType );
CImageList指向一個CImageList對象,如果這個值為空,則CTreeCtrl中的Image将被移除。
nImageListType有兩種,TVSIL_NORMAL-包含選擇和被選擇兩個狀态的Image,TVSIL_STATE-使用者定義狀态的Image。
在調用完成後控件中使用圖檔以設定的ImageList中圖檔為準。然後調用
HTREEITEM InsertItem( LPCTSTR lpszItem, int nImage, int nSelectedImage, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST);添加結點,
nImage為結點沒被選中時所使用圖檔序号,nSelectedImage為結點被選中時所使用圖檔序号。
下面的代碼示範了ImageList的設定。
m_list.Create(IDB_TREE,16,4,RGB(0,0,0));
m_tree.SetImageList(&m_list,TVSIL_NORMAL);
m_tree.InsertItem("Parent1",0,1);//添加,選中時顯示圖示1,未選中時顯示圖示0
Example2:
CImageList imglist;
CBitmap bitmap;
imglist.Create(16, 16, ILC_MASK, 1, 1);
bitmap.LoadBitmap( IDB_COMPUTER );
imglist.Add(&bitmap, (COLORREF)0xFFFFFF);
bitmap.DeleteObject();
treectrl.SetImageList(&m_imgList, TVSIL_NORMAL);
此外CTreeCtrl還提供了一些函數用于得到/修改控件的狀态。
HTREEITEM GetSelectedItem( );将傳回目前選中的結點的句柄。BOOL SelectItem( HTREEITEM hItem );将選中指明結點。
BOOL GetItemImage( HTREEITEM hItem, int& nImage, int& nSelectedImage ) / BOOL SetItemImage( HTREEITEM hItem, int nImage, int nSelectedImage )用于得到/修改某結點所使用圖示索引。
CString GetItemText( HTREEITEM hItem ) /BOOL SetItemText( HTREEITEM hItem, LPCTSTR lpszItem );用于得到/修改某一結點的顯示字元。
BOOL DeleteItem( HTREEITEM hItem );用于删除某一結點,BOOL DeleteAllItems( );将删除所有結點。
此外如果想周遊樹可以使用下面的函數:
HTREEITEM GetRootItem( );得到根結點。
HTREEITEM GetChildItem( HTREEITEM hItem );得到子結點。
HTREEITEM GetPrevSiblingItem/GetNextSiblingItem( HTREEITEM hItem );得到指明結點的上/下一個兄弟結點。
HTREEITEM GetParentItem( HTREEITEM hItem );得到父結點。
樹形控件的消息映射使用ON_NOTIFY宏,形式如同:ON_NOTIFY( wNotifyCode, id, memberFxn ),wNotifyCode為通知代碼,id為産生該消息的視窗ID,memberFxn為處理函數,函數的原型如同void OnXXXTree(NMHDR* pNMHDR, LRESULT* pResult),其中pNMHDR為一資料結構,在具體使用時需要轉換成其他類型的結構。對于樹形控件可能取值和對應的資料結構為:
TVN_SELCHANGED 在所選中的結點發生改變後發送,所用結構:NMTREEVIEW
TVN_ITEMEXPANDED 在某結點被展開後發送,所用結構:NMTREEVIEW
TVN_BEGINLABELEDIT 在開始編輯結點字元時發送,所用結構:NMTVDISPINFO
TVN_ENDLABELEDIT 在結束編輯結點字元時發送,所用結構:NMTVDISPINFO
TVN_GETDISPINFO 在需要得到某結點資訊時發送,(如得到結點的顯示字元)所用結構:NMTVDISPINFO
關于ON_NOTIFY有很多内容,将在以後的内容中進行詳細講解。
關于動态提供結點所顯示的字元:首先你在添加結點時需要指明lpszItem參數為:LPSTR_TEXTCALLBACK。在控件顯示該結點時會通過發送TVN_GETDISPINFO來取得所需要的字元,在處理該消息時先将參數pNMHDR轉換為LPNMTVDISPINFO,然後填充其中item.pszText。但是我們通過什麼來知道該結點所對應的資訊呢,我的做法是在添加結點後設定其lParam參數,然後在提供資訊時利用該參數來查找所對應的資訊。下面的代碼說明了這種方法:
char szOut[8][3]={"No.1","No.2","No.3"};
//添加結點
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得到需要顯示的字元在數組中的位置
*pResult = 0;
}
關于編輯結點的顯示字元:首先需要設定樹形控件的TVS_EDITLABELS風格,在開始編輯時該控件将會發送TVN_BEGINLABELEDIT,你可以通過在處理函數中傳回TRUE來取消接下來的編輯,在編輯完成後會發送TVN_ENDLABELEDIT,在處理該消息時需要将參數pNMHDR轉換為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);//判斷是否已經取消取消編輯
m_tree.SetItemText(pTVDI->item.hItem,pTVDI->pszText);//重置顯示字元
*pResult = 0;
}
上面講述的方法所進行的消息映射必須在父視窗中進行(同樣WM_NOTIFY的所有消息都需要在父視窗中處理)。
文章轉自:http://blog.csdn.net/dongle2001/articles/429704.aspx
1.樹視圖風格:
TVS_HASBUTTONS; //在父項旁邊顯示(+)和(-)
TVS_HASLINES; //使用線條顯示各項之間的層次
TVS_LINESATROOT;//使用線條連結樹視圖控件根部各項
2.處理單擊事件:TVN_SELCHANGED
void CTreeCtrlDlg::OnTvnSelchangedTree1(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
// TODO: 在此添加控件通知處理程式代碼
HTREEITEM ht=m_treeCtrl.GetSelectedItem();
CString strSelect=m_treeCtrl.GetItemText(ht);
m_strTreeVal=strSelect;
UpdateData(FALSE);
*pResult = 0;
}
3.同時讓自己派生的CMyTreeCtrl類和對話框處理TVN_SELCHANGED消息:
隻須在CMyTreeCtrl中處理以下消息,并傳回FALSE就OK了ON_NOTIFY_REFLECT_EX(TVN_SELCHANGED, OnTvnSelchanged) OnTvnSelchanged的簽名如下
BOOL CMyTreeCtrl::OnTvnSelchanged(NMHDR *pNMHDR,LRESULT *pResult)
4.編輯标簽:要允許編輯樹視圖控件中的文本,可以設定以下三個步驟
(1).設定樹視圖的TVS_EDITLABELS風格
TVS_EDITLABE可以通過資源編輯框内部修改(Edit labels),也可以用代碼的方式修改,如下
long lStyle=::GetWindowLong(m_treeCtrl.GetSafeHwnd(),GWL_STYLE);
lStyle|=TVS_EDITLABELS;
::SetWindowLong(m_treeCtrl.GetSafeHwnd(),GWL_STYLE,lStyle)
(2).處理TVN_BEGINLABELEDIT通知消息
//設定相關限制,如限制編輯框字元串長度
CEdit*pEdit=GetEditControl(); //擷取目前選中結點編輯框
ASSERT(pEdit);
if (pEdit)
{
pEdit->LimitText(15);//設定編輯框文本長度為15個字元串
*pResult = 0;
}
(3).處理TVN_ENDLABLEEDIT通知消息
HTREEITEM hParent=GetParentItem(pTVDispInfo->item.hItem); //擷取父結點
HTREEITEM hChild=GetChildItem(hParent?hParent:TVI_ROOT); //擷取第一個根結點
hChild=GetNextSiblingItem(hChild); //擷取下一個兄弟節點
if (pTVDispInfo->item.hItem!=hChild) //判斷是否與目前節點相同
pTVDispInfo->item.pszText //擷取目前節點的字元串
CString strText=GetItemText(hChild); //擷取節點的字元串
5.讓樹視圖處理Esc和Enter鍵
重載PreTranslateMessage函數
BOOL bHandleMsg=FALSE;
switch(pMsg->message) {
case VK_ESCAPE:
case VK_RETURN:
if (::GetKeyState(VK_CONTROL)&0x8000)
{
break;
}
if (GetEditControl())
{
::TranslateMessage(pMsg);
::DispatchMessage(pMsg);
bHandleMsg=TRUE;
}
break;
}
4.實作上下文菜單
在WM_RBUTTONDOWN消息處理函數上實作上下文菜單
5.展開和收起樹視圖結點:
HTREEITEM hItem=GetRootItem(); //擷取根結點,可能會有多個根結點
ItemHasChildren(hParent) //判斷結點是否有孩子結點
hItem=GetChildItem(hParent); //擷取第一個子結點
hItem=GetNextSiblingItem(hItem)); //擷取下一個兄弟結點結點
Expand(hItem,bExpand?TVE_EXPAND:TVE_COLLAPSE);//展開/疊起結點
Select(hItem,TVGN_FIRSTVISIBLE); //設定選中結點
CString str=GetItemText(hChild); //擷取結點字元串資訊
HTREEITEM hCurrSel = GetSelectedItem(); //擷取目前選中結點
SelectItem(hNewSel);
HTREEITEM hNewSel = HitTest(pt, &nFlags); //判斷坐标是否在目前結點範圍内
HTREEITEM hItem=InsertItem(dlg.m_strItemText,hItemParent); //插入結點
#pragma once
//定義檔案MyTreeCtrl.h
// CMyTreeCtrl
class CMyTreeCtrl : public CTreeCtrl
{
DECLARE_DYNAMIC(CMyTreeCtrl)
public:
CMyTreeCtrl();
virtual ~CMyTreeCtrl();
protected:
DECLARE_MESSAGE_MAP()
void ExpandBranch(HTREEITEM hItem,BOOL bExpand =TRUE);
public:
void ExpandAllBranches(BOOL bExpand =TRUE);
BOOL DoesItemExist(HTREEITEM hItemParent, CString const& strItem);
afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
afx_msg void OnAddItem();
virtual BOOL PreTranslateMessage(MSG* pMsg);
afx_msg void OnTvnBeginlabeledit(NMHDR *pNMHDR, LRESULT *pResult);
afx_msg void OnTvnEndlabeledit(NMHDR *pNMHDR, LRESULT *pResult);
afx_msg BOOL OnTvnSelchanged(NMHDR *pNMHDR, LRESULT *pResult);
};
// MyTreeCtrl.cpp : 實作檔案
#include "stdafx.h"
#include "TreeCtrl.h"
#include "MyTreeCtrl.h"
#include ".mytreectrl.h"
#include "AddItemDlg.h"
// CMyTreeCtrl
IMPLEMENT_DYNAMIC(CMyTreeCtrl, CTreeCtrl)
CMyTreeCtrl::CMyTreeCtrl()
{
}
CMyTreeCtrl::~CMyTreeCtrl()
{
}
BEGIN_MESSAGE_MAP(CMyTreeCtrl, CTreeCtrl)
ON_WM_RBUTTONDOWN()
ON_COMMAND(IDR_ADD_ITEM, OnAddItem)
ON_NOTIFY_REFLECT(TVN_ENDLABELEDIT, OnTvnEndlabeledit)
ON_NOTIFY_REFLECT(TVN_BEGINLABELEDIT, OnTvnBeginlabeledit)
ON_NOTIFY_REFLECT_EX(TVN_SELCHANGED, OnTvnSelchanged)
END_MESSAGE_MAP()
// CMyTreeCtrl 消息處理程式
void CMyTreeCtrl::ExpandBranch(HTREEITEM hItem,BOOL bExpand )
//展開
{
if (ItemHasChildren(hItem)) //判斷是否有孩子結點
{
Expand(hItem,bExpand?TVE_EXPAND:TVE_COLLAPSE);
//展開/疊起結點
hItem=GetChildItem(hItem);//擷取第一個子結點
do{
ExpandBranch(hItem);
} while(hItem=GetNextSiblingItem(hItem));//擷取兄弟結點
}
}
void CMyTreeCtrl::ExpandAllBranches(BOOL bExpand )
{
HTREEITEM hItem=GetRootItem();//擷取根結點,可能會有多個根結點
do{
ExpandBranch(hItem,bExpand);
} while(hItem=GetNextSiblingItem(hItem));
Select(hItem,TVGN_FIRSTVISIBLE);//設定選中結點
}
BOOL CMyTreeCtrl::DoesItemExist(HTREEITEM hItemParent,
CString const& strItem)
{
BOOL bDoesItemExist=FALSE;
ASSERT(strItem.GetLength());
HTREEITEM hChild=GetChildItem(hItemParent?hItemParent:TVI_ROOT);
while (NULL!=hChild&&!bDoesItemExist)
{
CString str=GetItemText(hChild);//擷取結點字元串資訊
if (0==str.CompareNoCase(strItem))
{
bDoesItemExist=TRUE;
}
else
{
hChild=GetNextSiblingItem(hChild);
}
}
return bDoesItemExist;
}
void CMyTreeCtrl::OnRButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息處理程式代碼和/或調用預設值
// set focus to the tree control
SetFocus();
// map the point that is passed to the
// function from client coordinates
// to screen coordinates
ClientToScreen(&point);
// Get the currently selected item
HTREEITEM hCurrSel = GetSelectedItem();//擷取目前選中結點
// Figure out which item was right clicked
CPoint pt(0, 0);
::GetCursorPos(&pt);
ScreenToClient (&pt);
HTREEITEM hNewSel = HitTest(pt, &nFlags);
// Set the selection to the item that the
// mouse was over when the user right
// clicked
if (NULL == hNewSel)
{
SelectItem(NULL);
}
else if (hCurrSel != hNewSel)
{
SelectItem(hNewSel);
SetFocus();
}
// Load the context menu
CMenu Menu;
if (Menu.LoadMenu(IDM_CONTEXT_MENU))
{
CMenu* pSubMenu = Menu.GetSubMenu(0);
if (pSubMenu!=NULL)
{
// Display the context menu
pSubMenu->TrackPopupMenu(
TPM_LEFTALIGN | TPM_RIGHTBUTTON,
point.x, point.y, this);
}
}
}
void CMyTreeCtrl::OnAddItem()
//添加上下文菜單
{
// TODO: 在此添加指令處理程式代碼
HTREEITEM hItemParent=GetSelectedItem();
//擷取目前選中結點
CAddItemDlg dlg;
if (dlg.DoModal()==IDOK)
{
if (!DoesItemExist(hItemParent,dlg.m_strItemText))
{
HTREEITEM hItem=InsertItem(dlg.m_strItemText,hItemParent);
//插入結點
SelectItem(hItem);
}
else
{
AfxMessageBox("已存在相同結點");
}
}
}
BOOL CMyTreeCtrl::PreTranslateMessage(MSG* pMsg)
{
// TODO: 在此添加專用代碼和/或調用基類
BOOL bHandledMsg = FALSE;
switch (pMsg->message)
{
case WM_KEYDOWN:
{
switch (pMsg->wParam)
{
case VK_ESCAPE:
case VK_RETURN:
if (::GetKeyState(VK_CONTROL) & 0x8000)
{
break;
}
if (GetEditControl())
{
::TranslateMessage(pMsg);
::DispatchMessage(pMsg);
bHandledMsg = TRUE;
}
break;
default: break;
} // switch (pMsg->wParam)
} // WM_KEYDOWN
break;
default: break;
} // switch (pMsg->message)
// continue normal translation and dispatching
return (bHandledMsg ?TRUE : CTreeCtrl::PreTranslateMessage(pMsg));
}
void CMyTreeCtrl::OnTvnBeginlabeledit(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMTVDISPINFO pTVDispInfo = reinterpret_cast<LPNMTVDISPINFO>(pNMHDR);
// TODO: 在此添加控件通知處理程式代碼
*pResult=1;
CEdit*pEdit=GetEditControl();
ASSERT(pEdit);
if (pEdit)
{
pEdit->LimitText(15);
*pResult=0;
}
}
void CMyTreeCtrl::OnTvnEndlabeledit(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMTVDISPINFO pTVDispInfo = reinterpret_cast<LPNMTVDISPINFO>(pNMHDR);
// TODO: 在此添加控件通知處理程式代碼
BOOL bValidItem=FALSE;
CString strItem=pTVDispInfo->item.pszText;
if (0<strItem.GetLength())
{
HTREEITEM hParent=GetParentItem(pTVDispInfo->item.hItem);
bValidItem=!DoesItemExist(hParent,strItem);
}
*pResult = bValidItem;
}
BOOL CMyTreeCtrl::OnTvnSelchanged(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
// TODO: 在此添加控件通知處理程式代碼
TRACE(GetItemText(pNMTreeView->itemNew.hItem));
TRACE(" ");
*pResult = 0;
return FALSE; //傳回FALSE可以讓父視窗進行進一步的處理
}
文章轉自:http://hi.baidu.com/leafiness/blog/item/12fc87031c7c71753812bb05.html