首先,我们要创建一个基本对话框的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。
始初化完成后,我们要添加CTreeCtrl的消息响应事件,这样才能让它按我们的要求起作用。我们打开Class Wizard点选IDC_TREE1添加TVN_SELCHANGED消息,并在消息响应函数中写入代码。
在创建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( );将删除所有结点。
-
如何展开/收缩一个父节点?
- CTreeCtrl::Expand
BOOL Expand( HTREEETEM hItem, UINT nColor );
返回值:如果成功则返回非零值;否则返回0。
参数:
hItem |
要被扩展的tree项的句柄。 |
nCode |
用来指示要被进行的动作的标志。这个标志可以是下列值之一:
· |
TVE_COLLAPSE |
收缩列表。 |
· |
TVE_COLLAPSERESET |
收缩列表并删除子项。 |
· |
TVE_EXPAND |
展开列表。 |
· |
TVE_TOGGLE |
如果列表当前是展开的则收缩列表;反之则展开列表。 |
|
说明:
此成员函数用来展开或收缩给定父项的子项列表(如果有)。
关于遍历:
此外如果想遍历树可以使用下面的函数:
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 {
POINT pt;
UINT flags;
HTREEITEM hItem;
} TVHITTESTINFO, FAR* LPTVHITTESTINFO;
|
说明:
此成员函数用来确定相对于一个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的所有消息都需要在父窗口中处理)。
关于修改树控件的背景位图
![[转载]转 <wbr>树形控件TreeCtrl [转载]转 <wbr>树形控件TreeCtrl](http://image86.360doc.com/DownloadImg/2015/06/1500/54972915_1)
对于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);
效果图:
![[转载]转 <wbr>树形控件TreeCtrl [转载]转 <wbr>树形控件TreeCtrl](http://image86.360doc.com/DownloadImg/2015/06/1500/54972915_2)
现在我想往child1和child2前加上图标:
首先,构造出来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个.
效果图:
![[转载]转 <wbr>树形控件TreeCtrl [转载]转 <wbr>树形控件TreeCtrl](http://image86.360doc.com/DownloadImg/2015/06/1500/54972915_3)
?????怎么会是这样????怎么全部都加上了图标??
待解答.....
?????有什么方法能在子节点的位置上不是只是一个字符串,而是插入一个其他的控件呢????
待解答.....
GetWindowLong
函数功能:该函数获得有关指定窗口的信息,函数也获得在额外窗口内存中指定偏移位地址的32位度整型值。
函数原型:LONG GetWindowLong(HWND hWnd,int nlndex);
参数:
nlndex:指定要检索的基于0的偏移量。有效值的范围从0到窗口额外内存空间的字节数,减去4。例如,若指定了12位或多于12位的窗体类的额外存储空间,则应设为第三个32位整数的索引位8(12- 4=8)。要获得任意其他值,
指定下列值之一:
GWL_EXSTYLE= (-20) 扩展窗口样式 GWL_STYLE=(-16) 窗口样式
GWL_WNDPROC= (-4) 该窗口的窗口函数的地址 GWL_HINSTANCE= (-6) 拥有窗口的实例的句柄
GWL_HWNDPARENT= (-8) 该窗口之父的句柄。不要用SetWindowWord来改变这个值
GWL_ID= (-12) 对话框中一个子窗口的标识符 GWL_USERDATA = (-21) 含义由应用程序规定 DWL_DLGPROC = 4 这个窗口的对话框函数地址
DWL_MSGRESULT = 0 在对话框函数中处理的一条消息返回的值 DWL_USER = 8 含义由应用程序规定
返回值:
如果函数成功,返回值是所需的32位值;如果函数失败,返回值是0。若想获得更多错误信息请调用 GetLastError函数。
SetWindowLong
函数功能:SetWindowLong该函数改变指定窗口的属性.函数也将指定的一个32位值设置在窗口的额外存储空间的指定偏移位置。
函数原型:LONG SetWindowLong(HWND hWnd,int nlndex,LONG dwNewLong);
参数:
nlndex:指定将设定的大于等于0的偏移值。有效值的范围从0到额外类的存储空间的字节数-4:例如若指定了12位或多于12位的额外类存储空间,则应设为第三个32位整数的索引位8。
要设置其他任何值,可以指定下面值之一:
GWL_EXSTYLE:设定一个新的扩展风格。GWL_STYLE:设定一个新的窗口风格。
GWL_WNDPROC:为窗口过程设定一个新的地址。GWL_ID:设置一个新的窗口标识符。
GWL_HINSTANCE:设置一个新的应用程序实例句柄。
GWL_USERDATA:设置与窗口有关的32位值。每一个窗口均有一个由创建该窗口的应用程序使用的32位值。 当hWnd参数标识了一个对话框时,也可使用下列值:
DWL_MSGRESULT:设置在对话框过程中处理的消息的返回值。
DWL_USER:设置的应用程序私有的新的额外信息,例如一个句柄或指针。
dwNewLong:指定的替换值。
返回值:
如果函数成功,返回值是指定的32位整数的原来的值。如果函数失败,返回值为0。若想获得更多错误信息,请调用GetLastError函数。
备注:如果由hWnd参数指定的窗口与调用线程不属于同一进程,将导致SetWindowLong函数失败。
CImageList::Add
int Add( CBitmap* pbmImage, CBitmap* pbmMask );
int Add( CBitmap* pbmImage, COLORREF crMask );
int Add( HICON hIcon );
返回值:
如果成功,则为第一个新图象的基于零的索引,否则为-1。
参数:
pbmImage |
指向包含一个或多个图象的位图的指针。图象数由位图宽推断。 |
pbmMask |
指向包含掩码的位图的指针。如果无掩码与图象列表一起使用,则此参数被忽略。 |
crMask |
生成掩码的颜色。指定位图中的此颜色的每个像素被改为黑色,掩码中的相应位数被设置为1。 |
hIcon |
包含新图象的位图和掩码的图标的句柄。 |
说明:
调用此函数来添加一个或多个图象或图标到图象列表中。
什么是掩码图??
在ImageList.Create时如果选择ILC_MASK(既使用掩膜)。如果包含这个值,那么当前ImageList使用2个位图,指定一个单色位图,将其做为掩膜,掩膜是用来实现透明的,就是在显示的时候,会将这个单色位图与图片与PDC做一系列的“位或”“位与”等操作,最终实现透明。从单色位图的设置角度来看的话,如果单色位图“按位异或”图片得到的像素点颜色值为黑色,那么这个像素点就按透明处理。
掩码图就是是指的这一幅单色位图。
遍历例程:
以下是采用递归完成的遍历树的函数:
遍历树
//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));
}