控件之Tree Control (仿系统目录树视图)

翻译|其它|编辑:郝浩|2007-09-10 09:52:50.000|阅读 1876 次

概述:

# 界面/图表报表/文档/IDE等千款热门软控件火热销售中 >>

一、几个概念(摘)

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

3GetAttributesOf  获取文件属性

注意事项:参数中是绝对  PIDL  还是相对  PIDLSHGet  绝对,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: 在此处添加消息处理程序代码
}


标签:

本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至chenjj@evget.com

文章转载自:csdn

为你推荐

  • 推荐视频
  • 推荐活动
  • 推荐产品
  • 推荐文章
  • 慧都慧问
扫码咨询


添加微信 立即咨询

电话咨询

客服热线
023-68661681

TOP