浏览名字空间

http://blog.sina.com.cn/s/blog_56dee71a0100fucz.html

MSDN 2005 -> Win32 和 COM 开发 -> User Interface -> Windows User Experience -> Windows Shell -> Windows Shell -> Shell Programmer's Guide -> Shell Basics -> Navigating the Namespace

其实,在翻译这篇文档前,我就根据前面几篇文档的内容,写了个简单的名字空间浏览程序了。它比这篇MSDN文档的示例程序功能完整些,可以说是个简化版的资源管理器程序。其界面如下:

浏览名字空间

 

它可以在名字空间中浏览,双击某文件夹项时,将打开文件夹,列出其内容;而双击叶子节点时,将执行与之关联的默认动作,就跟在资源管理器中双击节点类似。比如说,双击某控制面板项将执行相关的控制面板应用;双击某文件时,将启动关联程序对其进行编辑。

从程序角度总结如下:

 

1 获取文件夹的IShellFolder接口指针

列出文件夹内容是通过IEnumIDList接口进行的,而这个接口要通过IShellFolder接口获得。获取某文件夹IShellFolder接口指针的代码如下:

 

int CShellNamespace::Goto(LPCTSTR pPath)
{
    if (NULL == m_pFolder) SHGetDesktopFolder(&m_pFolder);
    if (NULL == m_pFolder) return -1;

    if (NULL != pPath)
    {
        LPTSTR      pTmpPath;
        LPOLESTR    pOlePath;
        ITEMIDLIST* pidl;
        HRESULT hRet;
        IShellFolder* pNewFolder;
        USES_CONVERSION;

        pTmpPath = StrDup(pPath);
        if (NULL == pTmpPath) return -1;
        pOlePath = T2OLE(pTmpPath);
       
        hRet = m_pFolder->ParseDisplayName(NULL,NULL,pOlePath,NULL,&pidl,NULL);
        LocalFree((HLOCAL)pTmpPath);
        if (FAILED(hRet)) return -1;
       
        hRet = m_pFolder->BindToObject(pidl,NULL,IID_IShellFolder,(void**)&pNewFolder);    
        if (FAILED(hRet)) return -1;

        FreeMemory(m_pIdl);
        m_pIdl = pidl;
        m_pFolder->Release();
        m_pFolder = pNewFolder;
    }

    return RefreshList();
}

 

首先是获取桌面的IShellFolder接口指针,然后通过ParseDisplayName获取指定路径的PIDL,再通过BindToObject获取PIDL对应的文件夹的IShellFolder接口指针。要注意的是:

(1) 调用ParseDisplayName时,第三个参数表示文件夹显示名,对于文件系统文件夹,也就是通常所说的路径。这个参数是LPOLESTR类型的,所以要对LPCTSTR类型的路径名进行转换。方法是使用COM编程中的转换宏。这个是从我买的那本烂书《COM+编程指南》中学来的。

(2) 把pNewFolder赋值给m_pFolder之前,先调用m_pFolder->Release()。这是COM编程中,接口指针的引用计数机制要求的。这个还是得益于那本烂书。

(3) 当然,用SHParseDisplayName和SHBindToObject替换IShellFolder的两个同名方法也是可以的。感觉SHxxx方法等价于桌面对象的IShellFolder::xxx方法。

 

2 列出文件夹内容

 


int CShellNamespace::RefreshList()
{
    IShellFolder* pDesktop = NULL;
    IEnumIDList* pEnum = NULL;
    ITEMIDLIST*  pidl = NULL;
    ITEMIDLIST*  pfullidl = NULL;
    STRRET   objName = {0};
    LPTSTR  pName = NULL;
    SHFILEINFO sfiFileInfo;
    CImageList* pImageList;
    int nIdx,nCount;
    SHCONTF flags;
    HRESULT hRet;
    DWORD   dwRet;

    ASSERT(m_pFolder);
    ASSERT(m_pListCtrl);
   
    SHGetDesktopFolder(&pDesktop);
    if (NULL == pDesktop) return -1;

    // 开始枚举
    flags = SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN;
    hRet  = m_pFolder->EnumObjects(NULL,flags,&pEnum);
    if (FAILED(hRet)) return -1;
   
    // 复位列表控件
    m_pListCtrl->SetRedraw(FALSE);
    m_pListCtrl->DeleteAllItems();
    ResetImageList();
    if (m_pFolder != pDesktop)
    {
        m_pListCtrl->InsertItem(0,_T("向上"),0);
        m_pListCtrl->SetItemData(0,0);
    }
    pDesktop->Release();
   
    hRet = pEnum->Next(1,&pidl,NULL);
    while (NOERROR == hRet)
    {
        // 取显示名
        pfullidl = NULL;
        ZeroMemory(&objName,sizeof(objName));
        objName.uType = STRRET_WSTR;
        hRet = m_pFolder->GetDisplayNameOf(pidl,SHGDN_NORMAL,&objName);
        if (S_OK != hRet) goto next_item;

        hRet = StrRetToStr(&objName,pidl,&pName);
        if (S_OK != hRet) goto next_item;

        // 取关联图标
        pfullidl = CatIDList(pfullidl,m_pIdl);
        pfullidl = CatIDList(pfullidl,pidl);
        if (NULL == pfullidl) pfullidl = pidl;

        nIdx = 0;
        pImageList = m_pListCtrl->GetImageList(LVSIL_NORMAL);      
        dwRet = SHGetFileInfo((LPCTSTR)pfullidl,FILE_ATTRIBUTE_NORMAL,
                    &sfiFileInfo,sizeof(sfiFileInfo),
                    SHGFI_ICON | SHGFI_PIDL);
        if (dwRet > 0) nIdx = pImageList->Add(sfiFileInfo.hIcon);

        nCount = m_pListCtrl->GetItemCount();
        m_pListCtrl->InsertItem(nCount,pName,nIdx);
        m_pListCtrl->SetItemData(nCount,(DWORD_PTR)pidl);

next_item:
        if (pfullidl != pidl) FreeMemory(pfullidl);
        if (pName) CoTaskMemFree(pName);
        if (objName.pOleStr) FreeMemory(objName.pOleStr);
        hRet = pEnum->Next(1,&pidl,NULL);
    }

    pEnum->Release();

    m_pListCtrl->SetRedraw(TRUE);

    UpdateUI();

    return 0;
}

 

要点如下:

(1)调用IShellFolder::EnumObjects方法获取IEnumIDList接口指针,通过它的Next方法枚举文件夹内容。

(2)IEnumIDList::Next方法会返回文件夹内容项的单层PIDL,把它传给IShellFolder::GetDisplayName方法就可以获取内容项的显示名。显示名是以STRRET结构体的形式返回的,需要用StrRetToStr把它转换成字符串形式。

(3)SHGetFileInfo可以获取文件的各种属性,包括图标相关属性。它要求使用全限定PIDL,所以另外编写了CatIDList方法来串接两个PIDL。把当前全限定PIDL,即m_pIdl与内容项的单层PIDL,即pidl串接起来,就得到内容项的全限定PIDL了。这就好像把文件名串接到文件所在文件夹的全路径名之后,就得到文件的全路径名一样。CatIDList方法的代码如下:

 

// 连接两个ITEMIDLIST
ITEMIDLIST* CShellNamespace::CatIDList(ITEMIDLIST* pDstList,const ITEMIDLIST* pSrcList)
{
    const BYTE* pData;
    unsigned short nDstTotalSize = 0;
    unsigned short nSrcTotalSize = 0;
    unsigned short nItemSize,nLastItemSize;
    LPMALLOC   pMalloc;

    if (S_OK != CoGetMalloc(1,&pMalloc)) return NULL;

    // Src List Size
    if (NULL != pSrcList)
    {
        pData = (BYTE*)pSrcList;
        while (0 != (nItemSize = *((unsigned short*)pData)))
        {
            nSrcTotalSize += nItemSize;
            pData += nItemSize;
        }
        nSrcTotalSize += 2;
    }

    // Dst List Size
    nLastItemSize = 0;
    if (NULL != pDstList)
    {
        pData = (BYTE*)pDstList;
        while (0 != (nItemSize = *((unsigned short*)pData)))
        {
            nDstTotalSize += nItemSize;
            pData += nItemSize;
            nLastItemSize = nItemSize;
        }
    }
   
    // Copy
    // To parent folder
    if (0 == nSrcTotalSize)
    {
        nDstTotalSize -= nLastItemSize;
        pDstList = (ITEMIDLIST*)pMalloc->Realloc(pDstList,nDstTotalSize + 2);
        if (NULL == pDstList) return NULL;
        *((BYTE*)pDstList + nDstTotalSize + 0) = 0;
        *((BYTE*)pDstList + nDstTotalSize + 1) = 0;
    }
    // To child folder
    else
    {
        pDstList = (ITEMIDLIST*)pMalloc->Realloc(pDstList,nDstTotalSize + nSrcTotalSize);
        if (NULL == pDstList) return NULL;
        CopyMemory((BYTE*)pDstList + nDstTotalSize,pSrcList,nSrcTotalSize);
    }
   
    pMalloc->Release();

    return pDstList;
}

 

只要理解了《Shell名字空间》一文《标识Shell对象》节的内容,就不难理解了。

 

3 双击内容项的处理

双击“向上”图标时,需要返回到上级目录,列出其内容;如果双击的是文件夹对象,则打开文件夹,列出其内容;否则,对于文件对象,则执行其默认动作,比如说,对于.txt文件,可能是打开记事本程序对其进行编辑;而对于控制面板小应用,则是运行它。当然,这里的“文件夹”是指Shell文件夹,它既可以是文件系统文件夹,也可以是像“我的电脑”、“网络连接”这样的虚拟文件夹。对于文件,即可以是普通的文件系统中的文件,也可以是Shell名字空间的叶子节点,比如说,“鼠标”这个控制面板小应用。

双击内容项由下面的代码进行处理:

 


int CShellNamespace::GotoSubFolder(DWORD dwData)
{
    const ITEMIDLIST* pidl = (ITEMIDLIST*)dwData;
    IShellFolder* pNewFolder;
    HRESULT hRet;
    ULONG   nAttr;

    if (NULL == pidl)
    {
        hRet = SHBindToParent(m_pIdl,IID_IShellFolder,(void**)&pNewFolder,NULL);
    }
    else
    {
        nAttr = SFGAO_FOLDER;
        hRet  = m_pFolder->GetAttributesOf(1,&pidl,&nAttr);
        // 浏览内容
        if (nAttr & SFGAO_FOLDER)
        {
            hRet  = m_pFolder->BindToObject(pidl,NULL,IID_IShellFolder,(void**)&pNewFolder);
        }
        // 打开对象
        else
        {
            OpenItem(pidl);
            return 0;
        }
    }
    if (S_OK != hRet) return -1;
   
    m_pIdl = CatIDList(m_pIdl,pidl);
    if (NULL == m_pIdl) return -1;
    m_pFolder->Release();
    m_pFolder = pNewFolder;
    return RefreshList();
}

 

要点如下:

(1)传入的参数是列表项的附加数据,这在上文的RefreshList方法中被设置为内容项的单层PIDL。对于“向上”图标,设置附加数据为NULL。

(2)用IShellFolder::GetAttributesOf可以获取Shell对象的各种属性。这里获取文件夹内容项的SFGAO_FOLDER属性,即判断内容项是不是子文件夹。如果是子文件夹,就打开它,列出其内容;否则调用OpenItem对其执行默认动作。

(3) OpenItem方法的代码如下:

 

void CShellNamespace::OpenItem(const ITEMIDLIST* pidl)
{
    ITEMIDLIST* pFullIdl = NULL;
    SHELLEXECUTEINFO info = {0};
   
    pFullIdl = CatIDList(pFullIdl,m_pIdl);
    pFullIdl = CatIDList(pFullIdl,pidl);
    if (NULL == pFullIdl) return;

    info.cbSize = sizeof(info);
    info.fMask  = SEE_MASK_IDLIST;
    info.lpIDList = pFullIdl;
    info.nShow  = SW_SHOWNORMAL;
    ShellExecuteEx(&info);
    FreeMemory(pFullIdl);
}

 

参考《启动应用程序》一文的内容,很容易理解的。

 

4 一个小问题

程序写好后,发现对于控制面板中的“网络连接”、“管理工具”这两个虚拟文件夹,无法打开浏览其内容。因为对于“我的电脑”、“网上邻居”这两个虚拟文件夹,工作是正常的,所以应该不是虚拟文件夹的问题。调试发现,运行到GotoSubFolder方法的下面一行时:

hRet  = m_pFolder->BindToObject(pidl,NULL,IID_IShellFolder,(void**)&pNewFolder);

在Watch窗口输入hRet,其值显示为“0x800401f0 尚未调用CoInitialize。”;而输入@err,hr,值显示为“0x0000007e 找不到指定的模块。”。原来没有显式调用CoInitialize,程序也好像能够正确工作,我就以为对于MFC应用程序,初始化的时候,CoInitialize是已经被CWinApp调用了的,没想到会出这样的错误。在InitInstance方法中加入对CoInitialize的调用,然后调试程序就OK了。对于使用COM的程序,应该对每个线程分别调用CoInitialize或者CoInitializeEx来初始化COM系统。

 

 

以下是MSDN文档原文翻译:

现在已经了解浏览名字空间中任何地方所需的所有元素了。开始浏览的最简单方法是调用SHGetDesktopFolder获取桌面的IShellFolder接口。然后,可以按照下列步骤在名字空间中向下浏览:

  1. 枚举文件夹的内容
  2. 确定哪些对象是子文件夹,并且选择其中一个
  3. 绑定到选定的子文件夹,获取其IShellFolder接口

重复上述步骤直到达到浏览目标。

浏览名字空间的简单例子

下面的代码是一个简单的控制台应用程序。它展示了前一节讨论的过程。为使结构清晰,省略了错误检查。程序进行下列任务:

  • 获取Program Files文件夹的IShellFolder接口
  • 枚举文件夹的内容
  • 确定显示名并输出
  • 查找子文件夹
  • 绑定到找到的子文件夹
  • 输出子文件夹中对象的显示名

<…… 省略示例代码 ……>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值