学习心得:控件之Tree Control (仿系统目录树视图)
2007-09-08 02:10
387 查看
一、几个概念(摘)
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: 在此处添加消息处理程序代码
}
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: 在此处添加消息处理程序代码
}
相关文章推荐
- 学习心得:控件之CToolTipCtrl(加入仿系统目录树视图)
- MFC树视图控件(Tree Control)
- MFC控件(四)(树视图控件TreeControl)
- radcontrol radupload 上传控件的学习与使用
- 交通灯管理系统——学习心得
- MFC编程(标签控件Tab Control(选项卡控件) 和picture control 来实现视图的切换 )
- 从零开始--系统深入学习android(已完成部分的目录--带链接)
- 鸡啄米vc++2010系列28(列表视图控件List Control 下)
- Linux系统学习第二章:文件与目录操作(三):文件操作
- java 实现系统目录树 ,树控件使用,显示 系统文件夹
- MYSQL学习心得(7) -- 视图
- 一步步学习微软InfoPath2010和SP2010--第三章节--表单设计基础:处理InfoPath布局、控件和视图(1)--表单布局
- linux系统学习笔记——文件与目录管理
- 【系统学习SpringBoot】目录结构(建议)
- Cocoa Touch 入门记——《精通 iOS 开发》学习心得(1) [基本控件的交互]
- react-native-htmlview 是一个将 HTML 目录作为本地视图的控件
- 《水泥公司信息管理系统》Access学习心得
- WPF下的地图开发控件(GMap.NET)使用心得—— GMap学习笔记(二)
- 计算机科学与技术学习心得之专谈计算机系统的学习(转载)
- 一步步学习微软InfoPath2010和SP2010--第三章节--表单设计基础:处理InfoPath布局、控件和视图(7)--添加含规则的提交按钮到Flight Delay表单