Windows下使用标准Shell接口遍历文件和文件夹(2)

TreeView实现目录树-tree_view的相关方法

使用TreeView实现目录树,其中每个节点都是一个文件夹对象(也可能是虚拟文件夹)。而ListView显示所有的文件对象和子文件夹对象。如下图:

TreeView

如果某个文件夹对象拥有子文件夹,这个文件夹所对应的节点前面会有一个表示可以展开的符号“+”。点击“+”可以展开当前节点,显示这个文件夹对象中的所有子文件夹对象。

实现的时候,我们的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: }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值