TreeView实现目录树-tree_view的相关方法
使用TreeView实现目录树,其中每个节点都是一个文件夹对象(也可能是虚拟文件夹)。而ListView显示所有的文件对象和子文件夹对象。如下图:
如果某个文件夹对象拥有子文件夹,这个文件夹所对应的节点前面会有一个表示可以展开的符号“+”。点击“+”可以展开当前节点,显示这个文件夹对象中的所有子文件夹对象。
实现的时候,我们的TreeView包含一个普通的子窗体和一个TreeView窗体,将这两个窗体作为TreeView使用。TreeView窗体是普通子窗体的子窗体。在使用的时候,如果在主窗体使用我们提供的方法创建TreeView,实际上是在主窗体上创建了一个子窗体,在子窗体中创建一个TreeView。下面要实现的是一个普通的TreeView所必备的一些功能的封装。我称为tree_view。要实现真正的显示本地的命名空间树,或者显示远程主机的命名空间树的时候,可以扩展使用tree_view来实现。
tree_view的实现
下面先定义了一些要使用的回调函数:
1: typedef HRESULT(*pfn_tv_after_create)(struct tree_view* tv);
2: typedef BOOL(*pfn_tv_first_expanding)(struct tree_view* tv, LPTVITEM ptvi);
3: typedef BOOL(*pfn_tv_expanding)(struct tree_view* tv, LPTVITEM ptvi);
4: typedef BOOL(*pfn_tv_collapse)(struct tree_view* tv, LPTVITEM ptvi);
5: typedef HRESULT(*pfn_tv_delitem)(struct tree_view* tv, LPTVITEM ptvi);
6: typedef HRESULT(*pfn_tv_selchanged)(struct tree_view* tv, LPTVITEM ptvi_old, LPTVITEM ptvi_new);
7: typedef HRESULT(*pfn_tv_check_drag_type)(struct tree_view* tv, CLIPFORMAT cfFormat);
8: typedef HRESULT(*pfn_tv_drop)(struct tree_view* tv, CLIPFORMAT cfFormat, STGMEDIUM medium);
9: typedef HRESULT(*pfn_tv_begin_drag) (struct tree_view* tv, LPNMTREEVIEW lpNMTV);
这些回调函数实际上是为了扩展准备的,供给tree_view的使用者实现一些特殊功能所使用的。
下面的结构保存所有的回调函数指针:
1: /**
2: * TreeView使用到的事件处理函数指针集合。
3: * @struct tv_callback
4: */
5: struct tv_callback
6: {
7: pfn_tv_after_create fn_after_create; /**< 当创建TreeView完成后调用的函数 */
8: pfn_tv_first_expanding fn_first_expanding; /**< 当某个节点第一次被展开时调用的函数 */
9: pfn_tv_expanding fn_expanding; /**< 当某个节点不是第一次,但是被展开时调用的函数 */
10: pfn_tv_collapse fn_collapse; /**< 当某个节点被合并时调用的函数 */
11: pfn_tv_delitem fn_delitem; /**< 当某个节点被删除时调用的函数 */
12: pfn_tv_selchanged fn_selchanged; /**< 当选择了一个新的节点时调用的函数 */
13: pfn_tv_check_drag_type fn_check_drag_type; /**< 判断给定的拖拽对象类型是否可以接受 */
14: pfn_tv_drop fn_drop; /**< 一个对象被拖拽到窗口这个函数进行实际操作 */
15: pfn_tv_begin_drag fn_begin_drag; /**< 开始拖拽某个对象时调用的函数 */
16: };
下面的结构保持着tree_view所需要的所有信息:
1: /**
2: * TREEVIEW结构
3: * @struct tree_view
4: */
5: struct tree_view
6: {
7: struct tv_callback cb; /**< TreeView使用到的回调函数 */
8: HWND hParent; /**< 父窗体句柄 */
9: HWND hWnd; /**< TreeView父窗体窗体句柄 */
10: HWND hTree; /**< TreeView的句柄 */
11: ATOM atom; /**< 注册窗体类的ATOM对象 */
12: BOOL fDragging; /**< 是否正在拖拽的标志 */
13: LPVOID lpArgs; /**< 用户参数 */
14: };
下面这个结构是tree_view中每个节点需要保存的信息:
1: /**
2: * TreeView中结点的信息。
3: * @struct tv_node_info
4: */
5: struct tv_node_info
6: {
7: LPTSTR lpszText; /**< 节点显示的文字 */
8: int iImage; /**< 小图标索引 */
9: int iSelImage; /**< 选择图标索引 */
10: int iHasChild; /**< 是否有子结点 */
11: HTREEITEM hParent; /**< 父节点句柄 */
12: HTREEITEM hAfter; /**< 在某个兄弟节点的后面 */
13: LPVOID lpParam; /**< 参数 */
14: };
下面是提供的对外方法:
1: /**
2: * 创建TREE VIEW。
3: * @param hParent 父窗体句柄。
4: * @param lpszClassName 创建的窗体类名。
5: * @param nID TreeView的控件ID。
6: * @param lpszTitle 创建的窗体标题名称。
7: * @param rtPos 创建的窗体大小,由于TreeView充满整个新创建的窗体,所以这也是TreeView的大小。
8: * @param cb 设置TreeView所有要调用的回调函数。
9: * @param lpArgs 用户自定义参数。
10: * @return 创建成功返回tree_view结构对象指针。
11: */
12: struct tree_view* tv_create (HWND hParent, LPCTSTR lpszClassName, UINT nID, LPCTSTR lpszTitle, RECT rtPos,
13: struct tv_callback cb, LPVOID lpArgs);
14:
15: /**
16: * 删除tree_view结构对象指针,并删除说有窗体。
17: * @param tv 要删除的tree_view结构对象指针。
18: */
19: void tv_free (struct tree_view* tv);
20:
21: /**
22: * 创建的新窗体的消息处理函数。
23: * @param hWnd 窗体句柄。
24: * @param message 接收到的消息。
25: * @param wParam 参数。
26: * @param lParam 参数。
27: */
28: LRESULT CALLBACK tv_WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
29:
30: /**
31: * 获得TreeView句柄。
32: * @param tv tree_view结构对象指针。
33: * @return 返回TreeView句柄。
34: */
35: HWND tv_get_tree_view (struct tree_view* tv);
36:
37: /**
38: * 插入一个节点。
39: * @param tv tv tree_view结构对象指针。
40: * @param tvni tv_node_info 结构对象指针。
41: * @return 插入成功返回新节点句柄。
42: */
43: HTREEITEM tv_insert_node (struct tree_view* tv, struct tv_node_info* tvni);
44:
45: /**
46: * 负责构造IDataObject和IDataSource接口,并调用DoDragDrop。
47: * @param pFormatEtc 类型数组。
48: * @param pStgMedium 每种类型对应的数据数组。
49: * @param nCount 类型数组和数据数组中元素的个数。
50: * @param pArgs 用户参数。
51: * @return 成功返回S_OK。
52: */
53: HRESULT tv_DoDragDrop (FORMATETC* pFormatEtc, STGMEDIUM* pStgMedium, UINT nCount, void* pArgs);
下面逐步介绍各个方法
struct tree_view* tv_create (HWND hParent, LPCTSTR lpszClassName, UINT nID, LPCTSTR lpszTitle, RECT rtPos, struct tv_callback cb, LPVOID lpArgs);
这个方法创建一个tree_view对象指针,这个对象指针不再使用的时候需要调用tv_free来释放。
1: struct tree_view*
2: tv_create (HWND hParent, LPCTSTR lpszClassName, UINT nID, LPCTSTR lpszTitle, RECT rtPos,
3: struct tv_callback cb, LPVOID lpArgs)
4: {
5: struct tree_view* tv = NULL;
6: HINSTANCE hInst = NULL;
7: WNDCLASSEX wcex;
8: HWND hWnd;
9:
10: tv = (struct tree_view*) malloc (sizeof (struct tree_view));
11: if (NULL == tv)
12: {
13: return NULL;
14: }
15: ZeroMemory (tv, sizeof (struct tree_view));
16:
17: hInst = (HINSTANCE) GetWindowLong (hParent, GWL_HINSTANCE);
18: ZeroMemory (&wcex, sizeof (WNDCLASSEX));
19: wcex.cbSize = sizeof(WNDCLASSEX);
20:
21: wcex.style = CS_HREDRAW | CS_VREDRAW;
22: wcex.lpfnWndProc = tv_WndProc;
23: wcex.cbClsExtra = 0;
24: wcex.cbWndExtra = 0;
25: wcex.hInstance = hInst;
26: wcex.hIcon = NULL;
27: wcex.hCursor = NULL;
28: wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
29: wcex.lpszMenuName = NULL;
30: wcex.lpszClassName = lpszClassName;
31: wcex.hIconSm = NULL;
32:
33: tv->atom = RegisterClassEx(&wcex);
34: if (!tv->atom)
35: {
36: free (tv);
37: return NULL;
38: }
39:
40: tv->hParent = hParent;
41: memcpy (&(tv->cb), &cb, sizeof (struct tv_callback));
42: tv->lpArgs = lpArgs;
43:
44: hWnd = CreateWindowEx (WS_EX_CLIENTEDGE, tv->atom, lpszTitle, WS_CHILD | WS_VISIBLE,
45: rtPos.left, rtPos.top, rtPos.right-rtPos.left, rtPos.bottom-rtPos.top, hParent, NULL, hInst, tv);
46:
47: if (!hWnd)
48: {
49: DWORD dwErr = GetLastError ();
50: free (tv);
51: return NULL;
52: }
53:
54: ShowWindow (hWnd, SW_SHOW);
55: UpdateWindow (hWnd);
56:
57: return tv;
58: }
这个函数创建了tree_view结构对象并注册一个窗体类,最后调用CreateWindowEx创建一个窗体,这个窗体就是TreeView的父窗体,我们会在这个窗体的WM_CREATE消息中创建TreeView。用户传递的参数pArgs保持在tree_view的pArgs成员中,这是用户数据。特别注意的是在调用CreateWindowEx时,最后一个参数传递了tree_view的指针,这个指针在tree_view的父窗体(以后称为当前窗体)接收到WM_CREATE消息时放在CREATESTRUCT结构的lParam成员中。
void tv_free (struct tree_view* tv);
这个方法销毁创建的当前窗体和tree_view对象。
1: void
2: tv_free (struct tree_view* tv)
3: {
4: HINSTANCE hInst = NULL;
5: if (tv)
6: {
7: if (tv->hWnd)
8: {
9: DestroyWindow (tv->hWnd);
10: }
11: if (tv->atom)
12: {
13: hInst = (HINSTANCE) GetWindowLong (tv->hParent, GWL_HINSTANCE);
14: UnregisterClass ((LPCTSTR)tv->atom, hInst);
15: }
16: free (tv);
17: }
18: }
下面是当前窗体的消息响应函数,我们先给出代码,然后进行详细介绍:
1: LRESULT CALLBACK
2: tv_WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
3: {
4: switch (message)
5: {
6: case WM_CREATE:
7: {
8: CREATESTRUCT* cs;
9: cs = (CREATESTRUCT*) lParam;
10: return _tv_on_create (hWnd, cs);
11: }
12: break;
13: case WM_NOTIFY:
14: {
15: struct tree_view* tv = (struct tree_view*) GetWindowLong (hWnd, GWL_USERDATA);
16: if (NULL == tv)
17: {
18: return TRUE; // 有错误,不能展开
19: }
20: switch (((LPNMHDR) lParam)->code)
21: {
22: case TVN_ITEMEXPANDING:
23: {
24: LPNMTREEVIEW lpNMTV = (LPNMTREEVIEW) lParam;
25: switch (lpNMTV->action)
26: {
27: case TVE_EXPAND:
28: {
29: if (!(lpNMTV->itemNew.state & TVIS_EXPANDEDONCE))
30: {
31: return tv->cb.fn_first_expanding (tv, &(lpNMTV->itemNew));
32: }
33: else
34: {
35: return tv->cb.fn_expanding (tv, &(lpNMTV->itemNew));
36: }
37: }
38: break;
39: case TVE_COLLAPSE:
40: {
41: return tv->cb.fn_collapse (tv, &(lpNMTV->itemNew));
42: }
43: break;
44: }
45: }
46: break;
47: case TVN_DELETEITEM:
48: {
49: LPNMTREEVIEW lpNMTV = (LPNMTREEVIEW) lParam;
50: return tv->cb.fn_delitem (tv, &(lpNMTV->itemOld));
51: }
52: break;
53: case TVN_SELCHANGED:
54: {
55: LPNMTREEVIEW lpNMTV = (LPNMTREEVIEW) lParam;
56: return tv->cb.fn_selchanged (tv, &(lpNMTV->itemOld), &(lpNMTV->itemNew));
57: }
58: break;
59: case TVN_BEGINDRAG:
60: {
61: LPNMTREEVIEW* lpNMTV = (LPNMTREEVIEW) lParam;
62: return _tv_on_begin_drag (tv, lpNMTV);
63: }
64: break;
65: }
66: }
67: break;
68: case WM_MOUSEMOVE:
69: {
70: struct tree_view* tv = (struct tree_view*) GetWindowLong (hWnd, GWL_USERDATA);
71: return _tv_on_mouse_move_drag (tv, wParam, lParam);
72: }
73: break;
74: case WM_LBUTTONUP:
75: {
76: struct tree_view* tv = (struct tree_view*) GetWindowLong (hWnd, GWL_USERDATA);
77: return _tv_on_lbtn_up_drag (tv, wParam, lParam);
78: }
79: break;
80: case TV_M_CHANGEFOCUS:
81: {
82: struct tree_view* tv = (struct tree_view*) GetWindowLong (hWnd, GWL_USERDATA);
83: TreeView_SelectDropTarget(tv->hTree, NULL);
84: TreeView_SelectItem(tv->hTree, (HTREEITEM)lParam) ;
85: }
86: break;
87: default:
88: return DefWindowProc(hWnd, message, wParam, lParam);
89: }
90: return S_OK;
91: }
WM_CREATE 消息
当调用CreateWindowEx创建当前窗体时,我们在最后的参数中传入了tree_view对象的指针。这里通过CREATESTRUCT结构的lParam成员获得了这个指针,并调用了我们自己的内部函数_tv_on_create:
1: static LRESULT
2: _tv_on_create (HWND hWnd, CREATESTRUCT* cs)
3: {
4: HRESULT hr;
5: RECT rt;
6: HWND hTree = NULL;
7: struct tree_view* tv = NULL;
8: IDropTarget* pDT = NULL;
9: struct idt_lpVtbl idtVtbl;
10:
11: GetClientRect (hWnd, &rt);
12:
13: // 创建TreeView
14: hTree = CreateWindow (WC_TREEVIEW,
15: "",
16: WS_CHILD | LVS_REPORT | WS_VISIBLE|TVS_HASLINES|TVS_HASBUTTONS|TVS_LINESATROOT|TVS_SHOWSELALWAYS ,
17: 0,
18: 0,
19: rt.right-rt.left,
20: rt.bottom-rt.top,
21: hWnd,
22: (HMENU) ID_LOCAL_DIR_TREE,
23: (HINSTANCE) GetWindowLong (hWnd, GWL_HINSTANCE),
24: NULL);
25: if (NULL == hTree)
26: {
27: return -1;
28: }
29:
30: tv = (struct tree_view*) cs->lpCreateParams;
31: tv->hTree = hTree;
32: tv->hWnd = hWnd;
33:
34: // 设置窗体参数
35: SetWindowLong (hWnd, GWL_USERDATA, (LONG) tv);
36:
37: /*
38: // 注册为支持拖拽窗体
39: ZeroMemory (&idtVtbl, sizeof (struct idt_lpVtbl));
40: idtVtbl.DragEnter = _tv_DragEnter;
41: idtVtbl.DragLeave = _tv_DragLeave;
42: idtVtbl.DragOver = _tv_DragOver;
43: idtVtbl.Drop = _tv_Drop;
44: pDT = idt_create (&idtVtbl, tv);
45: hr = RegisterDragDrop (hWnd, pDT);
46: if (FAILED (hr))
47: {
48: return -1;
49: }
50: */
51: if (tv->cb.fn_after_create)
52: {
53: return tv->cb.fn_after_create (tv);
54: }
55: return S_OK;
56: }
与拖拽有关的方法我都注释掉了,因为在测试拖拽的时候遇到了一些问题。这个方法调用CreateWindow创建了一个TreeView子窗体,并将tree_view对象指针保存在窗体的用户数据中,以便以后处理其它消息的时候使用。最后我们调用了用户的回调函数。
下面我们看一下WM_NOTIFY消息,这个消息用于当前窗体处理TreeView的事件。我们处理了TVN_ITEMEXPANDING、TVN_DELETEITEM、TVN_SELCHANGED和TVN_BEGINDRAG事件。
TVN_ITEMEXPANDING 事件
当TreeView中的某个节点被展开或者收缩的时候,这个事件被激发。这时,WM_NOTIFY消息的lParam参数是NMTREEVIEW结构的指针。NMTREEVIEW结构中的action成员表示当前节点是被展(TVE_EXPAND)开还是被收缩(TVE_COLLAPSE)。NMTREEVIEW结构中的itemNew表示发出事件的当前节点。如果是被展开,当前节点的state成员标识着当前节点的状态,当某个节点第一次被展开时,state的TVIS_EXPANDEDONCE标志没有被置位,第一次展开完成后,state的TVIS_EXPANDEDONCE标志被置位。所以我们通过探测TVIS_EXPANDEDONCE标志是否被置位,来判断当前节点是否是第一次被展开。通常在某个节点第一次被展开时,准备相关的数据(如创建并填充所有的子节点);而不是第一次被展开时,我们可以什么都不做。无论是被展开还是被收缩,具体的操作我们都是调用回调函数,让用户实现。
TVN_DELETEITEM 事件
当TreeView中某个节点被删除时,这个事件被激发。这时,WM_NOTIFY消息的lParam参数是NMTREEVIEW结构的指针。NMTREEVIEW结构中的itemOld成员表示要被删除的节点。在这里我们调用用户参数,因为节点是用户创建的,用户可能在节点中存储了用户自定义信息,在这里用户可以释放用户自定义信息所使用的资源。
TVN_SELCHANGED 事件
当TreeView中某个节点被选中时,这个事件被激发。这时,WM_NOTIFY消息的lParam参数是NMTREEVIEW结构的指针。其中itemOld表示以前被选中的节点,itemNew表示后来被选中的新节点。这里调用用户的回调函数,让用户处理,如控制ListView显示当前选中的新节点的文件对象和子文件夹对象等。
TVN_BEGINDRAG 事件
当TreeView中某个节点开始被拖拽时,这个事件被激发。这时,WM_NOTIFY消息的lParam参数是NMTREEVIEW结构的指针。NMTREEVIEW结构中的itemNew表示被拖拽的当前节点。这里调用_tv_on_begin_drag函数:
1: static LRESULT
2: _tv_on_begin_drag (struct tree_view* tv, LPNMTREEVIEW lpNMTV)
3: {
4: /*
5: HIMAGELIST hDrag = NULL;
6: if (tv->cb.fn_begin_drag)
7: {
8: if (S_OK != tv->cb.fn_begin_drag (tv, lpNMTV))
9: {
10: return S_OK;
11: }
12: }
13: //Create an image list that holds our drag image
14: hDrag = TreeView_CreateDragImage(tv->hTree, lpNMTV->itemNew.hItem) ;
15: //begin the drag operation
16: ImageList_BeginDrag(hDrag, 0, 0, 0) ;
17: //hide the cursor
18: ShowCursor(FALSE) ;
19: //capture the mouse
20: SetCapture(GetParent(tv->hTree)) ;
21: //set global flag to indicate we are in the middle of a drag operation
22: tv->fDragging = TRUE ;
23: //convert coordinates to screen coordinates
24: ClientToScreen(tv->hTree, &(lpNMTV->ptDrag)) ;
25: //paint our drag image and lock the screen.
26: ImageList_DragEnter(NULL, lpNMTV->ptDrag.x, lpNMTV->ptDrag.y) ;
27: */
28: return S_OK ;
29: }
这个函数被注释的部分先调用了用户的回调函数,然后设置拖拽的图标信息。
WM_MOUSEMOVE 消息
这个消息中调用了_tv_on_mouse_move_drag来处理拖拽过程中的图标显示:
1: static BOOL
2: _tv_on_mouse_move_drag (struct tree_view* tv, WPARAM wParam, LPARAM lParam)
3: {
4: POINT pnt;
5: HTREEITEM hItem = NULL;
6: TVHITTESTINFO tv_ht;
7:
8: pnt.x = GET_X_LPARAM(lParam);
9: pnt.y = GET_Y_LPARAM(lParam);
10:
11: if (tv->fDragging)
12: {
13: //unlock window and allow updates to occur
14: ImageList_DragLeave(NULL) ;
15: ClientToScreen(tv->hWnd, &pnt) ;
16: //check with the tree control to see if we are on an item
17: ZeroMemory(&tv_ht, sizeof(TVHITTESTINFO));
18: tv_ht.flags = TVHT_ONITEM;
19: tv_ht.pt.x = pnt.x;
20: tv_ht.pt.y = pnt.y;
21: ScreenToClient(tv->hTree, &(tv_ht.pt));
22: hItem = (HTREEITEM)SendMessage(tv->hTree, TVM_HITTEST, 0, (LPARAM)&tv_ht);
23:
24: if (hItem)
25: {
26: //if we had a hit, then drop highlite the item
27: TreeView_SelectItem (tv->hTree, hItem);
28: }
29:
30: //paint the image in the new location
31: ImageList_DragMove(pnt.x,pnt.y);
32: //lock the screen again
33: ImageList_DragEnter(NULL, pnt.x, pnt.y);
34: }
35: return TRUE;
36: }
在这个处理过程中,不断的探测鼠标拖拽的对象覆盖了哪个节点,使被覆盖的节点处于被选中状态。
WM_LBUTTONUP 消息
这个消息表示拖拽结束,调用_tv_on_lbtn_up_drag停止拖拽图标的显示:
1: static BOOL
2: _tv_on_lbtn_up_drag (struct tree_view* tv, WPARAM wParam, LPARAM lParam)
3: {
4: HTREEITEM hItem = NULL;
5: TVHITTESTINFO tv_ht;
6: TVITEM tvi;
7: ZeroMemory(&tvi, sizeof(TVITEM));
8: ZeroMemory(&tv_ht, sizeof(TVHITTESTINFO));
9:
10: if (tv->fDragging)
11: {
12: ImageList_DragLeave(NULL);
13: ImageList_EndDrag();
14: ReleaseCapture();
15: //determin if we let up on an item
16: GetCursorPos(&(tv_ht.pt));
17: ScreenToClient(tv->hTree, &(tv_ht.pt));
18: tv_ht.flags = TVHT_ONITEM;
19: hItem = (HTREEITEM)SendMessage(tv->hTree, TVM_HITTEST, 0, (LPARAM)&tv_ht);
20: ShowCursor(TRUE);
21: tv->fDragging = FALSE;
22: if (hItem)
23: {
24: /*we need to defer changing the selection until done processing this message post message allows us to do this. */
25: PostMessage(tv->hWnd, TV_M_CHANGEFOCUS, (WPARAM)0, (LPARAM)hItem);
26: }
27: }
28: return TRUE;
29: }
在这个消息中调用PostMessage发送了一个自定义的TV_M_CHANGEFOCUS异步消息,在这个消息中,我们调用TreeView_SelectDropTarget来选中最终拖拽的目标节点。
HWND tv_get_tree_view (struct tree_view* tv);
用于获得tree_view中的TreeView窗体句柄。
1: HWND
2: tv_get_tree_view (struct tree_view* tv)
3: {
4: if (tv)
5: {
6: return tv->hTree;
7: }
8: return NULL;
9: }
HTREEITEM tv_insert_node (struct tree_view* tv, struct tv_node_info* tvni);
用于插入一个新节点:
1: HTREEITEM
2: tv_insert_node (struct tree_view* tv, struct tv_node_info* tvni)
3: {
4: TVINSERTSTRUCT tvins;
5: TVITEM tvi;
6: if (NULL == tv || NULL == tvni)
7: {
8: return NULL;
9: }
10:
11: ZeroMemory (&tvins, sizeof (TVINSERTSTRUCT));
12: ZeroMemory (&tvi, sizeof (TVITEM));
13:
14: tvi.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM;
15: if (tvni->iHasChild)
16: {
17: tvi.mask |= TVIF_CHILDREN;
18: tvi.cChildren = 1;
19: }
20: tvi.cchTextMax = MAX_PATH;
21: tvi.pszText = tvni->lpszText;
22: tvi.iImage = tvni->iImage;
23: tvi.iSelectedImage = tvni->iSelImage;
24: tvi.lParam = (LPARAM) tvni->lpParam;
25: tvins.item = tvi;
26: tvins.hInsertAfter = tvni->hAfter;
27: tvins.hParent = tvni->hParent;
28: return TreeView_InsertItem (tv->hTree, &tvins);
29: }
在这个函数中,调用TreeView_InsertIterm来插入节点,并且设置了节点的文字、图片、被选中时的图片以及用户参数。
HRESULT tv_DoDragDrop (FORMATETC* pFormatEtc, STGMEDIUM* pStgMedium, UINT nCount, void* pArgs);
这个函数用于拖拽某个节点后,构造拖拽信息,并调用DoDragDrop函数进行拖拽后的处理。其中需要实现IDataObject和IDropSource这两个系统接口。如果想实现将某个节点表示的文件夹拖拽到支持拖拽的目标后,将这个文件夹拷贝到目标对象,那么必须调用这个函数。它可以支持将节点拖拽到Explorer。以后再详细介绍吧:
1: HRESULT tv_DoDragDrop (FORMATETC* pFormatEtc, STGMEDIUM* pStgMedium, UINT nCount, void* pArgs)
2: {
3:
4: IDataObject* pDO = NULL;
5: IDropSource* pDS = NULL;
6: struct ido_lpVtbl ido_vtbl;
7: struct ids_lpVtbl ids_vtbl;
8: DWORD dwOKEffect;
9:
10: ZeroMemory (&ido_vtbl, sizeof (struct ido_lpVtbl));
11: ZeroMemory (&ids_vtbl, sizeof (struct ids_lpVtbl));
12:
13: ido_vtbl.DAdvise = _tv_DAdvise;
14: ido_vtbl.DUnadvise = _tv_DUnadvise;
15: ido_vtbl.EnumDAdvise = _tv_EnumDAdvise;
16: ido_vtbl.EnumFormatEtc = _tv_EnumFormatEtc;
17: ido_vtbl.GetCanonicalFormatEtc = _tv_GetCanonicalFormatEtc;
18: ido_vtbl.GetData = _tv_GetData;
19: ido_vtbl.GetDataHere = _tv_GetDataHere;
20: ido_vtbl.QueryGetData = _tv_QueryGetData;
21: ido_vtbl.SetData = _tv_SetData;
22:
23: pDO = ido_create (&ido_vtbl, pFormatEtc, pStgMedium, nCount, pArgs);
24: if (NULL == pDO)
25: {
26: return S_FALSE;
27: }
28:
29: ids_vtbl.GiveFeedback = _tv_GiveFeedback;
30: ids_vtbl.QueryContinueDrag = _tv_QueryContinueDrag;
31:
32: pDS = ids_create (&ids_vtbl, pArgs);
33: if (NULL == pDS)
34: {
35: pDO->lpVtbl->Release (pDO);
36: return S_FALSE;
37: }
38:
39: ZeroMemory (&dwOKEffect, sizeof (DWORD));
40:
41: return DoDragDrop (pDO, pDS, DROPEFFECT_COPY, &dwOKEffect);
42: }