对Win32程序来说,插入树控件项也不难,只需要向树控件发送TVM_INSERTITEM消息即可。下面是用Win32写出的等价代码(为了简明起见,去掉了对控件项图像的支持)在主窗口过程中加入如下代码:
HWND hwndTree;
TVINSERTSTRUCT tvinsert;
switch (message)
{
case WM_CREATE:
{ hwndTree = CreateWindow(WC_TREEVIEW, _T("TreeView"),
WS_CHILD | WS_VISIBLE | WS_BORDER | TVS_HASLINES | TVS_HASBUTTONS | TVS_LINESATROOT,
0,0,200,100, hWnd,(HMENU)1, hInst,NULL);
tvinsert.hParent = NULL;
tvinsert.hInsertAfter = TVI_LAST;
tvinsert.item.mask = TVIF_TEXT;
tvinsert.item.hItem = NULL;
tvinsert.item.state = 0;
tvinsert.item.stateMask = 0;
tvinsert.item.cchTextMax = 6;
tvinsert.item.cChildren = 0;
tvinsert.item.lParam = 0;
// top level
tvinsert.item.pszText = _T("Parent1");
HTREEITEM hDad = (HTREEITEM)SendMessage(hwndTree, TVM_INSERTITEM , 0 ,(LPARAM)&tvinsert);
tvinsert.item.pszText = _T("Parent2");
HTREEITEM hMom = (HTREEITEM)SendMessage(hwndTree, TVM_INSERTITEM , 0 ,(LPARAM)&tvinsert);
// second level
tvinsert.hParent = hDad;
tvinsert.item.pszText = _T("Children1-1");
SendMessage(hwndTree,TVM_INSERTITEM , 0 ,(LPARAM)&tvinsert);
tvinsert.item.pszText = _T("Children1-2");
SendMessage(hwndTree,TVM_INSERTITEM , 0 ,(LPARAM)&tvinsert);
// second level
tvinsert.hParent = hMom;
tvinsert.item.pszText = _T("Children2-1");
SendMessage(hwndTree, TVM_INSERTITEM , 0 ,(LPARAM)&tvinsert);
tvinsert.item.pszText = _T("Children2-2");
SendMessage(hwndTree, TVM_INSERTITEM , 0 ,(LPARAM)&tvinsert);
tvinsert.item.pszText = _T("Children2-3");
HTREEITEM hOther = (HTREEITEM)SendMessage(hwndTree, TVM_INSERTITEM , 0 ,(LPARAM)&tvinsert);
// third level
tvinsert.hParent = hOther;
tvinsert.item.pszText = _T("Children2-3-1");
SendMessage(hwndTree, TVM_INSERTITEM , 0 ,(LPARAM)&tvinsert);
tvinsert.item.pszText = _T("Children2-3-2");
SendMessage(hwndTree, TVM_INSERTITEM , 0 ,(LPARAM)&tvinsert);
break;
}
case: WM_COMMAND:
…
…
break;
other case …
… …
return 0;
}
此外还要添加 InitCommonControls();函数,它的头文件为:commctrl.h,在工程设置链接选项卡中的“对象/库模块”中添加comctrl32.lib。这样便可顺利通过编译和链接。
2007年5月26日土曜日
树控件消息
树控件一共有43个控制消息(TVM_XXX)和23个通知消息(TVN_XXX)下面按操作的不同进行简单的说明。
一、 一、拖拽消息
拖拽(drag-drop)操作包含这样几步:
1. 1。选中一个节点后,按住鼠标左键(此时是左键拖拽)开始拖动鼠标时,树控件向其父窗口发送TVN_BEGINDRAG通知消息(通过此消息可以得到被拖拽节点的句柄)。此时,需要准备在拖拽操作时所显示的拖拽图标。那么需要向树控件发送TVM_CREATEDRAGIMAGE消息,它创建一个Image List对象,并把被选中节点的图标复制到这个Image List中,并返回Image List句柄。然后使用ImageList_BeginDrag以及ImageList_DragEnter宏对这个Image List做一些设置,比如拖拽时鼠标所处的作标点。
case TVN_BEGINDRAG:
{
POINT pt;
pt.x = 0;
pt.y = 0;
ClientToScreen(GetDlgItem(hWnd, ID_TREEVIEW), &pt);
NMTREEVIEW *lpnmtv = (NMTREEVIEW*) lParam;
hitemDrag = lpnmtv->itemNew.hItem;
hitemDrop = NULL;
himge = (HIMAGELIST)SendMessage(GetDlgItem(hWnd, ID_TREEVIEW), TVM_CREATEDRAGIMAGE,0,(LPARAM)(HTREEITEM)lpnmtv->itemNew.hItem);
if(!himge)
break;
bdragging = TRUE;
ImageList_BeginDrag(himge, 0, pt.x ,pt.y );
POINT point = lpnmtv->ptDrag;
ClientToScreen(GetDlgItem(hWnd, ID_TREEVIEW), &point);
ImageList_DragEnter(GetDlgItem(hWnd, ID_TREEVIEW),point.x ,point.y );
SetCapture(hWnd);
break;
}
2.2。 拖拽过程中会产生一系列的WM_MOUSEMOVE消息,因此要在这个消息的处理例程中要确定拖拽图标的位置(ImageList_DragMove())以及拖拽目标的位置。对于拖拽目标的确定,一般使用TVM_HITTEST消息来获得拖拽目标节点的句柄。并把这个节点加亮显示(使用带TVGN_DROPHILITE标记的TVM_SELECTITEM消息可以加亮显示拖拽节点)。
case WM_MOUSEMOVE:
{
TVHITTESTINFO tvht;
UINT flags;
flags = (UINT)wParam;
if(bdragging){
pos.x = LOWORD(lParam);
pos.y = HIWORD(lParam);
ClientToScreen(GetDlgItem(hWnd, ID_TREEVIEW), &pos);
ImageList_DragMove(pos.x, pos.y);
ImageList_DragShowNolock(FALSE);
ScreenToClient(GetDlgItem(hWnd, ID_TREEVIEW), &pos);
tvht.pt.x = pos.x;
tvht.pt.y = pos.y;
if(hitemDrop = (HTREEITEM)SendMessage(GetDlgItem(hWnd, ID_TREEVIEW),
TVM_HITTEST, (WPARAM)&flags ,(LPARAM)&tvht))
{
SendMessage(GetDlgItem(hWnd, ID_TREEVIEW), TVM_SELECTITEM,
TVGN_DROPHILITE,(LPARAM)hitemDrop);
}
ImageList_DragShowNolock(TRUE);
}
break;
}
3.3。 最后选定拖拽目标节点后,释放鼠标键产生WM_LBUTTONUP消息。在这里,Image List已完成它的任务,应该释放它(ImageList_DragLeave 、ImageList_EndDrag)。然后将源节点插入到目标节点处,并删除源节点。
case WM_LBUTTONUP:
{
if (bdragging){
bdragging = FALSE;
ImageList_DragLeave(GetDlgItem(hWnd, ID_TREEVIEW));
ImageList_EndDrag();
ReleaseCapture();
if( hitemDrag == hitemDrop )
break;
HTREEITEM htiParent = hitemDrop;
while( (htiParent = (HTREEITEM)SendMessage(GetDlgItem(hWnd, ID_TREEVIEW),
TVM_GETNEXTITEM,
(WPARAM)(UINT)TVGN_PARENT,
(LPARAM)htiParent )) != NULL )
{
if( htiParent == hitemDrag )
break;
}
DeleteObject(himge);
SendMessage(GetDlgItem(hWnd, ID_TREEVIEW), TVM_EXPAND,
(WPARAM) (UINT)TVE_EXPAND, (LPARAM)hitemDrop);
HTREEITEM htiNew = CopyBranch(GetDlgItem(hWnd, ID_TREEVIEW),
hitemDrag, hitemDrop, TVI_LAST );
SendMessage(GetDlgItem(hWnd, ID_TREEVIEW), TVM_DELETEITEM,
0, (LPARAM)hitemDrag);
/*检查最后高亮显示的节点,并选中它。还必须使得其不再高亮显示,否则其它的节点被选中时就不能高亮显示了。*/
SendMessage(GetDlgItem(hWnd, ID_TREEVIEW), TVM_SELECTITEM,
TVGN_DROPHILITE,(LPARAM)0);
SendMessage(GetDlgItem(hWnd, ID_TREEVIEW), TVM_SELECTITEM,
TVGN_CARET, (LPARAM)htiNew);
}
break;
}
为了将源节点插入到目标节点处,还需要进行插入操作。为此需要两个函数如下:
char Text[128];
HTREEITEM CopyItem(HWND htreewnd, HTREEITEM hItem, HTREEITEM htiNewParent, HTREEITEM htiAfter /*= TVI_LAST*/ )
{
TV_INSERTSTRUCT tvstruct;
HTREEITEM hNewItem;
// 获得源节点信息
tvstruct.item.hItem = hItem;
tvstruct.item.mask = TVIF_CHILDREN | TVIF_HANDLE |
TVIF_IMAGE | TVIF_SELECTEDIMAGE ;
SendMessage(htreewnd, TVM_GETITEM, 0 ,(LPARAM)&tvstruct.item);
// 在适当的位置上插入节点
tvstruct.hParent = htiNewParent;
tvstruct.hInsertAfter = htiAfter;
tvstruct.item.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_TEXT;
tvstruct.item.pszText = Text;
tvstruct.item.cchTextMax = 128;
SendMessage(htreewnd, TVM_GETITEM, 0 ,(LPARAM)&tvstruct.item);
hNewItem = (HTREEITEM)SendMessage(htreewnd, TVM_INSERTITEM, 0, (LPARAM)&tvstruct);
// 复制节点数据和状态
TVITEM item;
item.mask = TVIF_PARAM | TVIF_HANDLE | TVIF_STATE;
item.hItem = hItem;
item.stateMask = TVIS_STATEIMAGEMASK;
item.state = 0;
SendMessage(htreewnd, TVM_GETITEM, 0, (LPARAM)&item);
item.hItem = hNewItem;
SendMessage(htreewnd, TVM_SETITEM, 0, (LPARAM)&item);
return hNewItem;
}
HTREEITEM CopyBranch(HWND htreewnd, HTREEITEM htiBranch, HTREEITEM htiNewParent, HTREEITEM htiAfter )
{
HTREEITEM hChild;
HTREEITEM hNewItem = CopyItem(htreewnd, htiBranch, htiNewParent, htiAfter);
hChild = (HTREEITEM)SendMessage(htreewnd, TVM_GETNEXTITEM, TVGN_CHILD,
(LPARAM) htiBranch);
while( hChild != NULL)
{
// 递归地移动所有节点
CopyBranch(htreewnd, hChild, hNewItem, htiAfter);
hChild = (HTREEITEM)SendMessage(htreewnd, TVM_GETNEXTITEM, TVGN_CHILD, (LPARAM)hChild);
}
return hNewItem;
}
一、 一、拖拽消息
拖拽(drag-drop)操作包含这样几步:
1. 1。选中一个节点后,按住鼠标左键(此时是左键拖拽)开始拖动鼠标时,树控件向其父窗口发送TVN_BEGINDRAG通知消息(通过此消息可以得到被拖拽节点的句柄)。此时,需要准备在拖拽操作时所显示的拖拽图标。那么需要向树控件发送TVM_CREATEDRAGIMAGE消息,它创建一个Image List对象,并把被选中节点的图标复制到这个Image List中,并返回Image List句柄。然后使用ImageList_BeginDrag以及ImageList_DragEnter宏对这个Image List做一些设置,比如拖拽时鼠标所处的作标点。
case TVN_BEGINDRAG:
{
POINT pt;
pt.x = 0;
pt.y = 0;
ClientToScreen(GetDlgItem(hWnd, ID_TREEVIEW), &pt);
NMTREEVIEW *lpnmtv = (NMTREEVIEW*) lParam;
hitemDrag = lpnmtv->itemNew.hItem;
hitemDrop = NULL;
himge = (HIMAGELIST)SendMessage(GetDlgItem(hWnd, ID_TREEVIEW), TVM_CREATEDRAGIMAGE,0,(LPARAM)(HTREEITEM)lpnmtv->itemNew.hItem);
if(!himge)
break;
bdragging = TRUE;
ImageList_BeginDrag(himge, 0, pt.x ,pt.y );
POINT point = lpnmtv->ptDrag;
ClientToScreen(GetDlgItem(hWnd, ID_TREEVIEW), &point);
ImageList_DragEnter(GetDlgItem(hWnd, ID_TREEVIEW),point.x ,point.y );
SetCapture(hWnd);
break;
}
2.2。 拖拽过程中会产生一系列的WM_MOUSEMOVE消息,因此要在这个消息的处理例程中要确定拖拽图标的位置(ImageList_DragMove())以及拖拽目标的位置。对于拖拽目标的确定,一般使用TVM_HITTEST消息来获得拖拽目标节点的句柄。并把这个节点加亮显示(使用带TVGN_DROPHILITE标记的TVM_SELECTITEM消息可以加亮显示拖拽节点)。
case WM_MOUSEMOVE:
{
TVHITTESTINFO tvht;
UINT flags;
flags = (UINT)wParam;
if(bdragging){
pos.x = LOWORD(lParam);
pos.y = HIWORD(lParam);
ClientToScreen(GetDlgItem(hWnd, ID_TREEVIEW), &pos);
ImageList_DragMove(pos.x, pos.y);
ImageList_DragShowNolock(FALSE);
ScreenToClient(GetDlgItem(hWnd, ID_TREEVIEW), &pos);
tvht.pt.x = pos.x;
tvht.pt.y = pos.y;
if(hitemDrop = (HTREEITEM)SendMessage(GetDlgItem(hWnd, ID_TREEVIEW),
TVM_HITTEST, (WPARAM)&flags ,(LPARAM)&tvht))
{
SendMessage(GetDlgItem(hWnd, ID_TREEVIEW), TVM_SELECTITEM,
TVGN_DROPHILITE,(LPARAM)hitemDrop);
}
ImageList_DragShowNolock(TRUE);
}
break;
}
3.3。 最后选定拖拽目标节点后,释放鼠标键产生WM_LBUTTONUP消息。在这里,Image List已完成它的任务,应该释放它(ImageList_DragLeave 、ImageList_EndDrag)。然后将源节点插入到目标节点处,并删除源节点。
case WM_LBUTTONUP:
{
if (bdragging){
bdragging = FALSE;
ImageList_DragLeave(GetDlgItem(hWnd, ID_TREEVIEW));
ImageList_EndDrag();
ReleaseCapture();
if( hitemDrag == hitemDrop )
break;
HTREEITEM htiParent = hitemDrop;
while( (htiParent = (HTREEITEM)SendMessage(GetDlgItem(hWnd, ID_TREEVIEW),
TVM_GETNEXTITEM,
(WPARAM)(UINT)TVGN_PARENT,
(LPARAM)htiParent )) != NULL )
{
if( htiParent == hitemDrag )
break;
}
DeleteObject(himge);
SendMessage(GetDlgItem(hWnd, ID_TREEVIEW), TVM_EXPAND,
(WPARAM) (UINT)TVE_EXPAND, (LPARAM)hitemDrop);
HTREEITEM htiNew = CopyBranch(GetDlgItem(hWnd, ID_TREEVIEW),
hitemDrag, hitemDrop, TVI_LAST );
SendMessage(GetDlgItem(hWnd, ID_TREEVIEW), TVM_DELETEITEM,
0, (LPARAM)hitemDrag);
/*检查最后高亮显示的节点,并选中它。还必须使得其不再高亮显示,否则其它的节点被选中时就不能高亮显示了。*/
SendMessage(GetDlgItem(hWnd, ID_TREEVIEW), TVM_SELECTITEM,
TVGN_DROPHILITE,(LPARAM)0);
SendMessage(GetDlgItem(hWnd, ID_TREEVIEW), TVM_SELECTITEM,
TVGN_CARET, (LPARAM)htiNew);
}
break;
}
为了将源节点插入到目标节点处,还需要进行插入操作。为此需要两个函数如下:
char Text[128];
HTREEITEM CopyItem(HWND htreewnd, HTREEITEM hItem, HTREEITEM htiNewParent, HTREEITEM htiAfter /*= TVI_LAST*/ )
{
TV_INSERTSTRUCT tvstruct;
HTREEITEM hNewItem;
// 获得源节点信息
tvstruct.item.hItem = hItem;
tvstruct.item.mask = TVIF_CHILDREN | TVIF_HANDLE |
TVIF_IMAGE | TVIF_SELECTEDIMAGE ;
SendMessage(htreewnd, TVM_GETITEM, 0 ,(LPARAM)&tvstruct.item);
// 在适当的位置上插入节点
tvstruct.hParent = htiNewParent;
tvstruct.hInsertAfter = htiAfter;
tvstruct.item.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_TEXT;
tvstruct.item.pszText = Text;
tvstruct.item.cchTextMax = 128;
SendMessage(htreewnd, TVM_GETITEM, 0 ,(LPARAM)&tvstruct.item);
hNewItem = (HTREEITEM)SendMessage(htreewnd, TVM_INSERTITEM, 0, (LPARAM)&tvstruct);
// 复制节点数据和状态
TVITEM item;
item.mask = TVIF_PARAM | TVIF_HANDLE | TVIF_STATE;
item.hItem = hItem;
item.stateMask = TVIS_STATEIMAGEMASK;
item.state = 0;
SendMessage(htreewnd, TVM_GETITEM, 0, (LPARAM)&item);
item.hItem = hNewItem;
SendMessage(htreewnd, TVM_SETITEM, 0, (LPARAM)&item);
return hNewItem;
}
HTREEITEM CopyBranch(HWND htreewnd, HTREEITEM htiBranch, HTREEITEM htiNewParent, HTREEITEM htiAfter )
{
HTREEITEM hChild;
HTREEITEM hNewItem = CopyItem(htreewnd, htiBranch, htiNewParent, htiAfter);
hChild = (HTREEITEM)SendMessage(htreewnd, TVM_GETNEXTITEM, TVGN_CHILD,
(LPARAM) htiBranch);
while( hChild != NULL)
{
// 递归地移动所有节点
CopyBranch(htreewnd, hChild, hNewItem, htiAfter);
hChild = (HTREEITEM)SendMessage(htreewnd, TVM_GETNEXTITEM, TVGN_CHILD, (LPARAM)hChild);
}
return hNewItem;
}
二、编辑标签消息
可以对树控件节点标签(节点文本)进行编辑,此时控件会在节点文本上产生一个编辑控件(Edit Control)。想要对文本进行编辑并使其生效,必须获得该编辑控件的窗口句柄(发送TVM_GETEDITCONTROL消息)。之后,通过这个窗口句柄获得用户在控件上所编辑修改后的节点文本。最后再把这个节点文本设置进该节点属性中才能使编辑生效(若不然编辑修改后,在节点上显示的文本仍将是原来的文本)。
编辑节点文本只需要处理TVN_BEGINLABELEDIT和TVN_ENDLABELEDIT两个通知消息。用户已选中的节点上再次单击鼠标左键时树控件就会向其父窗口发送TVN_BEGINLABELEDIT通知消息,这时在节点上产生一个编辑控件,那么在处理这个通知消息时通常是获取编辑控件的窗口句柄:
case TVN_BEGINLABELEDIT:
{
//TV_DISPINFO* pTVDispInfo = (TV_DISPINFO*)lParam;
hedit =(HWND) SendMessage(GetDlgItem(hWnd, ID_TREECHECK), TVM_GETEDITCONTROL ,0, 0);
break;
}
当用户在节点的编辑控件上编辑完文本后在树控件空白处单击鼠标左键或按Enter键后,树控件向其父窗口发送TVN_ENDLABELEDIT通知消息结束编辑操作。在这里要更新节点文本并使其生效。此外,为了更新节点文本以及其他属性,在选中一个节点时要设法保存被选中节点的节点句柄(HTREEITEM),把它存储在一个全局变量hSelected中。
case TVN_ENDLABELEDIT:
{
char Text[256]="";
tvitem.mask = TVIF_HANDLE | TVIF_TEXT;
tvitem.hItem = hSelected;
SendMessage(GetDlgItem(hWnd,ID_TREECHECK), TVM_GETITEM,
0, (LPARAM)&tvitem);
GetWindowText(hedit, Text, sizeof(Text));
tvitem.pszText = Text;
SendMessage(GetDlgItem(hWnd,ID_TREECHECK), TVM_SETITEM,
0, (LPARAM)&tvitem);
break;
}
此时编辑节点标签操作全部完成。至于获得那个节点句柄的操作可以在NM_CLICK通知消息中发送TVM_HITTEST消息进行处理处理(当然也可以有其他的处理办法):
HTREEITEM hSelected;
case NM_CLICK:
{
UINT uFlags = 0;
POINT pt;
GetCursorPos(&pt);
ScreenToClient(GetDlgItem(hWnd, ID_TREECHECK),&pt);
TVHITTESTINFO hti;
hti.pt = pt;
//Selected必须是全局变量,然后到处理TVN_ENDLABELEDIT消息这个
//变量才有效!!
hSelected = (HTREEITEM)SendMessage(GetDlgItem(hWnd,
ID_TREECHECK),TVM_HITTEST, 0, (LPARAM)&hti);
//……
//……
//其他处理代码
break;
}
编辑节点文本只需要处理TVN_BEGINLABELEDIT和TVN_ENDLABELEDIT两个通知消息。用户已选中的节点上再次单击鼠标左键时树控件就会向其父窗口发送TVN_BEGINLABELEDIT通知消息,这时在节点上产生一个编辑控件,那么在处理这个通知消息时通常是获取编辑控件的窗口句柄:
case TVN_BEGINLABELEDIT:
{
//TV_DISPINFO* pTVDispInfo = (TV_DISPINFO*)lParam;
hedit =(HWND) SendMessage(GetDlgItem(hWnd, ID_TREECHECK), TVM_GETEDITCONTROL ,0, 0);
break;
}
当用户在节点的编辑控件上编辑完文本后在树控件空白处单击鼠标左键或按Enter键后,树控件向其父窗口发送TVN_ENDLABELEDIT通知消息结束编辑操作。在这里要更新节点文本并使其生效。此外,为了更新节点文本以及其他属性,在选中一个节点时要设法保存被选中节点的节点句柄(HTREEITEM),把它存储在一个全局变量hSelected中。
case TVN_ENDLABELEDIT:
{
char Text[256]="";
tvitem.mask = TVIF_HANDLE | TVIF_TEXT;
tvitem.hItem = hSelected;
SendMessage(GetDlgItem(hWnd,ID_TREECHECK), TVM_GETITEM,
0, (LPARAM)&tvitem);
GetWindowText(hedit, Text, sizeof(Text));
tvitem.pszText = Text;
SendMessage(GetDlgItem(hWnd,ID_TREECHECK), TVM_SETITEM,
0, (LPARAM)&tvitem);
break;
}
此时编辑节点标签操作全部完成。至于获得那个节点句柄的操作可以在NM_CLICK通知消息中发送TVM_HITTEST消息进行处理处理(当然也可以有其他的处理办法):
HTREEITEM hSelected;
case NM_CLICK:
{
UINT uFlags = 0;
POINT pt;
GetCursorPos(&pt);
ScreenToClient(GetDlgItem(hWnd, ID_TREECHECK),&pt);
TVHITTESTINFO hti;
hti.pt = pt;
//Selected必须是全局变量,然后到处理TVN_ENDLABELEDIT消息这个
//变量才有效!!
hSelected = (HTREEITEM)SendMessage(GetDlgItem(hWnd,
ID_TREECHECK),TVM_HITTEST, 0, (LPARAM)&hti);
//……
//……
//其他处理代码
break;
}
节点展开/折叠消息
当点击节点左边的加号(+)或减号(-)时,带有子节点的项目就相应展开或折叠起来。这时树控件向其父窗口发送TVN_ITEMEXPANDING通知消息。这个消息的lParam参数是NMTREEVIEW结构指针。它含有两个TVITEM成员itemOld和itemNew,itemOld成员代表刚刚失去焦点的节点而itemNew成员表示正在获得焦点的节点。其中itemNew成员的hItem字段即为正在获得焦点的节点句柄。在处理TVN_ITEMEXPANDING通知消息时就是通过该句柄来对此节点的状态属性进行设置或获取操作的。而NMTREEVIEW结构的action成员用于进一步确定节点是展开还是折叠:当action == TVE_EXPAND节点展开;当action == TVE_COLLAPSE节点折叠。通常节点展开时希望该节点的图标换成展开状态的图标,比如一个表示已打开文件样式的图标等等而结点折叠则用另外的图标表示之,下面的代码就是处理这样的情况:
case TVN_ITEMEXPANDING:
{
lpnmtv = (LPNMTREEVIEW)lParam;
HTREEITEM hSelected = lpnmtv->itemNew.hItem;
if(lpnmtv->action == TVE_EXPAND){
tvitem.mask = TVIF_HANDLE | TVIF_IMAGE |
TVIF_SELECTEDIMAGE | TVIF_STATE;
tvitem.hItem = hSelected;
tvitem.state = 0;
tvitem.stateMask = TVIS_SELECTED;
tvitem.iImage = 3;
tvitem.iSelectedImage = 3;
SendMessage(GetDlgItem(hWnd, ID_TREEVIEW), TVM_SETITEM,
(WPARAM)0, (LPARAM)(const LPTVITEM)&tvitem);
}
else if(lpnmtv->action == TVE_COLLAPSE){
tvitem.mask = TVIF_HANDLE | TVIF_IMAGE |
TVIF_SELECTEDIMAGE | TVIF_STATE;
tvitem.hItem = hSelected;
tvitem.state = 0;
tvitem.stateMask = TVIS_SELECTED;
tvitem.iSelectedImage = 2;
tvitem.iImage = 2;
SendMessage(GetDlgItem(hWnd, ID_TREEVIEW), TVM_SETITEM,
(WPARAM)0, (LPARAM)(const LPTVITEM)&tvitem);
}
break;
}
四、 节点状态图标消息
可以使用TVS_CHECKBOXES样式的树控件使每个节点旁都有一个checkbox,也可以自己手动添加checkbox样式的图标来表示下面的代码负责手动添加checkbox图标:
先在WM_CREATE消息处理中插入节点:
hbitmapcheck = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP2));
himlc = ImageList_Create(13, 13, ILC_COLOR16 , 8, 0);
ImageList_Add(himlc, hbitmapcheck, 0);
DeleteObject(hbitmapcheck);
tvinsert.hParent = NULL;
tvinsert.hInsertAfter = TVI_LAST;
tvinsert.item.mask = TVIF_TEXT | TVIF_HANDLE |TVIF_STATE;
tvinsert.item.hItem = NULL;
tvinsert.item.state = INDEXTOSTATEIMAGEMASK(1);
tvinsert.item.stateMask = TVIS_STATEIMAGEMASK;
tvinsert.item.cchTextMax = 6;
SendMessage(hwndTreeCheck, TVM_SETIMAGELIST,(WPARAM)TVSIL_STATE,
(LPARAM)(HIMAGELIST)himlc);
tvinsert.item.pszText = _T("Item01");
SendMessage(hwndTreeCheck, TVM_INSERTITEM , 0 ,(LPARAM)&tvinsert);
tvinsert.item.pszText = _T("Item02");
SendMessage(hwndTreeCheck, TVM_INSERTITEM , 0 ,(LPARAM)&tvinsert);
tvinsert.item.pszText = _T("Item03");
SendMessage(hwndTreeCheck, TVM_INSERTITEM , 0 ,(LPARAM)&tvinsert);
tvinsert.item.pszText = _T("Item04");
SendMessage(hwndTreeCheck, TVM_INSERTITEM , 0 ,(LPARAM)&tvinsert);
tvinsert.item.pszText = _T("Item05");
SendMessage(hwndTreeCheck, TVM_INSERTITEM , 0 ,(LPARAM)&tvinsert);
然后在NM_CLICK消息中处理当鼠标单击节点的checkbox图标时变化checkbox的状态用于确认节点被选中:
case NM_CLICK:
{
UINT uFlags = 0;
POINT pt;
GetCursorPos(&pt);
ScreenToClient(GetDlgItem(hWnd, ID_TREECHECK),&pt);
TVHITTESTINFO hti;
hti.pt = pt;
TVITEM item;
//Selected必须是全局变量,然后到处理TVN_ENDLABELEDIT消息这个变量才//有效!!
Selected = (HTREEITEM)SendMessage(GetDlgItem(hWnd, ID_TREECHECK),
TVM_HITTEST, 0, (LPARAM)&hti);
uFlags = hti.flags;
if( uFlags & TVHT_ONITEMSTATEICON){
item.mask = TVIF_HANDLE | TVIF_STATE;
item.hItem = Selected;
item.stateMask = TVIS_STATEIMAGEMASK;
item.state = 0;
SendMessage(GetDlgItem(hWnd, ID_TREECHECK), TVM_GETITEM,
(WPARAM)0, (LPARAM)&item);
item.state = INDEXTOSTATEIMAGEMASK((item.state>>12) == 1 ? 2 : 1);
item.stateMask = TVIS_STATEIMAGEMASK;
SendMessage(GetDlgItem(hWnd, ID_TREECHECK), TVM_SETITEM,
(WPARAM)0, (LPARAM)&item);
break;
}
case TVN_ITEMEXPANDING:
{
lpnmtv = (LPNMTREEVIEW)lParam;
HTREEITEM hSelected = lpnmtv->itemNew.hItem;
if(lpnmtv->action == TVE_EXPAND){
tvitem.mask = TVIF_HANDLE | TVIF_IMAGE |
TVIF_SELECTEDIMAGE | TVIF_STATE;
tvitem.hItem = hSelected;
tvitem.state = 0;
tvitem.stateMask = TVIS_SELECTED;
tvitem.iImage = 3;
tvitem.iSelectedImage = 3;
SendMessage(GetDlgItem(hWnd, ID_TREEVIEW), TVM_SETITEM,
(WPARAM)0, (LPARAM)(const LPTVITEM)&tvitem);
}
else if(lpnmtv->action == TVE_COLLAPSE){
tvitem.mask = TVIF_HANDLE | TVIF_IMAGE |
TVIF_SELECTEDIMAGE | TVIF_STATE;
tvitem.hItem = hSelected;
tvitem.state = 0;
tvitem.stateMask = TVIS_SELECTED;
tvitem.iSelectedImage = 2;
tvitem.iImage = 2;
SendMessage(GetDlgItem(hWnd, ID_TREEVIEW), TVM_SETITEM,
(WPARAM)0, (LPARAM)(const LPTVITEM)&tvitem);
}
break;
}
四、 节点状态图标消息
可以使用TVS_CHECKBOXES样式的树控件使每个节点旁都有一个checkbox,也可以自己手动添加checkbox样式的图标来表示下面的代码负责手动添加checkbox图标:
先在WM_CREATE消息处理中插入节点:
hbitmapcheck = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP2));
himlc = ImageList_Create(13, 13, ILC_COLOR16 , 8, 0);
ImageList_Add(himlc, hbitmapcheck, 0);
DeleteObject(hbitmapcheck);
tvinsert.hParent = NULL;
tvinsert.hInsertAfter = TVI_LAST;
tvinsert.item.mask = TVIF_TEXT | TVIF_HANDLE |TVIF_STATE;
tvinsert.item.hItem = NULL;
tvinsert.item.state = INDEXTOSTATEIMAGEMASK(1);
tvinsert.item.stateMask = TVIS_STATEIMAGEMASK;
tvinsert.item.cchTextMax = 6;
SendMessage(hwndTreeCheck, TVM_SETIMAGELIST,(WPARAM)TVSIL_STATE,
(LPARAM)(HIMAGELIST)himlc);
tvinsert.item.pszText = _T("Item01");
SendMessage(hwndTreeCheck, TVM_INSERTITEM , 0 ,(LPARAM)&tvinsert);
tvinsert.item.pszText = _T("Item02");
SendMessage(hwndTreeCheck, TVM_INSERTITEM , 0 ,(LPARAM)&tvinsert);
tvinsert.item.pszText = _T("Item03");
SendMessage(hwndTreeCheck, TVM_INSERTITEM , 0 ,(LPARAM)&tvinsert);
tvinsert.item.pszText = _T("Item04");
SendMessage(hwndTreeCheck, TVM_INSERTITEM , 0 ,(LPARAM)&tvinsert);
tvinsert.item.pszText = _T("Item05");
SendMessage(hwndTreeCheck, TVM_INSERTITEM , 0 ,(LPARAM)&tvinsert);
然后在NM_CLICK消息中处理当鼠标单击节点的checkbox图标时变化checkbox的状态用于确认节点被选中:
case NM_CLICK:
{
UINT uFlags = 0;
POINT pt;
GetCursorPos(&pt);
ScreenToClient(GetDlgItem(hWnd, ID_TREECHECK),&pt);
TVHITTESTINFO hti;
hti.pt = pt;
TVITEM item;
//Selected必须是全局变量,然后到处理TVN_ENDLABELEDIT消息这个变量才//有效!!
Selected = (HTREEITEM)SendMessage(GetDlgItem(hWnd, ID_TREECHECK),
TVM_HITTEST, 0, (LPARAM)&hti);
uFlags = hti.flags;
if( uFlags & TVHT_ONITEMSTATEICON){
item.mask = TVIF_HANDLE | TVIF_STATE;
item.hItem = Selected;
item.stateMask = TVIS_STATEIMAGEMASK;
item.state = 0;
SendMessage(GetDlgItem(hWnd, ID_TREECHECK), TVM_GETITEM,
(WPARAM)0, (LPARAM)&item);
item.state = INDEXTOSTATEIMAGEMASK((item.state>>12) == 1 ? 2 : 1);
item.stateMask = TVIS_STATEIMAGEMASK;
SendMessage(GetDlgItem(hWnd, ID_TREECHECK), TVM_SETITEM,
(WPARAM)0, (LPARAM)&item);
break;
}
用于树控件消息处理的几个结构
众所周知,Windows程序是消息驱动模式,各种消息由Windows操作系统侦测得到,并由用户创建的窗口所取得。这时,操作系统将消息的具体信息包含在两个典型的消息参数里:LPARAM和WPARAM,它们都是32位无符号整型参数UINT类型。其实,奥妙就在这两个参数当中。它们依据不同的消息,包含不同的参数格式。比如,当用户在窗口中点击鼠标左键时,就会发送WM_LBUTTIONDOWN消息。想想看,点击消息一定要包含点击的鼠标位置,那么自然地,这个消息中的LPARAM中包含了POINT数据类型。其中,低字节是X轴坐标,高字节是Y轴坐标,可以通过LOWORD和HIWORD宏取得:
POINT pt;
pt.x = LOWORD(lParam);
pt.y = HIWORD(lParam);
同时,消息参数WPARAM包含一些标志位flags,用于确定在点击鼠标的同时是否还按下了键盘上的功能键等等。
而有时候,有些消息需要传递一些比较大的结构struct,这时32位的无符号整数UINT肯定是装不下的。那么,就把传递的结构指针(地址)放在消息参数当中。当窗口接收到这种消息时,用户负责把消息参数转换成适当的指针类型即可。比如下面将要说明的NMHDR结构。当控件向其父窗口发送通知消息WM_NOTIFY时,消息参数LPARAM就是一个结构的地址指针。那么使用时,可以这样处理:
NMHDR *pnmhdr = (NMHDR *)lParam;
此外,对于控件来说,当控件上发生什么事件时(产生了相应的消息),那么控件会向其父窗口发送通知消息WM_NOTIFY,其中的lParam是NMHDR结构地址。给出这个结构还要进一步确定究竟是什么通知消息,这时code字段包含具体的消息类型。它可以是通用控件的通知消息,以NM_XXX形式命名,也可以是具体控件的通知消息。对于树控件来说它们以TVN_XXX形式命名。
另外,还要对控件进行控制,这些控制操作是通过向控件发送消息来实现。这些消息以TVM_XXX形式命名。
好了,可以简单地说,树控件上发生了某种事件时,树控件会向其父窗口发送通知消息WM_NOTIFY, 它们是TVN_XXX;当需要控制树控件时,通过SendMessage向它发送通知消息,它们是TVM_XXX。
1.NMHDR结构
typedef struct tagNMHDR {
HWND hwndFrom;
UINT idFrom;
UINT code;
} NMHDR;
该结构包含有关通知消息的信息。
hwndFrom
发送消息的控件窗口句柄。
idFrom
发送消息的控件标识符(ID)。
code
通知码。此成员可以是特殊控件的通知码,例如,TVN_ITEMEXPANDING.。也可以是通用控件的通知码之一。
通用通知码列表如下:
通知码
发送原因
NM_CLICK
用户在控件上单击鼠标左键
NM_DBLCLK
用户在控件上双击鼠标左键
NM_RCLICK
用户在控件上单击鼠标右键
NM_RDBLCLK
用户在控件上双击鼠标右键
NM_RETURN
当控件具有输入焦点时,用户按下回车键
NM_SETFOCUS
控件已经具有输入焦点
NM_KILLFOCUS
控件已经失去焦点
NM_OUTOFMEMORY
因为没有足够的内存可用,控件不能完成指定的操作
此结构一般作为控件通知消息处理函数的参数,例如上面代码中的通知消息处理函数:
afx_msg void OnTreeDblClk(NMHDR * pNMHDR, LRESULT *pResult);
――――――――――――――――――――――――――――――――――――――
2.NM_TREEVIEW结构
typedef struct _NM_TREEVIEW {
NMHDR hdr;
UINT action;
TV_ITEM itemOld;
TV_ITEM itemNew;
POINT ptDrag; }
NM_TREEVIEW;
typedef NM_TREEVIEW FAR *LPNM_TREEVIEW;
此结构包含树控件消息的信息。
此结构中action成员是特定的通知消息位。它表明是由鼠标动作引起的选择操作的改变,还是由键盘动作引起的。比如,当在树控件的小加号/减号(+/-)上点击鼠标左键时,带有子节点的项就要展开或者合拢。那么究竟是展开还是合拢,就要通过这个action字段来区分。当action == TVE_EXPAND时,就展开;当action == TVE_COLLAPSE时就合拢。 而itemOld和itemNew分别是旧结点的状态和新结点的状态。ptDrag是引起消息发送的事件发生时的鼠标在客户区上的坐标。
在通知消息处理函数中一般使用HMHDR指针,例如:
afx_msg void OnTreeDblClk(NMHDR * pNMHDR, LRESULT *pResult);
而有时,甚至是大多数时候,在消息处理函数需要使用NM_TREEVIEW对象,这时可以将消息处理函数传进来的NMHDR指针强制转换为NM_TREEVIEW指针。例如:
void CComDlg::OnSelchangedTreeview(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
CTreeCtrl* pTree = (CTreeCtrl*) GetDlgItem(IDC_TREEVIEW1);
HTREEITEM hSelected = pNMTreeView->itemNew.hItem;
if (hSelected != NULL) {
char text[31];
TV_ITEM item;
item.mask = TVIF_HANDLE | TVIF_TEXT;
item.hItem = hSelected;
item.pszText = text;
item.cchTextMax = 30;
VERIFY(pTree->GetItem(&item));
SetDlgItemText(IDC_STATIC_TREEVIEW1,
pTree->GetItemText(hSelected));
}
*pResult = 0;
}
――――――――――――――――――――――――――――――――――――
此函数处理了NM_SELCHANGED消息,在树控件中某项的选择状态被改变后,在一个静态文本框中显示改变后新项的项标签文本。其实这段代码也可以用如下代码代替:
CTreeCtrl* pTree = (CTreeCtrl*) GetDlgItem(IDC_TREEVIEW1);
HTREEITEM hSelected = pTree->GetSelectedItem();
if (hSelected != NULL) {
SetDlgItemText(IDC_STATIC_TREEVIEW1,
pTree->GetItemText(hSelected));
}
pResult = 0;
――――――――――――――――――――――――――――――――――――
区别是前一段代码中选择在程序栈中为项标签文本缓冲区分配内存,而后一段代码中选择在程序堆中为项标签文本分配内存。
POINT pt;
pt.x = LOWORD(lParam);
pt.y = HIWORD(lParam);
同时,消息参数WPARAM包含一些标志位flags,用于确定在点击鼠标的同时是否还按下了键盘上的功能键等等。
而有时候,有些消息需要传递一些比较大的结构struct,这时32位的无符号整数UINT肯定是装不下的。那么,就把传递的结构指针(地址)放在消息参数当中。当窗口接收到这种消息时,用户负责把消息参数转换成适当的指针类型即可。比如下面将要说明的NMHDR结构。当控件向其父窗口发送通知消息WM_NOTIFY时,消息参数LPARAM就是一个结构的地址指针。那么使用时,可以这样处理:
NMHDR *pnmhdr = (NMHDR *)lParam;
此外,对于控件来说,当控件上发生什么事件时(产生了相应的消息),那么控件会向其父窗口发送通知消息WM_NOTIFY,其中的lParam是NMHDR结构地址。给出这个结构还要进一步确定究竟是什么通知消息,这时code字段包含具体的消息类型。它可以是通用控件的通知消息,以NM_XXX形式命名,也可以是具体控件的通知消息。对于树控件来说它们以TVN_XXX形式命名。
另外,还要对控件进行控制,这些控制操作是通过向控件发送消息来实现。这些消息以TVM_XXX形式命名。
好了,可以简单地说,树控件上发生了某种事件时,树控件会向其父窗口发送通知消息WM_NOTIFY, 它们是TVN_XXX;当需要控制树控件时,通过SendMessage向它发送通知消息,它们是TVM_XXX。
1.NMHDR结构
typedef struct tagNMHDR {
HWND hwndFrom;
UINT idFrom;
UINT code;
} NMHDR;
该结构包含有关通知消息的信息。
hwndFrom
发送消息的控件窗口句柄。
idFrom
发送消息的控件标识符(ID)。
code
通知码。此成员可以是特殊控件的通知码,例如,TVN_ITEMEXPANDING.。也可以是通用控件的通知码之一。
通用通知码列表如下:
通知码
发送原因
NM_CLICK
用户在控件上单击鼠标左键
NM_DBLCLK
用户在控件上双击鼠标左键
NM_RCLICK
用户在控件上单击鼠标右键
NM_RDBLCLK
用户在控件上双击鼠标右键
NM_RETURN
当控件具有输入焦点时,用户按下回车键
NM_SETFOCUS
控件已经具有输入焦点
NM_KILLFOCUS
控件已经失去焦点
NM_OUTOFMEMORY
因为没有足够的内存可用,控件不能完成指定的操作
此结构一般作为控件通知消息处理函数的参数,例如上面代码中的通知消息处理函数:
afx_msg void OnTreeDblClk(NMHDR * pNMHDR, LRESULT *pResult);
――――――――――――――――――――――――――――――――――――――
2.NM_TREEVIEW结构
typedef struct _NM_TREEVIEW {
NMHDR hdr;
UINT action;
TV_ITEM itemOld;
TV_ITEM itemNew;
POINT ptDrag; }
NM_TREEVIEW;
typedef NM_TREEVIEW FAR *LPNM_TREEVIEW;
此结构包含树控件消息的信息。
此结构中action成员是特定的通知消息位。它表明是由鼠标动作引起的选择操作的改变,还是由键盘动作引起的。比如,当在树控件的小加号/减号(+/-)上点击鼠标左键时,带有子节点的项就要展开或者合拢。那么究竟是展开还是合拢,就要通过这个action字段来区分。当action == TVE_EXPAND时,就展开;当action == TVE_COLLAPSE时就合拢。 而itemOld和itemNew分别是旧结点的状态和新结点的状态。ptDrag是引起消息发送的事件发生时的鼠标在客户区上的坐标。
在通知消息处理函数中一般使用HMHDR指针,例如:
afx_msg void OnTreeDblClk(NMHDR * pNMHDR, LRESULT *pResult);
而有时,甚至是大多数时候,在消息处理函数需要使用NM_TREEVIEW对象,这时可以将消息处理函数传进来的NMHDR指针强制转换为NM_TREEVIEW指针。例如:
void CComDlg::OnSelchangedTreeview(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
CTreeCtrl* pTree = (CTreeCtrl*) GetDlgItem(IDC_TREEVIEW1);
HTREEITEM hSelected = pNMTreeView->itemNew.hItem;
if (hSelected != NULL) {
char text[31];
TV_ITEM item;
item.mask = TVIF_HANDLE | TVIF_TEXT;
item.hItem = hSelected;
item.pszText = text;
item.cchTextMax = 30;
VERIFY(pTree->GetItem(&item));
SetDlgItemText(IDC_STATIC_TREEVIEW1,
pTree->GetItemText(hSelected));
}
*pResult = 0;
}
――――――――――――――――――――――――――――――――――――
此函数处理了NM_SELCHANGED消息,在树控件中某项的选择状态被改变后,在一个静态文本框中显示改变后新项的项标签文本。其实这段代码也可以用如下代码代替:
CTreeCtrl* pTree = (CTreeCtrl*) GetDlgItem(IDC_TREEVIEW1);
HTREEITEM hSelected = pTree->GetSelectedItem();
if (hSelected != NULL) {
SetDlgItemText(IDC_STATIC_TREEVIEW1,
pTree->GetItemText(hSelected));
}
pResult = 0;
――――――――――――――――――――――――――――――――――――
区别是前一段代码中选择在程序栈中为项标签文本缓冲区分配内存,而后一段代码中选择在程序堆中为项标签文本分配内存。
登録:
投稿 (Atom)