本课中,我们将学习如何使用树型视图控件。另外还要学习如何在树型视图中完成拖-拉动作,以及如何使用图象列表。

理论:

树型视图是一种特别的窗口,我们可以使用它一目了然地表示某种层次关系。譬如象在资源管理器中左边窗口中的就是树型视图。您可以调用CreateWindowEx来创建树型视图,传递一个类名“"SysTreeView32"”,或者您也可以把它放到一个对话框中去。不要忘了在您的代码中加入InitCommonControls函数。 
树型视图有几种特有的风格。下面是几种经常使用的。

TVS_HASBUTTONS == 在父项目中显示(+)或(-)。用户可以通过点击该符号来展开或收起该父项目下的子项目。如果想在根目录下也有这个符号必须指定TVS_LINESATROOT风格。
TVS_HASLINES == 在层次中用线条来连接各个项目名称。
TVS_LINESATROOT == 
在根目录下的项目也用线连接。如果没有指定TVS_HASLINES风格,该风格也就会被忽略。

像其它的通用控件一样,树型视图用消息来完成通信。父窗口发送一系列的消息给树型视图,而树型视图发送"notification"消息给它的父窗口。在这方面,树型视图和其它的通用控制没什么两样。
当有事件发生时,树型视图发送一个WM_NOTIFY消息个父窗口,并在消息中附带传递一些附加信息。

WM_NOTIFY
wParam ==
控件的ID。因为该值不是唯一的,故我们不用它。我们使用NMHDR结构体中的hwndFrom或IDFrom成员变量。
lParam == 指向NMHDR结构体的指针。有一些控件可能传递一个指向更大一点的结构体的指针。但该结构体必须保证它的第一个成员变量是一个NMHDR型的变量。这样,您在处理lParam变量时,至少可以得到一个NMHDR型的变量。

下面我们来看NMHDR:
typedef struct tagNMHDR 

HWND hwndFrom; 
UINT idFrom; 
UINT code; 
}NMHDR;hwndFrom是发送WM_NOTIFY消息的控件的窗口句柄。
idFrom是发送WM_NOTIFY消息的控件的ID。 
code是控件发送给父窗口的数据。
树型视图发送给父窗口的通知消息以TVN_打头。 树型视图接收到的消息以TVM_打头,譬如:TVM_CREATEDRAGIMAGE。树型视图发送TVN_XXX消息时在code变量中放入NMHDR型变量。父窗口发送TVM_消息来控制树型视图。

在树型视图中加入项目

在创建完树型视图后可以通过发送TVM_INSERTITEM消息往其中加入项目了。

TVM_INSERTITEM 
wParam = 0; 
lParam = 指向结构体TV_INSERTSTRUCT的指针;

您应当知道一些关于树型视图中的项目之间关系的一些术语。一个项目可能是一个父亲、儿子或两者都是。父项目下含有子项目,而该父项目又有可能是其它项目的子项目。一个没有父项目的项目叫根项目。在树型视图中可能有多个根项目。现在我们来看看TV_INSERTSTRUCT结构体:

typedef struct tagTVINSERTSTRUCT {
HTREEITEM hParent;
HTREEITEM hInsertAfter;
#if (_WIN32_IE >= 0x0400)
union
{
TVITEMEX itemex;
TVITEM item;
} DUMMYUNIONNAME;
#else
TVITEM item;
#endif
} TVINSERTSTRUCT, *LPTVINSERTSTRUCT;

hParent = 父项目的句柄。如果该值为TVI_ROOT value或NULL,该项目插在树型视图的根部。
hInsertAfter = 应该插入在起后面的项目的句柄或下面的值:

  • TVI_FIRST ==> 插在列表的头部。
  • TVI_LAST ==> 插在列表的尾部。
  • TVI_SORT ==> 按字母顺序插入。
union
{
TVITEMEX itemex;
TVITEM item;
} DUMMYUNIONNAME;

我们仅使用TVITEM。
typedef struct tagTVITEM { 
UINT mask; 
HTREEITEM hItem; 
UINT state; 
UINT stateMask; 
LPTSTR pszText; 
int cchTextMax; 
int iImage; 
int iSelectedImage; 
int cChildren; 
LPARAM lParam; 
} TVITEM, *LPTVITEM;

该结构体根据消息类型,用来发送或接收关于一个树型视图的项目的有关信息。譬如:对于消息TVM_INSERTITEM,它用来指定插入树型视图控件的项目的属性。而对于消息TVM_GETITEM,该结构体用来填充关于选定项目的信息。
imask 用来指定TV_ITEM的那些成员变量有效。譬如,如果指定了TVIF_TEXT,这意味着pszText成员变量是有效的。您可以同时指定几个标志位。
hItem 是树型视图项目的句柄。每一个项目都有它自己的句柄,就像窗口一样。如果您想要操作一个项目,就必须选择它的句柄。
pszText 是一个字符串指针。它是项目的标签名。
cchTextMax仅在查询项目的名称时使用。由于在pszText中指定了指针,WINDOWS还要知道该缓冲去的大小。所以您必须给出该值。
iImage 和 iSelectedImage用来指定图象列表以及一个索引号。这样就知道当项目被选中或没被选中时用哪个图象来表示该项目。像资源管理器中左边窗口中的文件夹等小图表就是有这两个参数来决定的。 
为了在树型视图中插入一个项目,您必须至少设定hParent, hInsertAfter,另外您还要设定imask和pszText值。

把图形加到图形视图中

如果您想要在项目的名称左边显示图标的话,您必须创建一个图形列表,并且把它和树形视图相关联起来。您可以调用ImageList_Create来创建一个图形列表。

HIMAGELIST ImageList_Create(
int cx,
int cy,
UINT flags,
int cInitial,
int cGrow
);

如果创建成功的话,该函数返回一个空的图象列表的句柄。
cx == 以像素为单位的图象的宽度。
cy == 以像素为单位的图象的高度。图象列表中的每一幅的高度都必须相同。否则WINDOWS会对您的图象进行裁剪,如果过大的话就可能裁剪成几小块。所以您必须指定相同大小的图象。
flags == 指定图象列表的图象的颜色深度。详细情况请参考WIN32 API 指南。
cInitial == 指定包含的图象的数目。WINDWOS将依此来分配合适的内存。
cGrow == 在增加新图象是一次增加的数目。

图象列表不是窗口。仅仅是保存在那给其它的窗口使用的一种资源。在图象列表产生后,您可以调用ImageList_Add来向其中加入图象。

int ImageList_Add( 
HIMAGELIST himl, 
HBITMAP hbmImage, 
HBITMAP hbmMask 
); 
如果该函数调用失败的话,返回-1。
himl == 图象列表的句柄。它是调用ImageList_Create时返回的值。
hbmImage == 加入图象列表的位图的句柄。您通常把位图保存在资源中,然后调用LoadBitmap来把它加载进来。注意您没有必要指定该位图中包含的图象的数目。WINDOWS会根据它的大小,自动计算。
hbmMask == 掩码位图的句柄。如果没有使用掩码位图,可以忽略该值。通常我们加入两种图象到图象列表中。一种时被选中时显示的图象,另一种时没被选中时显示的。
当图象列表准备就绪后,您可以发送消息TVM_SETIMAGELIST给树型视图来让图象列表和树型视图联系起来。

TVM_SETIMAGELIST
wParam = 
图象列表的状态,一共有两种:

  • TVSIL_NORMAL 包含被选中和没有被选中两种状态的图象。
  • TVSIL_STATE 包含了用户自定义的状态的图象。

lParam = 图象列表的句柄。

检索树型视图的信息

您可以通过发送消息TVM_GETITEM来检索图形视图的信息。

TVM_GETITEM 
wParam = 0 
lParam =指向结构体TV_ITEM的指针。该结构体将用来得到相关的信息。

在发送该消息前必须设置成员变量imask的值,以便WINDOWS能告诉相关的信息。当然,最重要的是,您必须传递您想得到信息的项目的句柄。这就引起了一个问题,您如何得到项目的句柄?要保存所有项目的句柄吗?
答案是很简单的:没有必要。您可以发送消息TVM_GETNEXTITEM到树型视图以检索您想要得到其属性的项目的句柄。譬如:您可以查询第一个子项目的句柄、根目录的句柄、选中的项目的句柄等等。

TVM_GETNEXTITEM
wParam = 
标志
lParam = 树型视图的句柄(仅仅当wParam的值是某些标志位时才是必须的)。

wParam中的值非常重要, 我解释如下:

  • TVGN_CARET 选中的项目
  • TVGN_CHILD hitem参数指定项目的第一个子项目
  • TVGN_DROPHILITE 拖-拉操作的目的项目
  • TVGN_FIRSTVISIBLE 第一个可见项目
  • TVGN_NEXT 下一个同级项目
  • TVGN_NEXTVISIBLE 下一个可见项目,指定的项目必须可见。发送消息TVM_GETITEMRECT 来决定项目是否可见
  • TVGN_PARENT 指定项目的父项目
  • TVGN_PREVIOUS 前一个同级项目
  • TVGN_PREVIOUSVISIBLE 前一个可见项目,指定的项目必须可见。发送消息TVM_GETITEMRECT 来决定项目是否可见
  • TVGN_ROOT 根项目

由此您可以通过发送该消息来得到项目的句柄,然后在发送消息TVM_GETITEM时在结构体变量TV_ITEM的成员变量hItem中放入该项目的句柄就可以得到关于该项目的有关信息了。

在树型视图中进行拖-拉操作

也就是因为这一部分我才决定写这课教程。当我按照InPrise公司的WIN32帮助来运行例子时,发现它的帮助中缺少真正重要的信息。我只有通过自己做实验,最后总算弄明白来个中来由。希望您不要和我一样再去走这些弯路,下面我把我所知的在树型视图中进行拖-拉操作的步骤描述如下:

  1. 当用户要拖动一个项目时,树型视图控件会给它的父窗口发送TVN_BEGINDRAG通知消息。您可以在此处创建表示项目处在拖动操作中的图象,这可以通过发送TVM_CREATEDRAGIMAGE消息给树型视图,让其为目前使用的图象产生一副缺省的图象来实现。树型视图控件将创建一个图象列表,其中仅包含一副在拖动中显示的图象,图象列表创建后,您可以得到它的句柄。
  2. 在拖拉的图象生成后,您可以通过调用ImageList_BeginDrag来指定拖动图象的热点位置。
  3. BOOL ImageList_BeginDrag( 
    HIMAGELIST himlTrack, int iTrack, int dxHotspot, int dyHotspot );
    himlTrack 是包含了拖拉时显示的图象的图象列表的句柄 
    iTrack 是选中的图象在图象列表中的索引号。
    dxHotspot 因为在拖动中该图象被用来取代光标,所以我们必须指定图象中的哪一点是光标的左上角的位置。dxHotspot是水平相对位置。 
    dyHotspot 是垂直相对位置。
    iTrack等于0。如果您要想光标的热点在拖拉中显示的图象的左上角,把dxHotspot和dyHotspot都设成0。
  4. 当拖拉的图象要显示时,我们调用ImageList_DragEnter 在树型视图中显示该图象。
  5. BOOL ImageList_DragEnter( 
    HWND hwndLock, int x, int y );
    hwndLock 是进行拖拉中的窗口的句柄,拖拉的动作限制在该窗口中。
    x 和 y是在拖拉时显示图象的初始位置的坐标值。这些值是相对于窗口的左上角而不是客户区的左上角。
  6. 既然可以显示拖动中的图象了,我们就要处理拖动操作了。在这里有一个小问题。我们监视拖动是通过监视鼠标光标的移动来实现的,譬如在移动时我们通过捕获WM_MOUSEMOVE消息来得到移动中的坐标位置,通过捕获WM_LBUTTONUP消息来获知用户的放下操作。但这时如果鼠标光标移过子窗口时父窗口就无法再得到鼠标光标的移动以及鼠标的按键消息了。解决办法是调用SetCapture函数了锁定鼠标事件,这样无论鼠标移到那里和有什么动作,我们的窗口都可以知道了。
  7. 在处理WM_MOUSEMOVE消息时,您可以调用ImageList_DragMove来更新图象移动的轨迹。该函数可以移动拖放操作中的图象位置。另外,如果您想让移动中的图象经过某些项目时高量度显示,可以调用TVM_HITTEST 来确定是否经过某个项目的上面。如果是的话,您可以发送TVM_SELECTITEM消息并设置 TVGN_DROPHILITE标志位使得那个项目高亮度显示。注意:在发送消息TVM_SELECTITEM前,您必须先隐藏图象列表,否则会留下非常难看的轨迹。要隐藏拖动中的图象可以调用ImageList_DragShowNolock,在显示完高亮度的图象后再调用该函数以让拖动中的图象再正常显示。
  8. 当用户释放主键后,您必须做几件事。如果您在高亮度显示的时候释放鼠标主键(表示您想把该项目加到此处),您必须使该项目变成正常地显示,这可以通过发送消息TVM_SELECTITEM消息并设置标志位TVGN_DROPHILITE来实现,只是这时lParam必须为0。如果您不让高亮度显示的项目恢复正常,那就会发生一个奇怪的现象:当您再选择另外的项目时,那个项目的图象会包含在一个正方形中,当时高亮度显示的项目依旧是上一个项目。接下来必须调用ImageList_EndDrag和ImageList_DragLeave。还有调用ReleaseCapture来释放捕获的鼠标。如果您创建了一个图象列表,那还要调用calling ImageList来将它销毁,在拖放操作结束后您可以进行另外其它的操作。

例子代码:见光盘FirstWindow17

#include "Windows.h"
#include "tchar.h"
#include "commctrl.h"
#pragma comment(lib,"comctl32.lib")
#define IDB_TREE 4006

TCHAR ClassName[] = _T("TreeViewWinClass");
TCHAR AppName[] = _T("Tree View Demo");
TCHAR TreeViewClass[] = _T("SysTreeView32");
TCHAR Parent[] = _T("Parent Item");
TCHAR Child1[] = _T("Child1");
TCHAR Child2[] = _T("Child2");
BOOL DragMode = FALSE;
HINSTANCE g_hInstance;

HWND hwndTreeView;
HTREEITEM hParent;
HIMAGELIST hImageList;
HIMAGELIST hDragImageList;

INT_PTR CALLBACK ProcWinMain( HWND hWnd, 
UINT Msg, 
WPARAM wParam, 
LPARAM lParam 
)
{
TV_INSERTSTRUCT tvinsert;
HBITMAP hBitmap;
TV_HITTESTINFO tvhit;

switch(Msg)
{
case WM_CREATE:
hwndTreeView = CreateWindowEx(NULL,TreeViewClass,NULL,WS_CHILD|WS_VISIBLE|TVS_HASLINES|TVS_HASBUTTONS|TVS_LINESATROOT,0,0,200,400,hWnd,NULL,
g_hInstance,NULL);
hImageList = ImageList_Create(16,16,ILC_COLOR16,2,10);
hBitmap = LoadBitmap(g_hInstance,MAKEINTRESOURCE(IDB_TREE));
ImageList_Add(hImageList,hBitmap,NULL);
DeleteObject(hBitmap);
SendMessage(hwndTreeView,TVM_SETIMAGELIST,0,(LPARAM)hImageList);
tvinsert.hParent = NULL;
tvinsert.hInsertAfter = TVI_ROOT;
tvinsert.item.mask = TVIF_TEXT|TCIF_IMAGE|TVIF_SELECTEDIMAGE;
tvinsert.item.pszText = Parent;
tvinsert.item.iImage = 0;
tvinsert.item.iSelectedImage = 1;
hParent = (HTREEITEM) SendMessage(hwndTreeView,TVM_INSERTITEM,0,(LPARAM)&tvinsert);
tvinsert.hParent = hParent;
tvinsert.hInsertAfter = TVI_LAST;
tvinsert.item.pszText = Child1;
SendMessage(hwndTreeView,TVM_INSERTITEM,0 ,(LPARAM)&tvinsert);
tvinsert.item.pszText = Child2;
SendMessage(hwndTreeView,TVM_INSERTITEM,0,(LPARAM)&tvinsert);
break;

case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_MOUSEMOVE:
if(DragMode)
{
tvhit.pt.x = LOWORD(lParam);
tvhit.pt.y = HIWORD(lParam);
ImageList_DragMove(tvhit.pt.x,tvhit.pt.y);
ImageList_DragShowNolock(FALSE);
HTREEITEM hTmp = (HTREEITEM)SendMessage(hwndTreeView,TVM_HITTEST,NULL,(LPARAM)&tvhit);
if(hTmp != NULL)
SendMessage(hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,(LPARAM)hTmp);

ImageList_DragShowNolock(TRUE);
}

break;
case WM_LBUTTONDOWN:
if(DragMode)
{
ImageList_DragLeave(hwndTreeView);
ImageList_EndDrag();
ImageList_Destroy(hDragImageList);
HTREEITEM hTmp = (HTREEITEM)SendMessage(hwndTreeView,TVM_GETNEXTITEM,TVGN_DROPHILITE,0);
SendMessage(hwndTreeView,TVM_SELECTITEM,TVGN_CARET,(LPARAM)hTmp);
SendMessage(hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,0);
ReleaseCapture();
DragMode = FALSE;
}
break;
case WM_NOTIFY:
{
NM_TREEVIEW *pTree = (NM_TREEVIEW *)lParam;
if(pTree->hdr.code == TVN_BEGINDRAG)
{
hDragImageList = (HIMAGELIST ) SendMessage(hwndTreeView,TVM_CREATEDRAGIMAGE,0,(LPARAM)pTree->itemNew.hItem);
ImageList_BeginDrag(hDragImageList,0,0,0);
ImageList_DragEnter(hwndTreeView,pTree->ptDrag.x,pTree->ptDrag.y);
SetCapture(hWnd);
DragMode = TRUE;
}
}
break;
default:
return DefWindowProc(hWnd,Msg,wParam,lParam);
}
return 0;
}
int WINAPI WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
)
{
WNDCLASSEX wc;
MSG msg;
HWND hWnd;
g_hInstance = hInstance;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = ProcWinMain;
wc.cbClsExtra = NULL;
wc.cbWndExtra = NULL;
wc.hInstance = hInstance;
wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE);
wc.lpszMenuName = NULL;
wc.lpszClassName = ClassName;
wc.hIcon = wc.hIconSm = LoadIcon(NULL,IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL,IDC_ARROW);
RegisterClassEx(&wc);

hWnd = CreateWindowEx(WS_EX_CLIENTEDGE,ClassName,AppName,WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,CW_USEDEFAULT,200,400,NULL,NULL,hInstance,NULL);
ShowWindow(hWnd,SW_SHOWNORMAL);
UpdateWindow(hWnd);

while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return msg.wParam;
InitCommonControls();
}

win32 sdk树形控件的项拖拽实现的更多相关文章

  1. jqGrid选择列控件向右拖拽超出边界处理

    jqGrid选择列控件向右拖拽超出边界处理 $("#tb_DeviceInfo").jqGrid('navButtonAdd', '#jqGridPager', {         ...

  2. 让一个view 或者控件不支持拖拽

    让一个view 或者控件不支持拖拽: dragView.userInteractionEnabled = NO;

  3. WPF 在image控件用鼠标拖拽出矩形

    原文:WPF 在image控件用鼠标拖拽出矩形 版权声明:博客已迁移到 http://lindexi.gitee.io 欢迎访问.如果当前博客图片看不到,请到 http://lindexi.gitee ...

  4. 2018-11-19-WPF-在image控件用鼠标拖拽出矩形

    title author date CreateTime categories WPF 在image控件用鼠标拖拽出矩形 lindexi 2018-11-19 15:35:13 +0800 2018- ...

  5. ios-将代码创建的视图控件放入拖拽控件的下面

    如图所示 图片是拖拽上去的imageView,橘黄色控件是在代码中创建的添加上去的,此时黄色view在imageView 上方 调用方法bringSubviewToFront:试图将imageView ...

  6. Winform中Picture控件图片的拖拽显示

    注解:最近做了一个小工具,在Winform中对Picture控件有一个需求,可以通过鼠标从外部拖拽图片到控件的上,释放鼠标,显示图片! 首先你需要对你的整个Fom窗口的AllowDrop设置Ture ...

  7. 让您的WinForm控件快速支持拖拽文件

    实现原理:使用扩展方法. /// <summary> /// 控件扩展 /// </summary> public static class ControlExt { /// ...

  8. devpress 的gridview 控件的行拖拽 z

    首先,添加引用:using DevExpress.XtraGrid.Views.Grid.ViewInfo;               gridControl1.AllowDrop = true; ...

  9. VS2010/MFC编程入门之三十一(常用控件:树形控件Tree Control 下)

    前面一节讲了树形控件Tree Control的简介.通知消息以及相关数据结构,本节继续讲下半部分,包括树形控件的创建.CTreeCtrl类的主要成员函数和应用实例. 树形控件的创建 MFC为树形控件提 ...

随机推荐

  1. python idle 错误 subprocess didn&#39;t make connection

    今天打开python idle不反应.然后通过网上搜索让我在安装文件夹下点击idle.py 弹出如图所看到的的错误,进行了非常多尝试.任然没有得到解决.可是在尝试过程中发现了大家所说问题所在都是由于新 ...

  2. 嵌入式环境:挂载开发板根NFS文件系统失败

    挂载开发板根NFS文件系统的时候,发现了下面的问题: Looking up port of RPC 100003/2 on 192.168.2.109rpcbind: server 192.168.2 ...

  3. AOP编程

    <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...

  4. android 高效显示Bitmap - 开发文档翻译

    由于本人英文能力实在有限,不足之初敬请谅解 本博客只要没有注明“转”,那么均为原创,转贴请注明本博客链接链接 Displaying Bitmaps Efficiently 高效显示Bitmap Lea ...

  5. Codeforces Round #189 (Div. 2)

    题目地址:http://codeforces.com/contest/320 第一题:基本题,判断mod 1000,mod 100.,mod 10是不是等于144.14.1,直到为0 代码如下: #i ...

  6. Spring源代码解析 ---- 循环依赖

    一.循环引用: 1. 定义: 循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比方CircularityA引用CircularityB,CircularityB引用Circularit ...

  7. Collections在sort()简单分析法源

    Collections的sort方法代码: public static <T> void sort(List<T> list, Comparator<? super T& ...

  8. 使用MDK将STM32的标准库编译成lib使用

    1 .使用MDK将STM32的标准库编译成lib使用[图文]  http://www.cnblogs.com/zyqgold/p/3189719.html

  9. Eclipse用法和技巧十八:减少不必要的输入

    写代码的时候,很多人都有一个原则,尽量上输入.依靠IDE自动生成的代码,一般可读性,排版什么的都还是不错的,最主要的一般不会有什么低级错误.今天介绍几个在eclipse环境中,常用的依靠eclipse ...

  10. swift 有些语法还是不支持。

    <pre name="code" class="html">"func hasAnyMatches(list: Int[], condit ...