您的位置:首页 > 其它

学习心得:控件之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: 在此处添加消息处理程序代码


}

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: