学习心得:控件之Tree Control (仿系统目录树视图)

本文介绍了Windows外壳名字空间的概念,包括外壳名字空间的定义、PIDL路径以及相关API如SHGetSpecialFolderLocation和SHGetFileInfo的使用。此外,还讨论了如何利用这些知识来仿造系统目录树视图,提供了在VS2005环境下实现的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 一、几个概念(摘)

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: 在此处添加消息处理程序代码
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值