标题：CTreeCtrl
简介
树控制（CTreeCtrl）主要用来显示具有一定层次结构的数据项，如资源管理器中的磁盘目录等，以供用户在其中进行各种选择。树控制中的每个数据项包括数据项名称的文本字符串和用于表示该数据项的图像，每个数据项下面均可包含各种子项，整个结构就象目录树一样。对于包含各种子项的数据项，可通过鼠标双击来展开或合拢，这可以通过控制树的不同风格来实现树控制的不同显示形态。
详细资料
1.控件的建立CtreeCtrl&treeCtrl 建立树控制对象结构
Create 建立树控制并绑定对象
树控制CTreeCtrl::Create的调用格式如下：
BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );
其中参数dwStyle用来确定树控制的类型；rect用来确定树控制的大小和位置；pParentWnd用来确定树控制的父窗口，通用是一个对话框并且不能为NULL；nID用来确定树控制的标识。树控制的风格可以是下列值的组合：
TVS_HASLINES 表示树控制在各子项之间存在连线；
TVS_LINESATROOT 表示树控制在根项之间存在连线；
TVS_HASBUTTONS 表示树控制视在父项左侧存在展开合拢控制按钮；
TVS_EDITLABELS 表示可以控制鼠标单击修改树项的名称；
TVS_SHOWSELALWAYS 表示选中项即使在窗口失去输入焦点时仍然保持选中状态；
TVS_DISABLEDRAGDROP表示禁止树控制发送TVN_BEGINDRAG消息2.控件的属性树控制属性类包括取得树控制中项数GetCount、取得树控制中项相对于父项的偏移值GetIndent、取得树控制图像列表控制句柄GetImageList、设置树控制图像列表控制句柄SetImageList、取得匹配下一个树项GetNextItem、判断给定树项是否包含子项ItemHasChildren、取得树项子项GetChildItem、取得下一个同属树项GetNextSiblingItem、取得前一个同属树项GetPrevSiblingItem、取得父树项GetParentItem、取得第一个可视树项GetFirstVisibleItem、取得下一个可视树项GetNextVisible Item、取得前一个可视的树项GetPrevVisibleItem、取得被选中的树项GetSelectedItem、取得根树项GetRootItem、取得树项的属性GetItem、设置树项的属性SetItem、取得树项的状态GetItemState、设置树项的状态SetItemState、取得与树项关联图像GetItemImage、设置与树项关联图像SetItemImage、取得树项文本GetItemText、设置树项文本SetItemText和取得树项编辑控制句柄GetEditControl等。3.控件的操作方法树控制的操作方法包括插入一个树项InsertItem、删除一个树项DeleteItem、删除所有树项DeleteAllItems、展开或合拢树项的子项Expand、选中特定树项SelectItem、选择一个树项作为第一个可视树项SelectSetFirstVisible、编辑一个可视的树项EditLabel和排序给定父树项的子树项SortChildren等。4.控件的数据结构在使用树控制时需要了解两个个非常重要的数据结构TV_ITEM和TV_INSERTSTRUCT，前一个数据结构是用来表示树控制的树项信息，后一个数据结构是用来定义将树项增加到数据控制中所需要的数据内容。另外，还需要NM_TREEVIEW、TV_DISPINFO和TV_HITTESTINFO三个数据结构，这几个数据结构的定义方法如下：
①基本数据项结构
typedef struct _TV_ITEM {
UINT mask; //结构成员有效性屏蔽位
HTREEITEM hItem; //数据项控制句柄
UINT state; //数据项状态
UINT stateMask; //状态有效性屏蔽位
LPSTR pszText; //数据项名称字符串
int cchTextMax; //数据项名称的最大长度
int iImage; //数据项图标索引号
int iSelectedImage;//选中数据项图标索引号
int cChildren; //子项标识
LPARAM lParam; //程序定义的32位数据
} TV_ITEM, FAR *LPTV_ITEM;
②插入树项结构
typedef struct _TV_INSER TSTRUCT {
HTREEITEM hParent; //父项控制句柄
HTREEITEM hInsertAfter; //插入树项的位置
TV_ITEM item; //数据项的结构
} TV_INSERTSTRUCT, FAR *LPTV_INSERTSTRUCT;
其中插入的位置如果是TVI_FIRST 或TVI_LAST ，则分别插入到树控制的最前面或最后面，如果是TVI_SORT ，则插入的树项自动插入到合适的位置。
③树控制通知消息结构
typedef struct _NM_TREEVIEW {
NMHDR hdr; //通知消息句柄
UINT action; //通知消息标志
TV_ITEM itemOld; //原来的数据结构
TV_ITEM itemNew; //新的数据结构
POINT ptDrag; //拖动指针
} NM_TREEVIEW;
④取得或设置数据结构
typedef struct _TV_DISPINFO { tvdi
NMHDR hdr; //通知消息控制句柄
TV_ITEM item; //数据项结构
} TV_DISPINFO;
⑤指针测试数据结构
typedef struct _TVHITTESTINFO { tvhtst
POINT pt; //客户区域屏幕坐标指针
UINT flags; //存放测试结果的变量
HTREEITEM hItem; //测试的数据项结构
} TV_HITTESTINFO, FAR *LPTV_HITTESTINFO;
其中flags测试结果可以是如下值：
TVHT_ABOVE 在客户区域上面
TVHT_BELOW 在客户区域下面
TVHT_NOWHERE 在客户区域中并在最后一项下面
TVHT_ONITEM 在与树项关联的位图或标签内
TVHT_ONITEMBUTTON 在与树项关联的按钮上
TVHT_ONITEMICON 在与树项关联的位图上
TVHT_ONITEMINDENT 在与树项关联的联线上
TVHT_ONITEMLABEL 在与树项关联的标签上
TVHT_ONITEMRIGHT 在树项的右侧区域中
TVHT_ONITEMSTATEICON 在用户定义的状态图标上
TVHT_TOLEFT 在客户区域的左侧
TVHT_TORIGHT 在客户区域的右侧
控件应用示例
这里仍以基于对话框演示实例来具体介绍树控制及其和图像列表相结构的应用技巧：
通过“FILE->NEW->PROJECTS->MFC AppWizard(EXE)”建立名为VCTREE的工程，在建立过程中选择基于对话框（Dialog based）的应用；将对话框中的默认控件删除，并将所有对话框属性中的Language域设置为Chinese(P.R.C.),以使应用程序支持中文；建立两个图标IDI_PM和IDI_CJ，用来表示图标的选中和非选中状态，对于每个图标都应建立32X32和16X16两种大小，以保证程序的需要；在对话框窗口中添加树控制对象（TREE CONTROL）,并设置五个按钮“增加|删除|查看|排序|关闭”，其对应标识分别如下：
控制名称 标题名称 标识符号
树控制 IDC_TREECTRL
按钮 增 加 IDC_ADD
删 除 IDC_DEL
查 看 IDC_VIEW
排 序 IDC_SORT
关 闭 IDOK
选中树控制控件，选择“VIEW->ClassWizard->Member Variables。??DC_TREECTRL 引入成员变量，其变量类型为：
变量名 种类 变量类型
m_TreeCtrl Control CTreeCtrl
同时利用“MESSAGES MAP”为各命令按钮增加控制功能函数。
然后在代码文件VCTREEDlg.CPP中分别加入如下控制代码：
（1）在文件开始处增加图像列表定义
CImageList Cil1,Cil2;//大小图标像列表
（2）在初始化文件开始处增加代码
BOOL CVCTREEDlg::OnInitDialog()
{ CDialog::OnInitDialog();
.....//原来其它代码
// TODO: Add extra initialization here
// 此处开始增加代码
CVCTREEApp *pApp=(CVCTREEApp *)AfxGetApp();//创建图象列表
Cil1.Create(16,16,ILC_COLOR,2,2);
Cil1.Add(pApp->LoadIcon(IDI_PM));
Cil1.Add(pApp->LoadIcon(IDI_CJ));
m_TreeCtrl.SetImageList(&Cil1,TVSIL_NORMAL); //设置图象列表
DWORD dwStyles=GetWindowLong(m_TreeCtrl.m_hWnd,GWL_STYLE);//获取树控制原风格
dwStyles|=TVS_EDITLABELS|TVS_HASBUTTONS|TVS_HASLINES|TVS_LINESATROOT;
SetWindowLong(m_TreeCtrl.m_hWnd,GWL_STYLE,dwStyles);//设置风格
char * CJ[4]={"玉溪卷烟厂","云南卷烟厂","沈阳卷烟厂","成都卷烟厂"};//根数据名称
char * PM[4][5]={
{"红梅一","红梅二","红梅三","红梅四","红梅五"},//产品数据项
{"白梅一","白梅二","白梅三","白梅四","白梅五"},
{"绿梅一","绿梅二","绿梅三","绿梅四","绿梅五"},
{"青梅一","青梅二","青梅三","青梅四","青梅五"}};
int i,j;
HTREEITEM hRoot,hCur;//树控制项目句柄
TV_INSERTSTRUCT TCItem;//插入数据项数据结构
TCItem.hParent=TVI_ROOT;//增加根项
TCItem.hInsertAfter=TVI_LAST;//在最后项之后
TCItem.item.mask=TVIF_TEXT|TVIF_PARAM|TVIF_IMAGE|TVIF_SELECTEDIMAGE;//设屏蔽
TCItem.item.pszText="数据选择";
TCItem.item.lParam=0;//序号
TCItem.item.iImage=0;//正常图标
TCItem.item.iSelectedImage=1;//选中时图标
hRoot=m_TreeCtrl.InsertItem(&TCItem);//返回根项句柄
for(i=0;i<4;i++){//增加各厂家
TCItem.hParent=hRoot;
TCItem.item.pszText=CJ[i];
TCItem.item.lParam=(i+1)*10;//子项序号
hCur=m_TreeCtrl.InsertItem(&TCItem);
for(j=0;j<5;j++){//增加各产品
TCItem.hParent=hCur;
TCItem.item.pszText=PM[i][j];
TCItem.item.lParam=(i+1)*10+(j+1);//子项序号
m_TreeCtrl.InsertItem(&TCItem);
}
m_TreeCtrl.Expand(hCur,TVE_EXPAND);//展开树
}
m_TreeCtrl.Expand(hRoot,TVE_EXPAND);//展开上一级树
return TRUE; // return TRUE unless you set the focus to a control
}
增加树项功能的实现
在增加树项功能时，除了需要定义和设置插入树项的数据结构之外，还需要注意的是新增树项的名称初始时均为“新增数据”，增加后允许用户给数据项设置自定义名称。在编程时应特别注意m_TreeCtrl.EditLabel(hInsert);后面不能跟任何其它程序命令，否则这条编辑指令无效。
void CVCTREEDlg::OnAdd()
{ //增加子项功能函数
HTREEITEM hSel=m_TreeCtrl.GetSelectedItem();//取得选择项句柄
if(hSel==NULL) return;//无任何选项则返回
static int nAddNo=100;//编号大于100为新增数据
TV_INSERTSTRUCT TCItem;//定义插入项数据结构
TCItem.hParent=hSel; //设置父项句柄
TCItem.hInsertAfter=TVI_LAST;//在最后增加
TCItem.item.mask=TVIF_TEXT|TVIF_PARAM|TVIF_IMAGE|TVIF_SELECTEDIMAGE;//设屏蔽
TCItem.item.pszText="新增数据";
TCItem.item.lParam=nAddNo++;//索引号增加
TCItem.item.iImage=0;//正常图标
TCItem.item.iSelectedImage=1;//选中时图标
HTREEITEM hInsert=m_TreeCtrl.InsertItem(&TCItem);//增加
m_TreeCtrl.Expand(hSel,TVE_EXPAND);
m_TreeCtrl.EditLabel(hInsert);//修改增加的数据
}
删除树项功能的实现
在实现删除功能时，应对存在子项的树项进行提示，以警告用户是否连同其子项一起删除。
void CVCTREEDlg::OnDel()
{ //删除子项功能函数
HTREEITEM hSel=m_TreeCtrl.GetSelectedItem();//取得选项句柄;
if(hSel==NULL) return;//无任何选项则返回
if(m_TreeCtrl.ItemHasChildren(hSel))//判断是否有子项
if(MessageBox("厂家下存在品名,一同删除?","警告",MB_YESNO)==IDNO) return;
m_TreeCtrl.DeleteItem(hSel);
}
CTreeCtrl的用法
1.项目的信息处理BOOL CTreeCtrl::GetItem(TV_ITEM* pItem);BOOL CTreeCtrl::SetItem(TV_ITEM* pItem);BOOL CTreeCtrl::SetItem(HTREEITEM hItem,UINTnMask,LPCTSTR lpszItem,int Image,int nSelectedImage,UINT nState,UINT nStateMask,LPARAME lParam);2.项目的状态处理UINT CTreeCtrl::GetItemState(HTREEITEM hItem,UINT sStateMask)const;BOOL CTree Ctrl::SetItemState(HTREEITEM hItem,UINT nState,UINT nStateMask);3.项目的图形处理BOOL CTreeCtrl::GetItemImage(HTREEITEM hItem,int& nImage,int& nSelectedImage)const;BOOL CTreeCtrl::SetItemImage(HTREEITEM hItem,int nImage,int nSelectedImage);4. 项目的文本处理CString CTreeCtrl::GetItemText(HTREEITEM,hItem)const;BOOL CTreeCtrl::SetItemText(HTREEITEM hItem,LPCTSTR lpszItem);5. 查询项目的个数UINT CTreeCtrl::GetCount();6.查询父项目的句柄HTREEITEM CTreeCtrl::GetParentItem(HTREEITEM hItem);7.查询是否有子项BOOL CTreeCtrl::ItemHasChildren(HTREEITEM hItem);8.取得首子项的句柄HTREEITEM CTreeCtrl::GetChildItem(HTREEITEM hItem);9.查询前后的兄弟项HTREEITEM CTreeCtrl::GetPrevSiblingItem(HTREEITEM hItem);HTREEITEM CTreeCtrl::GetNextSiblingItem(HTREEITEM hItem);10.取得句柄HTREEITEM CTreeCtrl::GetSelectedItem();
HTREEITEM CTreeCtrl::GetRootItem();
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); //插入结点