边城浪子(QQ:16168666)
在网上找了很长时间也没有关于这方面的VC代码.倒是找到几个控件,用起来却很不爽.很奇怪WINDOWS里为什么没有这样的控件.没办法.自己实现一个呗. 下面这个类从CTreeView拜生.参考了DELPHI的相应控件源代码写的,本人对DELPHI不会,所以看得很勉强,好多地方也没看明白.不过这个类使用起来的效果,嘿嘿.已经和那个控件差不多了:).只是比起Explorer里的来,还差着一些.具体差在什么地方,大家用一用就知道了.
将你的类从此类拜生,就能做你想做的了. 该类只导出两个函数: 1.SetCurPath : 设置选中一个路径.如果需要的话,它会展开相应有文件夹 2.GetSelectedDir : 返回当前选中的文件夹,没有选中的话,返回自然就是空串
如果你有兴趣,自己对这个类进行扩充吧.欢迎和我交流.
////////////////////////////////////////////////////////////////////////// // ShellFolderView.h ---- 头文件 #if !defined(AFX_SHELLFOLDERVIEW_H__D30D10ED_3D11_4873_9909_146B8E18BEB1__INCLUDED_) #define AFX_SHELLFOLDERVIEW_H__D30D10ED_3D11_4873_9909_146B8E18BEB1__INCLUDED_
#if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 // ShellFolderView.h : header file //********************************************************************** // 该类根据DELPHI相应控件源代码改编 /*********************************************************************** 简介: CShellFolderView从CTreeView拜生,它显示整个WINDOWS的文件目录树.在WINDOWS中 又叫外壳名字空间(Shell Name Space).外壳名字空间是Windows下的标准文件系统, 它大大扩展了Dos文件系统,形成了以“桌面”(Desktop)为根的单一的文件系统 树,原有的C盘、D盘等目录树变成了“我的电脑”这一外壳名字空间子树的下一级 子树,而像“控制面板”、“回收站”、“网上邻居”等应用程序及“打印机”等设 备也被虚拟成了外壳名字空间中的节点。另外,与DOS中物理存储只能和文件系统 项一一对应这一点不同的是,一个实际目录在外壳名字空间中可以表现为不同的项。 例如“我的文档”与“C:\My Documents”其实都指向“C:\My Documents”目录,但 它们在外壳名字空间中是不同的项。
外壳名字空间下的路径: PIDL PIDL是一个元素类型为ITEMIDLIST结构的数组,数组中元素的个数是未知的,但紧接 着数组末尾的必是一个双字节的零。每个数组元素代表了外壳名字空间树中的一层( 即一个文件夹或文件),数组中的前一元素代表的是后一元素的父文件夹。由此可见, PIDL实际上就是指向一块由若干个顺序排列的ITEMIDLIST结构组成、并在最后有一个 双字节零的空间的指针。所以PIDL的类型就被Windows定义为ITEMIDLIST结构的指针。
PIDL亦有“绝对路径”与“相对路径”的概念。表示“相对路径”的PIDL只有一个 ITEMIDLIST结构的元素,用于标识相对于父文件夹的“路径”;表示“绝对路径”的 PIDL(简称为“绝对PIDL”)有若干个ITEMIDLIST结构的元素,第一个元素表示外壳 名字空间根文件夹(“桌面”)下的某一子文件夹A,第二个元素则表示文件夹A下的 某一子文件夹B,其余依此类推。这样绝对PIDL就通过保存一条从“桌面”下的直接子 文件夹或文件的绝对PIDL与相对PIDL是相同的,而其他的文件夹或文件的相对PIDL就 只是其绝对PIDL的最后一部分了。由于所有的PIDL都是从桌面下的某一个子文件夹开 始的,所以对于桌面本身来说,它的PIDL数组显然一个元素都没有。这样就只剩下PIDL 数组最后的那个双字节的零了。所以,“桌面”的PIDL就是一个16位的零。
**********************************************************************/ // 监视指定的文件夹的变化的类. 该类由CShellFolderView专用,所以定义在源文件中 // 不对外部公开,在这里进行提前声明,因为下面要用到 class CFolderChangeMonitor;
// 外壳名字空间下结点的串表示 typedef struct _FILEPATHLIST { char cNormal[MAX_PATH]; // 绝对名称(如,"我的电脑") char cForParsing[MAX_PATH]; // 用来分析解释的名称, 可以根据它来产生PIDL char cInFolder[MAX_PATH]; // 相对名称 } FILEPATHLIST, *LPFILEPATHLIST;
// 文件夹的分类 typedef enum emFolderType{ ftFolder, // 文件夹 ftNetworkNeighborhood, // 网上邻居 ftRecycleBin, // 回收站 ftMyComputer, // 我的电脑 ftDesktop, // 桌面 ftNetNode, // 网络结点 ftNone // 空 }FOLDERTYPE;
// 路径信息 typedef struct _PATHINFO{ char cPath[MAX_PATH]; // 路径 char cText[MAX_PATH]; // 名称 UINT uIconIndex; // 图标索引 UINT uSelectedIndex; // 选中时的图标索引 LPITEMIDLIST pidl; // PIDL }PATHINFO,*LPPATHINFO;
// 结点数据 typedef struct _FOLDERNODE{ char cPath[MAX_PATH]; // 路径 BOOL bHasParent; // 是否有父结点 LONG Vol_Ser; // 卷标 ULONG dwAttributes; // 属性 FOLDERTYPE fType; // 类型 IShellFolder* pShellFolder; // 指向该文件夹的IShellFolder接口 LPITEMIDLIST pidl; // 文件夹的PIDL WORD widlen; // PIDL的长度 CFolderChangeMonitor* pMonitorThread; // 监视文件夹变化的线程 }FOLDERNODE, *LPFOLDERNODE;
///////////////////////////////////////////////////////////////////////////// // CShellFolderView view
class CShellFolderView : public CTreeView { protected: CShellFolderView(); // protected constructor used by dynamic creation DECLARE_DYNCREATE(CShellFolderView)
// Attributes public:
// Operations public: BOOL SetCurPath(LPCTSTR lpszPath); // 设置当前选中的文件夹, 成功返回TRUE CString GetSelectedDir(); // 取得当前选中的文件夹,没有则返回一个空串
// Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CShellFolderView) protected: //virtual void OnDraw(CDC* pDC); // overridden to draw this view //}}AFX_VIRTUAL
// Implementation protected: virtual ~CShellFolderView(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif
// Generated message map functions protected: //{{AFX_MSG(CShellFolderView) afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnItemexpanding(NMHDR* pNMHDR, LRESULT* pResult); afx_msg void OnDestroy(); //}}AFX_MSG afx_msg LRESULT OnFolderChanged(WPARAM wParam, LPARAM); // 处理文件夹发生变化的消息 DECLARE_MESSAGE_MAP() private: IMalloc* m_pMalloc; // COM内存分配接口用来分配和回收PIDL使用的空间 protected: void GetSpecialFolder(int nFolder, LPPATHINFO lppi); // 取得指定的特殊文件夹的信息 void GetNetHood(LPPATHINFO lpPI); // 取得网上邻居结点 // 创建一个结点 HTREEITEM CreateFolderNode(LPITEMIDLIST lpidl, HTREEITEM hParent, BOOL bVirtual); CString GetPath(HTREEITEM hNode); // 取得结点的对应的路径 BOOL AttachFolders(HTREEITEM hNode); // 展开指定的结点下的子结点 LONG GetVol_Ser(LPCTSTR strDriver); // 获取卷标 void ShowDesktop(); // 显示桌面 void RetrieveSysImageList(); // 获取系统图像列表 void FreeNode(HTREEITEM hNode); // 释放结点 void FreeNodeData(LPFOLDERNODE lpfn); // 释放结点数据 int Expand(HTREEITEM hNode, UINT nCode); // 展开结点
};
/////////////////////////////////////////////////////////////////////////////
//{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_SHELLFOLDERVIEW_H__D30D10ED_3D11_4873_9909_146B8E18BEB1__INCLUDED_)
/////////////////////////////////////////////////////////////////////////// // ShellFolderView.cpp : implementation file ///////////////////////////////////////////////////////////////////////////
#include "stdafx.h" #include "ShellFolderView.h"
#ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif
#define WM_U_FOLDERCHANGED (WM_USER+215) // 消息
// 一些固定的名字: static const char c_cMyComputer[] = "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}"; // Normal = 我的电脑 static const char c_cNetworkNeighborhood[] = "::{208D2C60-3AEA-1069-A2D7-08002B30309D}"; // Normal = 网上邻居 static const char c_cRecycleBin[] = "::{645FF040-5081-101B-9F08-00AA002F954E}"; // Normal = 回收站 static const char c_cController[] = "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\::{21EC2020-3AEA-1069-A2DD-08002B30309D}"; // Normal = 控制面板 static const char c_cPrinter[] = "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\::{21EC2020-3AEA-1069-A2DD-08002B30309D}\\::{2227A280-3AEA-1069-A2DE-08002B30309D}"; // Normal = 打印机 static const char c_cDial[] = "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\::{21EC2020-3AEA-1069-A2DD-08002B30309D}\\::{7007ACC7-3202-11D1-AAD2-00805FC1270E}"; // Normal = 网络和拨号连接 static const char c_cFonts[] = "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\::{21EC2020-3AEA-1069-A2DD-08002B30309D}\\::{D20EA4E1-3957-11D2-A40B-0C5020524152}"; // Normal = 字体 static const char c_cManager[] = "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\::{21EC2020-3AEA-1069-A2DD-08002B30309D}\\::{D20EA4E1-3957-11D2-A40B-0C5020524153}"; // Normal = 管理工具 static const char c_cPlans[] = "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\::{21EC2020-3AEA-1069-A2DD-08002B30309D}\\::{D6277990-4C6A-11CF-8D87-00AA0060F5BF}"; // Normal = 任务计划
// 文件夹监视线程类 class CFolderChangeMonitor { public: CFolderChangeMonitor(LPCTSTR lpszPath, HWND hWnd, WPARAM wParam); ~CFolderChangeMonitor();
private: CWinThread* m_pThread; CString m_strPath; HWND m_hWnd; WPARAM m_wParam; BOOL m_bStop;
friend UINT MonitorProc(void* lp); void Run(); };
///////////////////////////////////////////////////////////////////////////// // CFolderChangeMonitor class ///////////////////////////////////////////////////////////////////////////// // 文件夹监视线程类的构造函数 // lpszPath : 被监视的文件夹 // hWnd : 文件夹已经改变的消息将发往该窗口 // wParam : 消息的参数,在这里是树上的一结点句柄 CFolderChangeMonitor::CFolderChangeMonitor(LPCTSTR lpszPath, HWND hWnd, WPARAM wParam) :m_hWnd(hWnd), m_wParam(wParam), m_strPath(lpszPath) { m_bStop = FALSE; // 开始监视线程 m_pThread = AfxBeginThread(MonitorProc, (void*)this); }
CFolderChangeMonitor::~CFolderChangeMonitor() { // 停止监视线程 m_bStop = TRUE; // 原来可以等待线程结束后才返回,后来不知道怎么搞的,居然会死锁,只好先不等待了,嘻嘻 // ::WaitForSingleObject(m_pThread->m_hThread, INFINITE); }
// 监视线程函数 UINT MonitorProc(void*lp) { if(lp) { // 将线程反调回类中 ((CFolderChangeMonitor*)lp)->Run(); } return 0; }
// 监视函数 void CFolderChangeMonitor::Run() { // 检测结束标志, 没有就一直循环 while(!m_bStop) { // 创建事件句柄,当文件夹发生了指定的变化时,该事件被触发 HANDLE handle = ::FindFirstChangeNotification(m_strPath, FALSE, FILE_NOTIFY_CHANGE_DIR_NAME); // 如果事件创建不成功则结束 if(handle == INVALID_HANDLE_VALUE) break;
// 开始监视 if(::FindNextChangeNotification(handle)) { int i = WAIT_TIMEOUT; // 除非指明结束监视或者发生了一次事件,否则一直循环等待 while(i == WAIT_TIMEOUT && !m_bStop) { // 等待事件发生(每次等待20mS)因为线程需要不断检测m_bStop // 标志,以便及时退出,所以不能无限制地等待下去 i = ::WaitForSingleObject(handle, 20); } // 循环结束,如果不是指定结束,则一定是发事件被触发了 if(!m_bStop) { // 发送消息通知窗口来更新目录树 ::SendMessage(m_hWnd, WM_U_FOLDERCHANGED, m_wParam,0); } } else { // 监视不能开始,则退出 ::FindCloseChangeNotification(handle); break; } // 关闭事件 ::FindCloseChangeNotification(handle); } }
/*************************************************************** FUNCTION : GetName
描述:lpio lpsf所指的IshellFolder接口代表的文件夹下的相对PIDL, 本函数获得lpi所指项的显示名称,dwFlags表明欲得到的显示名称类型, lpFriendlyName为存放显示名称的缓冲区。 ***************************************************************/
BOOL GetName(LPSHELLFOLDER lpsf,LPITEMIDLIST lpi,DWORD dwFlags,LPSTR lpFriendlyName) { STRRET str; AGAIN: //得到显示名称 if(NOERROR!=lpsf->GetDisplayNameOf(lpi,dwFlags,&str)) return FALSE;
//根据返回值进行转换 switch(str.uType) { case STRRET_WSTR: //如为Unicode字符串,则转成Ansi字符集的字符串case STRRET_WSTR: if(str.pOleStr != NULL) WideCharToMultiByte(CP_ACP,0,str.pOleStr,-1,lpFriendlyName,MAX_PATH,NULL,NULL); else if(dwFlags == SHGDN_NORMAL) { dwFlags = SHGDN_FORPARSING; goto AGAIN; } break; case STRRET_OFFSET: //如为偏移量,则去除偏移量 lstrcpy(lpFriendlyName,(LPSTR)lpi+str.uOffset); break; case STRRET_CSTR: // 如为Ansi字符串,则直接拷贝 lstrcpy(lpFriendlyName,(LPSTR)str.cStr); break; default: //非法情况 return FALSE;
}
return TRUE; }
// 获取路径 void GetLongFilePath(IShellFolder* pShellFolder, LPITEMIDLIST lpidl, LPFILEPATHLIST lpfpl) { BOOL bRelease = FALSE; // 如果IShellFolder接口为空,则使用桌面的IShellFolder接口 if(pShellFolder == NULL) { if(SHGetDesktopFolder(&pShellFolder) != NOERROR) return; bRelease = TRUE; } // 取得显示, PIDL及相对名称 GetName(pShellFolder, lpidl, SHGDN_NORMAL, lpfpl->cNormal); GetName(pShellFolder, lpidl, SHGDN_FORPARSING, lpfpl->cForParsing); GetName(pShellFolder, lpidl, SHGDN_INFOLDER, lpfpl->cInFolder);
// 取得的COM接口一定要释放 if(bRelease) pShellFolder->Release(); }
///////////////////////////////////////////////////////////////////////////// // CShellFolderView
IMPLEMENT_DYNCREATE(CShellFolderView, CTreeView)
CShellFolderView::CShellFolderView() { // 取得内存分配的COM接口, 这里一定要成功,否则接下来的程序中无法分配空间 VERIFY(SHGetMalloc(&m_pMalloc)==NOERROR); }
CShellFolderView::~CShellFolderView() { // 释放IMalloc接口 m_pMalloc->Release(); }
BEGIN_MESSAGE_MAP(CShellFolderView, CTreeView) //{{AFX_MSG_MAP(CShellFolderView) ON_WM_CREATE() ON_NOTIFY_REFLECT(TVN_ITEMEXPANDING, OnItemexpanding) ON_WM_DESTROY() //}}AFX_MSG_MAP ON_MESSAGE(WM_U_FOLDERCHANGED, OnFolderChanged) END_MESSAGE_MAP()
///////////////////////////////////////////////////////////////////////////// // CShellFolderView drawing
//void CShellFolderView::OnDraw(CDC* pDC) //{ // CDocument* pDoc = GetDocument(); // TODO: add draw code here //}
///////////////////////////////////////////////////////////////////////////// // CShellFolderView diagnostics
#ifdef _DEBUG void CShellFolderView::AssertValid() const { CTreeView::AssertValid(); }
void CShellFolderView::Dump(CDumpContext& dc) const { CTreeView::Dump(dc); } #endif //_DEBUG
///////////////////////////////////////////////////////////////////////////// // CShellFolderView message handlers
int CShellFolderView::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CTreeView::OnCreate(lpCreateStruct) == -1) return -1; // TODO: Add your specialized creation code here // 设置树控件的风格:带有展开按钮,结点间有连线 GetTreeCtrl().ModifyStyle(0, TVS_HASLINES|TVS_HASBUTTONS|TVS_LINESATROOT); RetrieveSysImageList(); // 取得系统图标列表 // 显示桌面虚拟文件夹 ShowDesktop();
return 0; }
// 当某个结点将要被展开前 void CShellFolderView::OnItemexpanding(NMHDR* pNMHDR, LRESULT* pResult) { NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR; // TODO: Add your control notification handler code here // 如果是展开操作 if(pNMTreeView->action == TVE_EXPAND) { // 如果被展开的结点还没有子结点则为该结点附加它的子结点 // 我们不能一开始就将所有的结点添加上去,这将是一个漫长的过程,且浪费 // 大量的系统资源,只有那些被展开的结点才需要显示其子结点 if(GetTreeCtrl().GetChildItem(pNMTreeView->itemNew.hItem) == NULL) { AttachFolders(pNMTreeView->itemNew.hItem); } } *pResult = 0; *pResult = 0; }
void CShellFolderView::OnDestroy() { // 释放所有的结点 FreeNode(NULL);
CTreeView::OnDestroy(); }
// 当被监视的文件夹发生变化时,该消息被监视线程发送过来 LRESULT CShellFolderView::OnFolderChanged(WPARAM wParam, LPARAM) { // 变化对应的结点句柄 HTREEITEM hNode = (HTREEITEM)wParam; CTreeCtrl& tree = GetTreeCtrl(); // 视图上的树控件 // 保存该结点的状态(是否是已经展开的) BOOL bExpanded = tree.GetItemState(hNode, TVIS_EXPANDED) == TVIS_EXPANDED; // 它的子结点 HTREEITEM hChild = tree.GetChildItem(hNode); // 删除该结点下的所有子结点 while(hChild != NULL) { HTREEITEM hCur = hChild; hChild = tree.GetNextSiblingItem(hChild); // 下一个子结点 FreeNode(hCur); tree.DeleteItem(hCur); }
// 重新生成子结点. AttachFolders(hNode); // 恢复展开状态(如果以前是展开的) if(bExpanded) tree.Expand(hNode, TVE_EXPAND);
return 0; }
////////////////////////////////////////////////////////////////// // protected member function
// 获取系统图标列表 void CShellFolderView::RetrieveSysImageList() { PATHINFO DesktopInfo; SHFILEINFO FileInfo; CString strDesktop; GetSpecialFolder(CSIDL_DESKTOPDIRECTORY, &DesktopInfo); // SHGetFileInfo函数的返回值就是系统图标列表的句柄(可参考MSDN) HIMAGELIST hSysImageList = (HIMAGELIST)::SHGetFileInfo( (LPCTSTR)DesktopInfo.pidl, 0, &FileInfo, sizeof(FileInfo), SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_SMALLICON); // 设置重叠图标 // 设置1号重叠图标对应图标列表中索引为0的图标,这是一个手托,象征被共享的文件夹 // 设置2号重叠图标对应图标列表中索引为1的图标,这是一个箭头,象征快捷方式 // 设置3号重叠图标对应图标列表中索引为2的图标,这个图标我研究了好长时间也没看出来是个什么东东 ImageList_SetOverlayImage(hSysImageList, 0,1); ImageList_SetOverlayImage(hSysImageList, 1,2); ImageList_SetOverlayImage(hSysImageList, 2,3); // 树控件就使用这个图标列表 GetTreeCtrl().SetImageList(CImageList::FromHandle(hSysImageList),TVSIL_NORMAL);
}
// 获取特殊的文件夹信息 void CShellFolderView::GetSpecialFolder(int nFolder, LPPATHINFO lppi) { LPITEMIDLIST lpidl = NULL; // 取得该文件夹的PIDL if(SUCCEEDED(SHGetSpecialFolderLocation(m_hWnd, nFolder, &lpidl))) { lppi->pidl = lpidl;
// Get the actual path of the directory from the PItemIDList SHGetPathFromIDList(lpidl, lppi->cPath); // Do it
// Get the Normal Image index SHFILEINFO shfi; SHGetFileInfo((LPCTSTR)lpidl, SFGAO_SHARE, &shfi, sizeof(shfi), SHGFI_PIDL | SHGFI_SYSICONINDEX); lppi->uIconIndex = shfi.iIcon;
// Get the selected image index SHGetFileInfo((LPCTSTR)lpidl, SFGAO_SHARE, &shfi, sizeof(shfi), SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_OPENICON); lppi->uSelectedIndex = shfi.iIcon; } }
void CShellFolderView::GetNetHood(LPPATHINFO lpPI) { FILEPATHLIST fpl; GetSpecialFolder(CSIDL_NETWORK, lpPI); GetLongFilePath(NULL, lpPI->pidl, &fpl); strcpy(lpPI->cText, fpl.cNormal); }
// CreateFolderNode creates a Folder_Node and inserts it under // the "parent" node (if any), using the last of the path // string as the Name, and setting the new node's text // property to match. HTREEITEM CShellFolderView::CreateFolderNode( LPITEMIDLIST lpidl, // 文件夹的PIDL HTREEITEM hParent, // 父结点 BOOL bVirtual) // 是否包含虚拟文件夹 { CTreeCtrl& tree = GetTreeCtrl(); LPFOLDERNODE lpfnParent = NULL; FILEPATHLIST fpl;
// 父结点的信息 if(hParent != NULL) { lpfnParent = (LPFOLDERNODE)tree.GetItemData(hParent); GetLongFilePath(lpfnParent->pShellFolder, lpidl, &fpl); } else { GetLongFilePath(NULL, lpidl, &fpl); }
CString strFileName(fpl.cInFolder); // 相对名字 CString strPath(fpl.cForParsing); // 法定名字
// 以"::"开头的,是一些特殊的文件夹,普通的目录或文件的名称中是不能包含":"字符的 // 打印出来看看都是些什么东东 if(strncmp(fpl.cForParsing, "::", 2) ==0) { TRACE3("<%s> = \"%s\"; // Normal = %s\n", fpl.cInFolder, fpl.cForParsing, fpl.cNormal); }
// 取得属性 ULONG Attributes = SFGAO_SHARE | SFGAO_FILESYSTEM | SFGAO_LINK | SFGAO_HASSUBFOLDER; if(lpfnParent == NULL || lpfnParent->pShellFolder->GetAttributesOf( 1, (LPCITEMIDLIST*)&lpidl, &Attributes) != NOERROR) { Attributes = 0; //lpfnParent = NULL; }
// 是否是一个虚拟文件夹 BOOL bVirFolder = ((Attributes & SFGAO_FILESYSTEM) == 0);
if(lpfnParent != NULL) { switch(lpfnParent->fType) { case ftNetworkNeighborhood: case ftNetNode: bVirFolder = TRUE; } } if(!bVirtual && bVirFolder) return NULL;
// 生成该文件夹的绝对PIDL,方法就是将相对PIDL附加到父PIDL后 WORD wParentPIDLLen = 0; LPITEMIDLIST lpidlParent = NULL; if(lpfnParent != NULL) { wParentPIDLLen = lpfnParent->widlen; lpidlParent = lpfnParent->pidl; }
WORD pidlen = lpidl->mkid.cb; // 使用IMalloc接口分配新PIDL需要的空间.因为从系统API返回的PIDL // 都是通过IMalloc接口分配的空间,所以为了统一,这里也使用IMalloc来分配空间 LPITEMIDLIST lpidlNew = (LPITEMIDLIST)m_pMalloc->Alloc(pidlen + wParentPIDLLen + 2);
if(wParentPIDLLen != 0) { memcpy(lpidlNew, lpidlParent, wParentPIDLLen); memcpy((char*)lpidlNew+wParentPIDLLen, lpidl,pidlen); } else { memcpy(lpidlNew, lpidl, pidlen); }
*(WORD*)((char*)lpidlNew + wParentPIDLLen + pidlen) = 0;
LPITEMIDLIST lpidlTemp = /*bVirFolder ? lpidl : */lpidlNew;
// 获取图标索引 SHFILEINFO shfi; UINT uIcon, uSelectedIcon; ::SHGetFileInfo((LPCTSTR)lpidlTemp, SFGAO_SHARE, &shfi, sizeof(shfi), SHGFI_PIDL | SHGFI_SYSICONINDEX); uIcon = uSelectedIcon = shfi.iIcon; //if((Attributes & SFGAO_FOLDER) == SFGAO_FOLDER) { ::SHGetFileInfo((LPCTSTR)lpidlTemp, SFGAO_SHARE, &shfi, sizeof(shfi), SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_OPENICON); uSelectedIcon = shfi.iIcon; }
// 一个新的结点 LPFOLDERNODE lpNewNode = new FOLDERNODE; lpNewNode->widlen = wParentPIDLLen + pidlen; lpNewNode->pidl = lpidlNew; strcpy(lpNewNode->cPath, (LPCTSTR)strPath); lpNewNode->bHasParent = (hParent != NULL); //lpNewNode->pChangeThread = NULL; //lpNewNode->VolSer = 1; // 结点的类型(只要和几个固定的名字比较一下啦) if(strPath.Compare(c_cMyComputer) == 0) lpNewNode->fType = ftMyComputer; else if(strPath.Compare(c_cNetworkNeighborhood) == 0) lpNewNode->fType = ftNetworkNeighborhood; else if(strPath.Compare(c_cRecycleBin) == 0) lpNewNode->fType = ftRecycleBin; else lpNewNode->fType = ftFolder; lpNewNode->dwAttributes = Attributes; lpNewNode->pMonitorThread = NULL;
// 获取该文件夹对应的IShellFolder接口 // If this Node has no parent, it mut be the Desktop. if(lpfnParent != NULL) { lpfnParent->pShellFolder->BindToObject(lpidl, NULL, IID_IShellFolder, (void**)&lpNewNode->pShellFolder); //if(lpfnParent->fType == ftNetworkNeighborhood) //{ // lpNewNode->fType = ftNetNode; // sprintf(lpNewNode->cPath, "\\\\%s", (LPCTSTR)strFileName); //}
} else { // 桌面的IShellFolder ::SHGetDesktopFolder(&lpNewNode->pShellFolder); }
// 插入树结点 TVINSERTSTRUCT tvis; tvis.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_STATE | TVIF_PARAM; // 如果该文件夹包含子文件夹,则对应的树结点也应该有子结点 if(Attributes & SFGAO_HASSUBFOLDER) { tvis.item.mask |= TVIF_CHILDREN; // 使用 I_CHILDRENCALLBACK 值告诉控件,该结点有子结点,但具体的结点还没给出 // 当该结点被展开时就会通知父窗口.这时你应该为该结点添加子结点 tvis.item.cChildren = I_CHILDRENCALLBACK; }
// 设置覆盖图标 if(Attributes & SFGAO_SHARE) // 共享的 tvis.item.state = INDEXTOOVERLAYMASK(1); else if(Attributes & SFGAO_LINK) // 快捷方式 tvis.item.state = INDEXTOOVERLAYMASK(2); else // 其它的 tvis.item.state = INDEXTOOVERLAYMASK(0);
tvis.item.stateMask = TVIS_OVERLAYMASK; // 指明状太标志包含覆盖图标 tvis.item.iImage = uIcon; // 正常图标 tvis.item.iSelectedImage = uSelectedIcon;// 选中时的图标 //CString strTemp = strFileName + "(" + strPath + ")"; // 看看它的完整路径 tvis.item.pszText = (char*)fpl.cNormal; // 标题 tvis.hParent = hParent; // 父结点 tvis.hInsertAfter = TVI_LAST; // 插入到其它兄弟结点的后面 tvis.item.lParam = (DWORD)lpNewNode; // 自定义的结点数据
HTREEITEM hIns = tree.InsertItem(&tvis); // 插入该结点
if(hIns == NULL) // 插入失败??不大可能的事 FreeNodeData(lpNewNode);
return hIns; // 返回新结点句柄 }
// GetPath returns the path name for true folders, and a null string // for virtual folders. CString CShellFolderView::GetPath(HTREEITEM hNode) { CString strPath("");
if(hNode == NULL) return strPath;
// 对于虚拟文件夹返回空串 switch(((LPFOLDERNODE)(GetTreeCtrl().GetItemData(hNode)))->fType) { case ftNetworkNeighborhood: case ftNetNode: case ftRecycleBin: case ftMyComputer: return strPath; }
// 对于真正的文件夹返回绝对路径 strPath = ((LPFOLDERNODE)(GetTreeCtrl().GetItemData(hNode)))->cPath;
if(strPath.GetLength() > 0) // If path is null, it's a virtual folder { if(strPath.GetLength() < 3) // If the result is less than 3 bytes, strPath += "\\"; // It's the root -- add a "\". }
return strPath; }
// 对目录进行排序的回调函数 // The lParam1 and lParam2 parameters correspond to the lParam member // of the TVITEM structure for the two items being compared. // The lParamSort parameter corresponds to the lParam member of this structure.
static int CALLBACK ShortItemProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) { CTreeCtrl* pCtrl = (CTreeCtrl*) lParamSort;
LPFOLDERNODE lpfn1 = (LPFOLDERNODE)lParam1; LPFOLDERNODE lpfn2 = (LPFOLDERNODE)lParam2; if(lpfn1 == NULL || lpfn2 == NULL) return 0;
// 虚拟文件夹排在前面,并且不改变原顺序 if((lpfn1->dwAttributes & SFGAO_FILESYSTEM) == 0 && (lpfn2->dwAttributes & SFGAO_FILESYSTEM) == 0) return 0; else if((lpfn1->dwAttributes & SFGAO_FILESYSTEM) == 0) return -1; else if((lpfn2->dwAttributes & SFGAO_FILESYSTEM)==0) return 1; // 真正的文件夹排在虚拟文件夹之后,且按字母顺序(不区分大小写) else if((lpfn1->dwAttributes & SFGAO_FOLDER) && (lpfn1->dwAttributes & SFGAO_FOLDER)) return stricmp(lpfn1->cPath, lpfn2->cPath); else if(lpfn1->dwAttributes & SFGAO_FOLDER) return -1; else if(lpfn2->dwAttributes & SFGAO_FOLDER) return 1; else // 其它的排在最后面,按字母序(不区分大小写) return stricmp(lpfn1->cPath, lpfn2->cPath); }
// AttachFolders unconditionally attaches child folders to the // node passed in Node. If there are any, it also starts a thread // to watch for changes in the tree structure. BOOL CShellFolderView::AttachFolders(HTREEITEM hNode) { CWaitCursor cur; // 显示等待光标 BOOL bRet = FALSE;
BOOL bChildren = TRUE; CTreeCtrl& tree = GetTreeCtrl(); tree.SetRedraw(FALSE); // 禁止控件更新窗口,以免插入时闪烁 //tree.EnableWindow(FALSE);
CString strPath = GetPath(hNode); // The full path for the node. LPFOLDERNODE lpfn = (LPFOLDERNODE)tree.GetItemData(hNode);
// If the node being expanded is a root, get the volume serial number // and lop off the backslash. if(strPath.GetLength() <= 3) { lpfn->Vol_Ser = GetVol_Ser(strPath); }
// Enumerate the folder's contents. IEnumIDList* pEnum = NULL; if(lpfn->pShellFolder->EnumObjects(m_hWnd, SHCONTF_FOLDERS | SHCONTF_INCLUDEHIDDEN, &pEnum) == NOERROR) { bChildren = FALSE; pEnum->Reset(); // Reset the enumerate interface ULONG u = 1; // Enumerate context. LPITEMIDLIST lpidlChild = NULL; while(pEnum->Next(1, &lpidlChild, &u) == NOERROR) { // 为每个PIDL创建对应的树结点(包含其中的虚拟文件夹) HTREEITEM hChild = CreateFolderNode(lpidlChild, hNode, TRUE); // 释放PIDL m_pMalloc->Free(lpidlChild); if(hChild != NULL) { // 调整结点的类型 bChildren = TRUE; LPFOLDERNODE lpfnChild = (LPFOLDERNODE)tree.GetItemData(hChild); switch(lpfnChild->fType) { case ftMyComputer: case ftNetNode: lpfnChild->Vol_Ser = GetVol_Ser(lpfnChild->cPath); break; default: lpfnChild->Vol_Ser = lpfn->Vol_Ser; bRet = TRUE; break; } } } // 释放枚举接口 pEnum->Release(); } // 调整父结点的属性 TVITEM tvi; tvi.mask = TVIF_CHILDREN; tvi.cChildren = bChildren?1:0; tvi.hItem = hNode; tree.SetItem(&tvi);
// Alphabetize the nodes, unless the parent is MyComputer or // Network Neighborhood. switch(lpfn->fType) { case ftMyComputer: case ftNetworkNeighborhood: break; default:{ // 排序所有子结点(根据给定的回调函数) TVSORTCB tvs; tvs.hParent = hNode; tvs.lpfnCompare = ShortItemProc; tvs.lParam = (LPARAM)&tree; tree.SortChildrenCB(&tvs); }break; }
if(bRet) { // 如果还没有的话,则启动一个线程,开始监视该文件夹的变化 if(lpfn->pMonitorThread == NULL) { lpfn->pMonitorThread = new CFolderChangeMonitor(strPath, m_hWnd, (WPARAM)hNode); } }
// 可以更新窗口了 tree.SetRedraw(TRUE); // tree.EnableWindow(TRUE); return bRet; }
// This routine is used only for diskettes. It returns the volume serial // number of a disk in the drive--if there is no disk there, it returns // -1. It's used whenever the user clicks on a node, to determine whether // he has swapped diskettes, necessitating a discard of all nodes for the // affected drive. // For volumes other than diskettes, it returns a dummy value of 1, // as -1 is used to signify a diskette drive that isn't loaded. LONG CShellFolderView::GetVol_Ser(LPCTSTR strDriver) { LONG result = -1;
// If there is no path it's My Computer -- exit now if(strlen(strDriver) == 0) return result;
// If the driver isn't removeable, we dont have to do this CString strPath = strDriver[0] + ":\\"; strPath.MakeLower();
if(::GetDriveType(strPath) != DRIVE_REMOVABLE) { result = 1; return result; } // I f there's no disk in the drive, bail out with a result = -1. DWORD dwVSN, dwMCL, dwFSF; if(GetVolumeInformation(strPath, NULL, 0, &dwVSN, &dwMCL, &dwFSF, NULL, 0)) { result = (LONG) dwVSN; } else result = -1; return result; }
// ShowDesktop is performed when Create posts a WM_SHOWDESKTOP // message, so we see the correct value of fFolderOptions. void CShellFolderView::ShowDesktop() { CTreeCtrl& tree = GetTreeCtrl();
PATHINFO PathInfo; GetSpecialFolder(CSIDL_DESKTOP, &PathInfo); //strDesktopPath = PathInfo.cPath;
// 创建桌面这个根结点 HTREEITEM hDesktop = CreateFolderNode(PathInfo.pidl, NULL, TRUE); LPFOLDERNODE lpfn = (LPFOLDERNODE)tree.GetItemData(hDesktop); FILEPATHLIST fpl; GetLongFilePath(lpfn->pShellFolder, lpfn->pidl, &fpl); tree.SetItemText(hDesktop, fpl.cNormal); strcpy(lpfn->cPath , PathInfo.cPath); lpfn->fType = ftDesktop;
// 展开桌面 AttachFolders(hDesktop);
tree.Expand(hDesktop, TVE_EXPAND); }
// 展开指定的结点,如果需要的话,附加创建其下子结点 int CShellFolderView::Expand(HTREEITEM hNode, UINT nCode) { if(GetTreeCtrl().GetChildItem(hNode) == NULL) { AttachFolders(hNode); } return GetTreeCtrl().Expand(hNode, nCode); }
// 将路径转化为PIDL,如果失败,返回NULL LPITEMIDLIST PathToIDList(LPCTSTR lpszPath) { OLECHAR szOleStr[MAX_PATH]; IShellFolder* pDesktop = NULL; LPITEMIDLIST lpidl = NULL; // 使用桌面的IShellFolder来转换,这将生成一个绝对的PIDL if(SHGetDesktopFolder(&pDesktop) != NOERROR) return NULL;
// 将路径转换成宽字符格式 MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED, lpszPath, -1, szOleStr, sizeof(szOleStr)); // 转换路径 pDesktop->ParseDisplayName(NULL, NULL, szOleStr, NULL, &lpidl, NULL);
// 释放接口 pDesktop->Release();
return lpidl; }
// PIDL中的下一个结构 inline LPITEMIDLIST PIDL_Next(LPITEMIDLIST p) { return (LPITEMIDLIST)((DWORD)p + p->mkid.cb); }
// 求指定的PIDL的长度,不包括结尾的两个字节的0 inline int PIDL_Len(LPITEMIDLIST p) { WORD wLen = 0; // 累加所有的单元结构尺寸 while(p && p->mkid.cb != 0) { wLen += p->mkid.cb; p = PIDL_Next(p); } return wLen; }
// 设置当前目录 BOOL CShellFolderView::SetCurPath(LPCTSTR lpszPath) { // 解释路径为PIDL LPITEMIDLIST lpidl = PathToIDList(lpszPath); if(lpidl == NULL) return FALSE;
CTreeCtrl& tree = GetTreeCtrl(); // 从桌面结点开始搜索 HTREEITEM hDesktop = tree.GetRootItem(); if(hDesktop == NULL){ m_pMalloc->Free(lpidl); return FALSE; }
HTREEITEM hCur = hDesktop; WORD wPart = 0; LPITEMIDLIST lpidlPart = lpidl; // 对于每一个存在的结点,搜索其下的子结点 while(hCur != NULL && lpidlPart->mkid.cb != 0) { // 展开该结点 Expand(hCur,TVE_EXPAND); wPart += lpidlPart->mkid.cb; HTREEITEM hChild = tree.GetChildItem(hCur); // 如果子结点存在,则: while (hChild != NULL) { // 取得结点数据 LPFOLDERNODE lpfn = (LPFOLDERNODE)tree.GetItemData(hChild); // 如果PIDL的长度匹配的话,则比较这两个PIDL // PIDL的匹配长度按搜索深度层次而增加,即,第一次搜索匹配一个结构的长度,第二次 // 则匹配两个结构的长... if(lpfn && PIDL_Len(lpfn->pidl) == wPart) { if(memcmp(lpidl, lpfn->pidl, wPart) == 0) { // 匹配成功 break; } } hChild = tree.GetNextSiblingItem(hChild); // 下一个结点 } hCur = hChild; lpidlPart = PIDL_Next(lpidlPart); // PIDL的下一个结构 } // 如果子结点存在,则匹配成功. 选中该结点 if(hCur != NULL) { tree.SelectItem(hCur); tree.EnsureVisible(hCur); }
m_pMalloc->Free(lpidl);
return hCur != NULL; }
// 释放结点数据 void CShellFolderView::FreeNodeData(LPFOLDERNODE lpfn) { if(lpfn) { m_pMalloc->Free(lpfn->pidl); // 释放PIDL if(lpfn->pShellFolder) // 释放IShellFolder接口 lpfn->pShellFolder->Release(); if(lpfn->pMonitorThread != NULL) // 删除监视线程 delete lpfn->pMonitorThread; delete lpfn; // 删除该结点 } }
void CShellFolderView::FreeNode(HTREEITEM hNode) { CTreeCtrl& tree = GetTreeCtrl(); if(hNode == NULL) { // 空的参数指明,将删除所有结点的数据 hNode = tree.GetRootItem(); } else { // 删除本结点数据 LPFOLDERNODE lpfn = (LPFOLDERNODE)tree.GetItemData(hNode); FreeNodeData(lpfn); // 结点指向子结点 hNode = tree.GetChildItem(hNode); } // 删除所有子结点的数据 while(hNode != NULL) { FreeNode(hNode); hNode = tree.GetNextSiblingItem(hNode); } }
// 返回当前选中的路径(没有则返回空串) CString CShellFolderView::GetSelectedDir() { HTREEITEM hSel = GetTreeCtrl().GetSelectedItem(); if(hSel == NULL) return ""; return ((LPFOLDERNODE)GetTreeCtrl().GetItemData(hSel))->cPath; }
|