一、几个概念(摘)
1.外壳名字空间:
在WINDOWS中又叫外壳名字空间(Shell Name Space).外壳名字空间是Windows下的标准文件系统,它大大扩展了Dos文件系统,形成了以“桌面”(Desktop)为根的单一的文件系统树,原有的C盘、D盘等目录树变成“我的电脑”这一外壳名字空间子树的下一级子树,而像“控制面板”、“回收站”、“网上邻居”等应用程序及“打印机”等设备也被虚拟成了外壳名字空间中的节点。另外,与DOS中物理存储只能和文件系统项一一对应这一点不同的是,一个实际目录在外壳名字空间中可以表现为不同的项。例如“我的文档”与“C:/MyDocuments”其实都指向“C:/My Documents”目录,但它们在外壳名字空间中是不同的项。
2. 外壳名字空间下的路径: 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位的零。
二、几个API
1.HRESULT SHGetSpecialFolderLocation( HWND hwndOwner , int nFolder , //CSIDL LPITEMIDLIST *ppidl //返回CSIDL所对应的绝对PIDL(输出) );
2.DWORD_PTR SHGetFileInfo( LPCTSTR pszPath , //uFlags含SHGFI_PIDL时为绝对PIDL(输入) DWORD dwFileAttributes , //绝对PIDL所对应文件的 file attribute flags (输出) SHFILEINFO *psfi , //返回file information UINT cbFileInfo , //SHFILEINFO结构大 UINT uFlags //指定你所要获取的信息 ); //该函数返回系统HIMAGELIST
说明:
typedef struct _SHFILEINFO { HICON hIcon ; int iIcon ; //图标索引 DWORD dwAttributes ; //文件属性 TCHAR szDisplayName [MAX_PATH]; // 显示名称 TCHAR szTypeName [80]; //文件类型:一个值对应一个二进制位 } SHFILEINFO;
uFlags : SHGFI_DISPLAYNAME | SHGFI_TYPENAME :psfi返回中包含显示名称和文件类型 (其他类推)
3.HRESULT SHGetDesktopFolder( IShellFolder** ppshf //f返回桌面IShellFolder接口 );
IShellFolder的几个方法:
(1).BingToObject 获取子文件夹的IShellFolder接口
(2).EnumObject获取IEnumIDList ,IEnumIDList ->Next方法获取子文件的相对PIDL(详见MSDN)
(3)GetAttributesOf获取文件属性
注意事项:参数中是绝对PIDL还是相对PIDL。SHGet绝对,IShellFolder相对。PIDL用IMalloc接口开辟空间
三、仿系统目录树
注:本程序在VS2005编译通过(存在小Bug:树节点的加号要展开后才显示,待修改)。
下面是原码
//
BrowseSysTreeDlg.h : 头文件
//
#pragma
once
#include
"
afxcmn.h
"
//
CBrowseSysTreeDlg 对话框
class
CBrowseSysTreeDlg :
public
CDialog
...
{ // 构造 public : CBrowseSysTreeDlg(CWnd * pParent = NULL); // 标准构造函数 // 对话框数据 enum ... { IDD = IDD_BROWSESYSTREE_DIALOG } ; protected : virtual void DoDataExchange(CDataExchange * pDX); // DDX/DDV 支持 // 添加 protected : HTREEITEM CreateFolderNode(LPITEMIDLIST lpPidl,HTREEITEM hParent); BOOL AttachFolders(HTREEITEM hNode); void FreeNode(HTREEITEM hNode); // 实现 protected : HICON m_hIcon; CTreeCtrl m_ctrlTree; CImageList m_imageList; IMalloc * m_pMalloc; // 生成的消息映射函数 virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() public : afx_msg void OnNMDblclkSysTree(NMHDR * pNMHDR, LRESULT * pResult); public : afx_msg void OnTvnItemexpandingSysTree(NMHDR * pNMHDR, LRESULT * pResult); public : afx_msg void OnDestroy(); }
;
//
BrowseSysTreeDlg.cpp : 实现文件
//
#include
"
stdafx.h
"
#include
"
BrowseSysTree.h
"
#include
"
BrowseSysTreeDlg.h
"
#include
"
shlobj.h
"
#ifdef _DEBUG
#define
new DEBUG_NEW
#endif
//
用于应用程序“关于”菜单项的 CAboutDlg 对话框
class
CAboutDlg :
public
CDialog
...
{ public : CAboutDlg(); // 对话框数据 enum ... { IDD = IDD_ABOUTBOX } ; protected : virtual void DoDataExchange(CDataExchange * pDX); // DDX/DDV 支持 // 实现 protected : DECLARE_MESSAGE_MAP() }
; CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
...
{ }
void
CAboutDlg::DoDataExchange(CDataExchange
*
pDX)
...
{ CDialog::DoDataExchange(pDX); }
BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) END_MESSAGE_MAP()
//
CBrowseSysTreeDlg 对话框
typedef
struct
_NodeInfo
//
节点信息
...
{ TCHAR szName[MAX_PATH]; // 显示的名称 UINT uIcon; // 图标 ULONG dwAttributes; // 属性 TCHAR szPath[MAX_PATH]; // 路径 LPITEMIDLIST lpPidl; // PIDL WORD wPidlLen; // PLID的长度 IShellFolder * pShellFolder; // 指向该节点的IShellFolder接口 BOOL bHasParent; // 是否有父节点 }
NODEINFO,
*
LPNODEINFO; HTREEITEM CBrowseSysTreeDlg::CreateFolderNode(LPITEMIDLIST lpPidl, HTREEITEM hParent)
...
{ LPNODEINFO lpParentNodeInfo = NULL; IShellFolder * pShellFolder = NULL; TCHAR szName[MAX_PATH]; BOOL bRelease = FALSE; if (NULL != hParent) // 获取父IShellFolder接口 ... { lpParentNodeInfo = (LPNODEINFO) m_ctrlTree.GetItemData(hParent); pShellFolder = lpParentNodeInfo -> pShellFolder; } else // 没有父节点,则取桌面IShellFolder ... { ::SHGetDesktopFolder( & pShellFolder); bRelease = TRUE; if (NULL == pShellFolder) return NULL; } // 获取属性 ULONG Attributes = SFGAO_SHARE | SFGAO_FILESYSTEM | SFGAO_LINK | SFGAO_HASSUBFOLDER; if (lpParentNodeInfo == NULL || pShellFolder -> GetAttributesOf( 1 ,(LPCITEMIDLIST * ) & lpPidl, & Attributes) != NOERROR) ... { Attributes = 0 ; } // 长度 WORD wParentPidlLen = 0 ; LPITEMIDLIST lpPidlParent; if (NULL != lpParentNodeInfo) ... { wParentPidlLen = lpParentNodeInfo -> wPidlLen; lpPidlParent = lpParentNodeInfo -> lpPidl; } // 使用IMalloc接口分配新PIDL需要的空间. LPITEMIDLIST lpPidlNew = (LPITEMIDLIST)m_pMalloc -> Alloc(lpPidl -> mkid.cb + wParentPidlLen + 2 ); if (wParentPidlLen != 0 ) ... { memcpy(lpPidlNew, lpPidlParent, wParentPidlLen); memcpy(( char * )lpPidlNew + wParentPidlLen, lpPidl,lpPidl -> mkid.cb); } else ... { memcpy(lpPidlNew, lpPidl, lpPidl -> mkid.cb); } * (WORD * )(( char * )lpPidlNew + wParentPidlLen + lpPidl -> mkid.cb) = 0 ; LPITEMIDLIST lpPidlTemp = lpPidlNew; // 获取显示图标 SHFILEINFO shif; UINT uIcon; UINT uSelectedIcon; ::SHGetFileInfo((LPCWSTR)lpPidlTemp, 0 , & shif, sizeof (shif), SHGFI_PIDL | SHGFI_SYSICONINDEX); uIcon = shif.iIcon; ::SHGetFileInfo((LPCWSTR)lpPidlTemp, 0 , & shif, sizeof (shif), SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_OPENICON | SHGFI_DISPLAYNAME); uSelectedIcon = shif.iIcon; // 获取路径 TCHAR szPath[MAX_PATH]; ::SHGetPathFromIDList(lpPidlTemp, szPath); lstrcpy(szName, shif.szDisplayName); // 取得显示名称 // 获取节点IShellFolder IShellFolder * pShellFolderNew = NULL; pShellFolder -> BindToObject(lpPidl, NULL, IID_IShellFolder,( void ** ) & pShellFolderNew); // 建立新节点 LPNODEINFO lpNodeInfoNew = new NODEINFO; lpNodeInfoNew -> bHasParent = (hParent != NULL); lpNodeInfoNew -> dwAttributes = Attributes; lpNodeInfoNew -> lpPidl = lpPidlNew; if (NULL == hParent) lpNodeInfoNew -> pShellFolder = pShellFolder; else lpNodeInfoNew-> pShellFolder = pShellFolderNew; memcpy(lpNodeInfoNew -> szName, szName, sizeof (szName)); memcpy(lpNodeInfoNew -> szPath, szPath, sizeof (szPath)); lpNodeInfoNew -> uIcon = uIcon; lpNodeInfoNew -> wPidlLen = wParentPidlLen + lpPidl -> mkid.cb; // 建立树视图插入结构 TVINSERTSTRUCT tvis; tvis.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_STATE | TVIF_PARAM; if (Attributes & SFGAO_HASSUBFOLDER) ... { tvis.item.mask |= TVIF_CHILDREN; tvis.item.cChildren = I_CHILDRENCALLBACK; // 使用 I_CHILDRENCALLBACK 值告诉控件,该结点有子结点,但具体的结点还没给出 // 当该结点被展开时就会通知父窗口.这时你应该为该结点添加子结点 } else tvis.item.cChildren = 0 ; tvis.item.stateMask = TVIS_OVERLAYMASK; // 指明状态标志包含覆盖图标 tvis.hInsertAfter = TVI_LAST; tvis.hParent = hParent; tvis.item.iImage = uIcon; tvis.item.iSelectedImage = uSelectedIcon; tvis.item.lParam = (DWORD)lpNodeInfoNew; tvis.item.cchTextMax = MAX_PATH; tvis.item.pszText = szName; tvis.item.stateMask = TVIS_OVERLAYMASK; // 设置覆盖图标 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 ); HTREEITEM hIns = m_ctrlTree.InsertItem( & tvis); // 插入该结点 if (bRelease) // 释放桌面IShellFolder pShellFolder -> Release(); return hIns; }
BOOL CBrowseSysTreeDlg::AttachFolders(HTREEITEM hNode)
...
{ CWaitCursor cur; // 显示等待光标 BOOL bRet = FALSE; BOOL bChildren = FALSE; m_ctrlTree.SetRedraw(FALSE); // 禁止控件更新窗口,以免插入时闪烁 LPNODEINFO lpfn = (LPNODEINFO)m_ctrlTree.GetItemData(hNode); IEnumIDList * pEnum = NULL; if (lpfn -> pShellFolder -> EnumObjects(m_hWnd, SHCONTF_FOLDERS | SHCONTF_INCLUDEHIDDEN, & pEnum) == NOERROR) ... { bChildren = FALSE; pEnum -> Reset(); ULONG u = 1 ; LPITEMIDLIST lpidlChild = NULL; while (pEnum -> Next( 1 , & lpidlChild, & u) == NOERROR) ... { // 为每个PIDL创建对应的树结点(包含其中的虚拟文件夹) HTREEITEM hChild = CreateFolderNode(lpidlChild, hNode); if (hChild != NULL) bChildren = TRUE; } // 释放枚举接口 pEnum -> Release(); // 调整父结点的属性 if (TRUE == bChildren) ... { TVITEM tvi; tvi.mask = TVIF_CHILDREN; tvi.hItem = hNode; m_ctrlTree.SetItem( & tvi); } } // 可以更新窗口了 m_ctrlTree.SetRedraw(TRUE); return TRUE; }
void
CBrowseSysTreeDlg::FreeNode(HTREEITEM hNode)
...
{ if (NULL == hNode) hNode = m_ctrlTree.GetRootItem(); else ... { LPNODEINFO lpNodeInfo = (LPNODEINFO)m_ctrlTree.GetItemData(hNode); if (NULL != lpNodeInfo -> pShellFolder) lpNodeInfo -> pShellFolder -> Release(); m_pMalloc -> Free(lpNodeInfo -> lpPidl); delete lpNodeInfo; hNode = m_ctrlTree.GetChildItem(hNode); } while (NULL != hNode) ... { FreeNode(hNode); hNode = m_ctrlTree.GetNextSiblingItem(hNode); } }
CBrowseSysTreeDlg::CBrowseSysTreeDlg(CWnd
*
pParent
/**/
/* =NULL */
) : CDialog(CBrowseSysTreeDlg::IDD, pParent)
...
{ m_hIcon = AfxGetApp() -> LoadIcon(IDR_MAINFRAME); VERIFY(SHGetMalloc( & m_pMalloc) == NOERROR); }
void
CBrowseSysTreeDlg::DoDataExchange(CDataExchange
*
pDX)
...
{ CDialog::DoDataExchange(pDX); DDX_Control(pDX, IDC_SYS_TREE, m_ctrlTree); }
BEGIN_MESSAGE_MAP(CBrowseSysTreeDlg, CDialog) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON()
//
}}AFX_MSG_MAP
ON_NOTIFY(NM_DBLCLK, IDC_SYS_TREE,
&
CBrowseSysTreeDlg::OnNMDblclkSysTree) ON_NOTIFY(TVN_ITEMEXPANDING, IDC_SYS_TREE,
&
CBrowseSysTreeDlg::OnTvnItemexpandingSysTree) ON_WM_DESTROY() END_MESSAGE_MAP()
//
CBrowseSysTreeDlg 消息处理程序
BOOL CBrowseSysTreeDlg::OnInitDialog()
...
{ CDialog::OnInitDialog(); // 将“关于...”菜单项添加到系统菜单中。 // IDM_ABOUTBOX 必须在系统命令范围内。 ASSERT((IDM_ABOUTBOX & 0xFFF0 ) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000 ); CMenu * pSysMenu = GetSystemMenu(NULL); if (pSysMenu != NULL) ... { CString strAboutMenu; strAboutMenu.LoadString(IDS_ABOUTBOX); if ( ! strAboutMenu.IsEmpty()) ... { pSysMenu -> AppendMenu(MF_SEPARATOR); pSysMenu -> AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } } // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动 // 执行此操作 SetIcon(m_hIcon, TRUE); // 设置大图标 SetIcon(m_hIcon, NULL); // 设置小图标 // TODO: 在此添加额外的初始化代码 LPITEMIDLIST lpPidl; HIMAGELIST hSysImageList = NULL; SHFILEINFO shif; if (NOERROR != SHGetSpecialFolderLocation(m_hWnd, CSIDL_DESKTOP, & lpPidl)) return FALSE; hSysImageList = (HIMAGELIST) ::SHGetFileInfo((LPCWSTR)lpPidl, 0 , & shif, sizeof (shif), SHGFI_PIDL | SHGFI_SMALLICON | SHGFI_SYSICONINDEX); // 设置重叠图标 // 设置1号重叠图标对应图标列表中索引为0的图标,这是一个手托,象征被共享的文件夹 // 设置2号重叠图标对应图标列表中索引为1的图标,这是一个箭头,象征快捷方式 // 设置3号重叠图标对应图标列表中索引为2的图标,这个图标?? ImageList_SetOverlayImage(hSysImageList, 0 , 1 ); ImageList_SetOverlayImage(hSysImageList, 1 , 2 ); ImageList_SetOverlayImage(hSysImageList, 2 , 3 ); m_ctrlTree.SetImageList(CImageList::FromHandle(hSysImageList),TVSIL_NORMAL); HTREEITEM hItem = CreateFolderNode(lpPidl, NULL); AttachFolders(hItem); m_ctrlTree.Expand(hItem, TVE_EXPAND); return TRUE; // 除非将焦点设置到控件,否则返回 TRUE }
void
CBrowseSysTreeDlg::OnSysCommand(UINT nID, LPARAM lParam)
...
{ if ((nID & 0xFFF0 ) == IDM_ABOUTBOX) ... { CAboutDlg dlgAbout; dlgAbout.DoModal(); } else ... { CDialog::OnSysCommand(nID, lParam); } }
//
如果向对话框添加最小化按钮,则需要下面的代码
//
来绘制该图标。对于使用文档/视图模型的 MFC 应用程序,
//
这将由框架自动完成。
void
CBrowseSysTreeDlg::OnPaint()
...
{ if (IsIconic()) ... { CPaintDC dc( this ); // 用于绘制的设备上下文 SendMessage(WM_ICONERASEBKGND, reinterpret_cast< WPARAM > (dc.GetSafeHdc()), 0 ); // 使图标在工作矩形中居中 int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect; GetClientRect( & rect); int x = (rect.Width() - cxIcon + 1 ) / 2 ; int y = (rect.Height() - cyIcon + 1 ) / 2 ; // 绘制图标 dc.DrawIcon(x, y, m_hIcon); } else ... { CDialog::OnPaint(); } }
//
当用户拖动最小化窗口时系统调用此函数取得光标显示。
//
HCURSOR CBrowseSysTreeDlg::OnQueryDragIcon()
...
{ return static_cast < HCURSOR > (m_hIcon); }
void
CBrowseSysTreeDlg::OnNMDblclkSysTree(NMHDR
*
pNMHDR, LRESULT
*
pResult)
...
{ // TODO: Add your control notification handler code here HTREEITEM hItem = m_ctrlTree.GetSelectedItem(); if (m_ctrlTree.GetChildItem(hItem) == NULL) AttachFolders(hItem); * pResult = 0 ; }
void
CBrowseSysTreeDlg::OnTvnItemexpandingSysTree(NMHDR
*
pNMHDR, LRESULT
*
pResult)
...
{ LPNMTREEVIEW pNMTreeView = reinterpret_cast < LPNMTREEVIEW > (pNMHDR); // TODO: Add your control notification handler code here if (pNMTreeView -> action == TVE_EXPAND ) if (m_ctrlTree.GetChildItem(pNMTreeView -> itemNew.hItem) == NULL) AttachFolders(pNMTreeView -> itemNew.hItem); * pResult = 0 ; }
void
CBrowseSysTreeDlg::OnDestroy()
...
{ FreeNode(NULL); m_pMalloc -> Release(); delete m_pToolTipCtrl; CDialog::OnDestroy(); // TODO: 在此处添加消息处理程序代码 }