学习VC中的树型控件CTreeCtrl

 

一、首先学习树型控件CTreeCtrl的TVINSERTSTRUCT结构

    TVINSERTSTRUCT

    包含添加新项到树形视控件所使用的信息。这个结构被tvm_insertitem消息使用。这个结构与tv_insertstruct结构是一样的,但它已经按当前的命名习惯重命名了。

    typedef struct tagtvinsertstruct

 {
       htreeitem hparent;
       htreeitem hinsertafter;
       #if (_win32_ie >= 0x0400)
             union
             {
                  tvitemex itemex;
                  tvitem item;
             } dummyunionname;
       #else
             tvitem item;
       #endif
  } tvinsertstruct, far *lptvinsertstruct;

  

   成员

hparent

       父项的句柄。如果这个成员的值是tvi_root或null,这项将被作为树形控件的根插入。

hinsertafter

       插入的新项之后的项的句柄。或是下列值之一:  
             tvi_first  在列表的开始插入项

             tvi_last   在列表的最后插入项

             tvi_root   作为一个根项添加

             tvi_sort   以字母顺序插入项 

item

      tvitem包含关于项添加的信息

   TVITEM

   指定或接收树形视项的属性。这个结构与tv_item结构一样,但它已经被当前命名协议重新命名了。新的应用程序应该使用这个结构。

   typedef struct tagtvitem

{
    uint      mask;
    htreeitem hitem;
    uint      state;
    uint      statemask;
    lptstr    psztext;
    int       cchtextmax;
    int       iimage;
    int       iselectedimage;
    int       cchildren;
    lparam    lparam;
} tvitem, far *lptvitem;

   成员

mask
指出其它的结构成员哪些包含有效数据的标记数组。当这个结构被 tvm_getitem消息使用时, mask成员指出项的属性被取回。这个成员可以是下列值的一个或多个。
tvif_children        cchildren成员是有效的。
tvif_di_setitem      树形视控件将保留支持信息并且不重新请求它。当处理 tvn_getdispinf通知时,这个标记是有效的。
tvif_handle           hitem成员有效。
tvif_image           iimage成员有效。
tvif_param           lparam成员有效。
tvif_selectedimage   iselectedimage成员有效。
tvif_state            statestatemask成员有效。
tvif_text            psztextcchtextmax成员有效。
hitem
这个函数引用的项。


state

     位标记和图像列表索引的设置,指出项的状态。当设置了一个项的状态,statemask成员指出这个成员的位是有效的。当取加一个项的状态时,这个成员返回statemask成员指出的位的当前状态。

     这个成员的0至7位包含了项的状态标记。关于可能的项状态标记,参见tree view control item states.

     覆盖图像覆盖在项的图标图像之上。这个成员的8至11位指定了以1为基准的覆盖图像索引。如果这些位是0,这个项没有覆盖图像。要隔离这些位,使用tvis_overlaymask掩码。要在这个成员中设置覆盖图像索引,使用indextooverlaymask宏。图像列表的覆盖图像是被imagelist_setoverlayimage函数设置的。

     一个状态图像是仅次于指出应用程序定义的状态的项的图标显示的。通过发送tvm_setimagelist消息来指定一个状态图像列表。要设置一个项的状态图像,在tvitem结构的statemask成员中包含tvis_stateimagemask值。结构的state成员的12至15位指定状态图像列表中被绘制图像的索引。

     要设置状态图像索引,使用indextostateimagemask。这个宏把一个索引适当的设置到12至15位上。要指出项没有状态图像,设置索引为0。这意味着在状态图像列表中的图像0不能被作为一个状态图像使用。要隔离state成员的位12至15,使用tvis_stateimagemask掩码。

statemask
state成员的位是有效的。如果你取回了一个项的状态,设置 statemask成员的位来指出 state成员中的这个位被返回。如果你设置了一个项的状态,设置 statemask成员的位来指出 state成员的这个位是你想设置的。要设置或取回一个项的覆盖图像的索引,设置tvis_overlaymask位。要设置和取回一个项的状态图像索引,设置tvis_stateimagemask位。


psztext
如果这个结构指定了项属性,那么这个成员是指向一个以空字符结束的字符串,包含有项的文本。如果这个成员是值lpstr_textcallback,那么父窗口为保存名字负责。既然这样,当树形视控件需要显示、保存或编辑项文本时,向父窗口发送 tvn_getdispinfo通过消息,当项文本改变时,发送 tvn_setdispinfo通知消息。
如果结构是取回项的属性,这个成员是取回项文本缓冲的地址。
cchtextmax
psztext成员指定缓冲的大小,以字符为单位。如果这个结构被使用来设置项属性,这个成员被忽略。


iimage
当项是在非选择状态中时,是树形控件的图像列表的索引。
如果这个成员是值i_imagecallback,父窗口为保存索引负责。既然这样,当树形视控件需要显示这个图像时,向父窗口发送 tvn_getdispinfo通知消息来获得索引。
iselectedimage
当项被选择时,是树形控件图像列表的索引。

     如果这个成员是值i_imagecallback,父窗口为保存索引负责。既然这样,当树形视控件需要显示这个图像时,向父窗口发送tvn_getdispinfo通知消息来获得索引。

cchildren
标记指出哪一个项有关联的子项。这个成员可以是下列值之一。
zero                这个项没有子项。
one                 这个项有一个或更多的子项。
                    the parent window keeps track of whether the item has child items. in this case, when
                    the tree view control needs to display the item, the control sends the parent a
                    tvn_getdispinfo btification message to determine whether the item has child items.
i_childrencallback  if the tree view control has the tvs_hasbuttons style, it uses this member to determine
                    whether to display the button indicating the presence of child items. you can use this
                    member to force the control to display the button even though the item does not have any
                    child items inserted. this allows you to display the button while minimizing the
                    control's memory usage by inserting child items only when the item is visible
                    or expanded.
lparam
与这项相关的32位值。

二、树形控件概述

      树形控件可以用于树形的结构,其中有一个根接点(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

/*假设m_tree为一个CTreeCtrl对象,而且该窗口已经创建*/
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 );指明当前所使用的ImageList,nImageListType为TVSIL_NORMAL。在调用完成后控件中使用图片以设置的ImageList中图片为准。然后调用
HTREEITEM InsertItem( LPCTSTR lpszItem, int nImage, int nSelectedImage, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST);添加结点,nImage为结点没被选中时所使用图片序号,nSelectedImage为结点被选中时所使用图片序号。下面的代码演示了ImageList的设置。
/*m_list 为CImageList对象
IDB_TREE 为16*(16*4)的位图,每个图片为16*16共4个图标*/
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


       此外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的所有消息都需要在父窗口中处理)。


三、待续。。。。  


 


=========================================================================

Checkboxes in a Tree Control

Abstract

"Checkboxes in a tree control" shows how to add normal and three-state checkboxes to a tree control, and how to get notified when the checkbox changes. The sample project uses MFC, but the technique can easily be adapted to a Win32 or an ATL/WTL application.

Overview

The TreeView common control supports checkboxes since version 4.70 (distributed originally with Internet Explorer 3.0), and enabling this is supposedly easy- choose one of three:

  • use the Checkboxes option in dialog resource editor
  • specify the TVS_CHECKBOXES window style bit when creating the tree
  • Use
      treeCtrl.SetWindowStyle(0, TVS_CHECKBOXES)
    in MFC applications, or
      SetWindowLong(hwndTree, GWL_STYLE, GetWindowLong(hwndTree, GWL_STYLE) | TVS_CHECKBOXES);
    InvalidateRect(hwndTree, NULL, true)

    for Win32 applications. This must be done before the tree control is filled with items.

However, there are a few problems asociated with this:

  • How do I detect when the checkbox is changed
  • How (and when!) do I retrieve the current check state
  • How to use Three-State Checkboxes.

 

Tree Control Images

The tree control uses the state image list to implement the checkboxes. When the TVS_CHECKBOXES style is set, the tree control creates a custom state image list with the two state image buttons - unchecked & checked. When clicking on the state image, the tree control cycles through the state images.

Tree Control images: The  tree control uses two image lists: First, the 'normal' image list, with the images as they are specified in CTreeCtrl::InsertItem as nImage or nSelectedImage. (These parameters are the same as the nImage / nSelectedImage members of the TVITEM struct.) Second, there is the State image list: State images are displayed left beside the normal image. The currently displayed item is controled to the item's state mask, bits 12..15. To add to the confusion, some of the state images can be overlayed onto the "normal" images.

Retrieving and Setting State Images

void TreeCtrl_SetStateImage(CTreeCtrl & tree, HTREEITEM item, int stateImage)
{
tree.SetItemState(item, INDEXTOSTATEIMAGEMASK(stateImage), TVIS_STATEIMAGEMASK);
}
int TreeCtrl_GetStateImage(CTreeCtrl & tree, HTREEITEM item)
{
return tree.GetItemState(item, TVIS_STATEIMAGEMASK) >> 12;
}

Note that 0 indicates no state image; so you can use bool isChecked = (TreeCtrl_GetStateImage(...) - 1) != 0.

 

Three-State Check boxes

For three-state checkboxes, we just need to replace the default created state image list with our custom image list, windows will figure out the number of check states from the length of the image list. Remember that the first image (index 0) is unused. The sample uses the following Bitmap resource (IDB_STATEIMG in the sample):

  • Add a member variable, CImageList m_ilState to the dialog
    (the image list must not be destroyed as long as the tree control is displayed)
  • In OnInitDialog, add the following:
    m_ilState.Create(IDB_STATEIMG /* resID of bitmap */,
                     16 /* width of single image */,    0 /* 0: the image list won't grow */,
                     RGB(255,255,255) /* transparent color */);
    m_treeCtrl.SetImageList(&m_ilState, TVSIL_STATE); // set state image list

The size of the bitmap affects the number of checkbox 'states'. To retrieve the current state, use TreeCtrl_GetStateImage(...) - 1. As with a checkbox, this value is 0 for unchecked, 1 for checked, and 2 for indeterminate.

This way, you could create a 'checkbox' with up to 15 states - which, on the other hand, ounds notlike fun for your users. More promising is the idea that you are not limited to checkboxes!

Being Notified when Checkbox changes

We want to know when the checkmark is clicked,so we can do something. The TreeView control does not send a separate notification, when item state changes, so we must manage both NM_CLICK wnd TVN_KEYDOWN message separately.

It's even trickier: When you get these notifications, the item state bits still indicates the old state. This wouldn't be as bad itself, but Microsoft might be tempted to change this behavior in the future. You can get the correct image (and thus, the current check state) when the current "Click" or "KeyDown"  message is completely handled. To do this, we can post a message to outselves, using PostMessage(some_unique_message_id, ..), and evaluating the new style there. With the message, I send the tree control ID in WPARAM (in case there are multiple tree controls in the dialog), and the item which has changed in LPARAM.

 

// .h:

#define UWM_TV_CHECKBOX WM_APP
class CDlgOrWhatever
{

CTreeCtrl m_tree;
afx_msg LRESULT OnTvCheckbox(WPARAM wp, LPARAM lp);
};
// .cpp
#include "windowsx.h"
BEGIN_MESSAGE_MAP(...)
ON_MESSAGE(UWM_TV_CHECKBOX, OnTvCheckbox)

END_MESSAGE_MAP()

void CCheckTreeDlg::OnClickTree1(NMHDR* pNMHDR, LRESULT* pResult)
{
DWORD dw = GetMessagePos();
CPoint p(GET_X_LPARAM(dw), GET_Y_LPARAM(dw));
m_tree.ScreenToClient(&p);
UINT htFlags = 0;
HTREEITEM it = m_tree.HitTest(p, &htFlags);
if (it != NULL && ( htFlags & TVHT_ONITEMSTATEICON)) {
PostMessage(UWM_TV_CHECKBOX, pNMHDR->idFrom, (LPARAM) it);
}
*pResult = 0;
}
LRESULT CCheckTreeDlg::OnTvCheckbox(WPARAM wp, LPARAM lp)
{
CTreeCtrl & tree = GetDlgItem(wp);
HTREEITEM hitem = (HTREEITEM) lp;
int checked = (tree.GetItemState(hitem, TVIS_STATEIMAGEMASK) >> 12) - 1;
CString s = tree.GetItemText(hitem);
if (checked==0) s += " unchecked";
else if (checked==1) s+= " checked";
else if (checked==2) s+= " dunno";
else s+= " ???";
SetDlgItemText(IDC_INFO, s);
return 0;
}

Notes:

19.2.2002: brushed up formatting. Fixed an alignment error in the resource bitmap 8the image on this page is still wrong, but that doesn#t mater much I guess). Added the TVN_KEYDOWN handler to the sample to catch the state change correctly when a key is hit.

你可能感兴趣的:(界面库WTL)