转自:http://www.vckbase.com/index.php/wv/1352
首先,我们要创建一个基本对话框的MFC工程MFC_TreeCRTL(名字随便给一个)。然后在资源视图中插入两个Dialog,ID分别为IDD_DIALOG11和IDD_DIALOG211,都更改Style属性为Child,Border属性为None,为它们建立两个类,分别命名为Cdialog11和Cdialog211,并在MFC_TreeCRTLDlg.CPP文件中包含dialog11.h和dialog211.h两个头文件。再导入几个资源图标作为树形控件节点的图标及装饰面板。最后在主面板上添加一个CTreeCtrl控件,ID为默认,并在ClassWizard中添加它的一个变量,命名为m_mytree。
接着,我们进行具体代码编写。
我们必须在CMFC_TreeCRTLDlg类中加入这些变量和函数
CDialog * m_treePages[2];
CString node_name;
BOOL InitMytree();
m_treePages[0]=new Cdialog11;
m_treePages[1]=new Cdialog211;
InitMytree()函数为m_mytree的初始化过程。(原文遗漏,若没有这句,不显示)在OnInitDialog里面加上 InitMytree(); //关键
BOOL CMFC_TreeCRTLDlg::InitMytree()
{
//节点的图标
int i=0;
int i_count=2;
//载入图标
HICON icon[4];
icon[0]=AfxGetApp()->LoadIcon (IDI_ICON6);
icon[1]=AfxGetApp()->LoadIcon (IDI_ICON7);
//创建图像列表控件
CImageList* m_imagelist=new CImageList;
m_imagelist->Create(16,16,0,7,7);
m_imagelist->SetBkColor (RGB(255,255,255));
for(int n=0; n < i_count; n++)
m_imagelist->Add(icon[n]);//把图标载入图像列表控件
m_mytree.SetImageList(m_imagelist,TVSIL_NORMAL); //为m_mytree设置一个图像列表,使CtreeCtrl的节点显示不同的图标
m_mytree.SetBkColor(RGB(0,250,255));//设置m_mytree的背景色
//创建节点
//父节点
HTREEITEM root0=m_mytree.InsertItem(L"Dialog1",0,1,TVI_ROOT,TVI_LAST);
HTREEITEM root1=m_mytree.InsertItem(L"Dialog2",0,1,TVI_ROOT,TVI_LAST);
//一层子节点
HTREEITEM sub_son0=m_mytree.InsertItem(L"Dialog 1-1",0,1,root0,TVI_LAST);
HTREEITEM sub_son1=m_mytree.InsertItem(L"Dialog 2-1",0,1,root1,TVI_LAST);
//二层孙子节点
HTREEITEM sub_m_son0=m_mytree.InsertItem(L"Dialog 2-1-1",0,1,sub_son1,TVI_LAST);
//建立节点对应的Dialog
m_treePages[0]->Create(IDD_DIALOG11,this);
m_treePages[1]->Create(IDD_DIALOG211,this);
m_treePages[0]->ShowWindow(SW_SHOW);
m_treePages[1]->ShowWindow(SW_HIDE);
//把Dialog移到合适位置
CRect m_rect;
GetClientRect(m_rect);
m_rect.left=200;
m_treePages[0]->MoveWindow(m_rect);
m_treePages[1]->MoveWindow(m_rect);
return true;
}
void CMFC_TreeCRTLDlg::OnTvnSelchangedTree1(NMHDR *pNMHDR, LRESULT *pResult)
{
//LPNMTREEVIEW pNMTreeView = reinterpret_cast(pNMHDR);
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
UpdateData(true);
CString node_name=m_mytree.GetItemText(pNMTreeView->itemNew.hItem);
//在标题栏显示节点信息
SetWindowText(node_name);
//切换面板
if(node_name=="Dialog 1-1"){
m_treePages[0]->ShowWindow(SW_SHOW);
m_treePages[1]->ShowWindow(SW_HIDE);
}
else if(node_name=="Dialog 2-1-1"){
m_treePages[0]->ShowWindow(SW_HIDE);
m_treePages[1]->ShowWindow(SW_SHOW);
}
UpdateData(false);
*pResult = 0;
}
树形控件TreeCtrl和下节要讲的列表控件 ListCtrl在系统中大量被使用,例如Windows资源管理器就是一个典型的例子。
关于创建及风格:
树形控件可以用于树形的结构,其中有一个根接点(Root)然后下面有许多子结点,而每个子结点上有允许有一个或多个或没有子结点。MFC中使用CTreeCtrl类来封装树形控件的各种操作。通过调用Create创建一个窗口:
BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );
dwStyle中可以使用以下一些树形控件的专用风格:
TVS_CHECKBOXES
4.70版:在每个item前面显示出复选框。必须为item关联了Image之后,复选框才能显示出来。设置这种风格的时候,要用DrawFrameControl创建并且设置两种状态的图片,一种是选中状态的图片,另一种是未选中时的图片。更多信息请查看Working with state image indexes.
在5.80版中,即使没有图片,也会显示复选框。
这种风格一旦创建,将不能移除。只能destroy后再create一个新的。
在创建treeview control之后,用SetWindowLong函数设置TVS_CHECKBOXES 即可。 TVS_DISABLEDRAGDROP
防止tree-view control发送TVN_BEGINDRAG消息。
TVS_EDITLABELS
允许用户修改item标签
TVS_FULLROWSELECT
允许选定整行。已选定的整行将高亮显示,点击这个item所在行的任意地方都将导致它被选中。这种风格不能与TVS_HASLINES并存。
TVS_HASBUTTONS
在父节点处显示(+)或(-)。用户可以点击这些按钮展开或者合并它的子节点。为了在tree-view的root处显示出按钮来,必须要用TVS_LINESATROOT.
TVS_HASLINES
用直线显示item之间的层次关系。
TVS_INFOTIP
通过发送TVN_GETINFOTIP得到功能提示信息。
TVS_LINESATROOT
用直线连接root处的item.如果没有TVS_HASLINES风格,这种风格将被忽略。
TVS_NOHSCROLL
不显示垂直滚动条。
TVS_NONEVENHEIGHT
让items之间的距离是不等的,否则就是等间距的.可用TVM_SETITEMHEIGHT设置高度。
TVS_NOSCROLL
无滚动条。.
TVS_NOTOOLTIPS
无提示
TVS_RTLREADING
按照从右到左的顺序显示文本。
TVS_SHOWSELALWAYS
当tree-view control失去焦点时,被选中的item仍然保留被选中状态。
TVS_SINGLEEXPAND
4.71版任何时刻只有一个item的child item被展开。如果用单击选中item并且这个item还没有展开的话,那么单击后它将被展开。如果选择item的时候用户按下了CTRL键,未被选中的item将不会自动收起。
5.80版将使被选中的item展开,未被选中的收起。如果按下了CTRL,未被选中的不会收起。
TVS_TRACKSELECT
允许跟踪
关于增加/删除
在树形控件中每一个结点都有一个句柄(HTREEITEM),同时添加结点时必须提供的参数是该结点的父结点句柄,(其中根Root结点只有一个,既不可以添加也不可以删除)利用InsertItem可以添加一个结点:
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;
//在根结点上添加Parent1
hItem = m_tree.InsertItem( "Parent1 ",TVI_ROOT);
//在Parent1上添加一个子结点
hSubItem = m_tree.InsertItem( "Child1_1 ",hItem);
//在Parent1上添加一个子结点,排在Child1_1后面
hSubItem = m_tree.InsertItem( "Child1_2 ",hItem,hSubItem);
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中图片为准。然后调用InsertItem添加结点:
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
关于插入标记
这是拖曳时经常用到的函数。
BOOL SetInsertMark( HTREEITEM hItem, BOOL fAfter = TRUE );
TRUE表示在hItem下面显示横杠,而FALSE则表示在上面。
同类函数还有:
SetInsertMarkColor,GetInsertMarkColor
关于得到/修改控件状态
此外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( );将删除所有结点。
如何展开/收缩一个父节点?
hItem | 要被扩展的tree项的句柄。 | ||||||||||||
nCode | 用来指示要被进行的动作的标志。这个标志可以是下列值之一:
|
关于遍历:
此外如果想遍历树可以使用下面的函数:
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有很多内容,将在以后的内容中进行详细讲解。
消息处理例程:
如何获得右击项句柄?
响应NM_RCLICK消息
void CLayerDialog::OnRclick(NMHDR* pNMHDR, LRESULT* pResult)
{
// TODO: Add your control notification handler code here
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
//右击获取所选项
CPoint point,p;
TVHITTESTINFO HitTestInfo;
GetCursorPos(&point);
m_treectrl.ScreenToClient(&point);
HitTestInfo.pt = point;
HTREEITEM h = m_treectrl.HitTest(&HitTestInfo);
if(h!=NULL)
{
。。。。。//需要代码
}
}
如何响应checkbox被单击?
响应NM_CLICK消息(checkbox就是分支前面的复选框,可从资源中修改属性添加)
void CLayerDialog::OnLclick(NMHDR *pNMHDR,LRESULT *pResult)
{
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
// TODO: Add your control notification handler code here
CPoint p;
GetCursorPos(&p);
m_treectrl.ScreenToClient(&p);
UINT nFlag;
HTREEITEM h=m_treectrl.HitTest(p,&nFlag);
if((h != NULL)&&(TVHT_ONITEMSTATEICON & nFlag))
{
。。。。。//需要代码
}
}
设置和获取checkbox的状态函数
GetCheck( )
SetCheck( )
如何知道某个点在CTreeCtrl上的位置
CTreeCtrl::HitTest
HTREEITEM HitTest( CPoint pt, UINT* pFlags );
HTREEITEM HitTest( TVHITTESTINFO* pHitTestInfo );
返回值:
返回位于指定点的tree view项的句柄,如果没有项位于该点,则返回NULL。
参数:
pt | in 要测试的点的客户坐标。 |
pFlags | out 指向一个用来接收有关点击测试的信息的整数的指针。它可以是说明部分中列出的flags成员值中的一个或多个。 其中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 在客户区域的右侧 |
pHitTestInfo | in/out 一个包含点击测试的位置并接收测试结果的信息的TVHITTESTINFO结构的地址。 typedef struct _TVHITTESTINFO { |
说明:
此成员函数用来确定相对于一个tree view控件的客户区的指定点的定位。
当调用这个函数时,pt参数指定要测试的点的坐标。此函数返回位于指定点的项的句柄,或者如果没有项位于该点则返回NULL。另外,pFlags参数包含了指明指定点的定位的值。
关于动态提供结点所显示的字符
首先你在添加结点时需要指明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的所有消息都需要在父窗口中处理)。
关于修改树控件的背景位图
对于Visual C++ MFC提供的标准树型控件CTreeCtrl来说,并不支持背景位图,所以如果需要实现背景位图就需要先让其在内存CDC对象上对TREEVIEW缺省绘图,然后在选择背景位图,与缺省位图合成,即采用贴图的方式,把标准的TREEVIEW窗口贴在底图上。这个操作在内存中完成。同时为了避免闪烁,必须重载OnItemexpanding()和OnItemexpanded()这两个函数。SetRedraw函数主要保证其不要在子节点弹出时重画,而是在子节点已经扩展后重画。为此,例程中定义了一个CTreeCtrl类的子类CmyTreeCtrl,并重载了以下几个成员函数:BOOL CMyTreeCtrl::SetBKImage(LPCTSTR LpszResource)
void CMyTreeCtrl::OnPaint()
void CMyTreeCtrl::OnItemexpanding(NMHDR* pNMHDR, LRESULT* pResult)
void CMyTreeCtrl::OnItemexpanded(NMHDR* pNMHDR, LRESULT* pResult)
BOOL CMyTreeCtrl::OnEraseBkgnd(CDC* pDC)
我的实践代码:
MFC:
关于变量:
在资源文件的主对话框上添加一个CTreeCtrl控件,选中此控件点击右键->CalassWizard->Member Variables 在这项中双击CTreeCtrl控件的ID,关联这个控件与一个CTreeCtrl变量(eg m_TreeCtrl)
关于属性设置:
增加 TreeCtrl 的 TVS_HASBUTTONS,TVS_HASLINES、TVS_LINESATROOT Style,代码如下:
DWORD dwStyle = GetWindowLong(m_TreeCtrl.m_hWnd,GWL_STYLE);
dwStyle |= TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT;
SetWindowLong(m_TreeCtrl.m_hWnd,GWL_STYLE,dwStyle);
如何插入一个节点:
HTREEITEM hRoot,hItem,hItem1,hItem2,hSubItem,hSubItem1;
hRoot = m_TreeCtrl.InsertItem("我的电脑");//并不是不是真正意义上的根节点,只是在视觉效果上看起来是
hItem = m_TreeCtrl.InsertItem( "Parent1 ",hRoot);
hSubItem = m_TreeCtrl.InsertItem("child1",hItem);
hSubItem1 = m_TreeCtrl.InsertItem("child2",hItem,hSubItem);
hItem1 = m_TreeCtrl.InsertItem( "Parent2 ",hRoot,hItem);
hItem2 = m_TreeCtrl.InsertItem( "Parent3 ",hRoot,hItem1);
效果图:
首先,构造出来ImageList :
CImageList* imageList=new CImageList();
imageList->Create(19, 19, ILC_COLOR24|ILC_MASK, 20, 1);
//参数意义:参看:http://blog.sina.com.cn/s/blog_4b3c1f950100b0eh.html
HBITMAP hBitmap = (HBITMAP)LoadImage(NULL,"D:\Project\TEMP\1.bmp",IMAGE_BITMAP,
19,19,LR_CREATEDIBSECTION | LR_DEFAULTSIZE | LR_LOADFROMFILE);
CBitmap* m_bitmap = new CBitmap();
m_bitmap->Attach(hBitmap);
imageList->Add(m_bitmap,RGB(0, 0, 0));//参数意义,下面有解释
然后,把ImageList和TreeCtrl关联起来:
m_TreeCtrl.SetImageList(imageList,TVSIL_NORMAL);
注:这个关联必须在m_TreeCtrl插入节点之前。
然后,在插入子节点时这样:
hSubItem = m_TreeCtrl.InsertItem("child1",0,1,hItem);
//添加节点 "child1",未选中时图标为imageList的第0个,选中后图标为imageList的第1个.
效果图:
待解答.....
?????有什么方法能在子节点的位置上不是只是一个字符串,而是插入一个其他的控件呢????
待解答.....
pbmImage | 指向包含一个或多个图象的位图的指针。图象数由位图宽推断。 |
pbmMask | 指向包含掩码的位图的指针。如果无掩码与图象列表一起使用,则此参数被忽略。 |
crMask | 生成掩码的颜色。指定位图中的此颜色的每个像素被改为黑色,掩码中的相应位数被设置为1。 |
hIcon | 包含新图象的位图和掩码的图标的句柄。 |
遍历例程:
以下是采用递归完成的遍历树的函数:
遍历树
//hitem:待遍历树的根节点
void TreeVisit(HTREEITEM hItem)
{
AfxMessageBox(GetItemText(hItem));
if(ItemHasChildren(hItem))
{
HTREEITEM hChildItem = GetChildItem(hItem);
while(hChildItem!=NULL)
{
TreeVisit(hChildItem); //递归遍历孩子节点
hChildItem = GetNextItem(hChildItem, TVGN_NEXT);
}
}
}
如何根据名称查找树中的某个节点(必须是节点名称是唯一的)
//item:待遍历树的根节点,strtext:待查找节点名称
HTREEITEM finditem(HTREEITEM item, CString strtext)
{
HTREEITEM hfind;
//空树,直接返回NULL
if(item == NULL)
return NULL;
//遍历查找
while(item!=NULL)
{
//当前节点即所需查找节点
if(GetItemText(item) == strtext)
return item;
//查找当前节点的子节点
if(ItemHasChildren(item))
{
item = GetChildItem(item);
//递归调用查找子节点下节点
hfind = finditem(item,strtext);
if(hfind)
{
return hfind;
}else //子节点中未发现所需节点,继续查找兄弟节点
item = GetNextSiblingItem(GetParentItem(item));
}
else{ //若无子节点,继续查找兄弟节点