天天看點

VC/MFC之TreeCtrl控件使用經驗總結

樹形控件可以用于樹形的結構,其中有一個根接點(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

繼續閱讀