如何检测给定的 PIDL 是否实际上是 .zip 文件或类似文件?

2022-01-12 00:00:00 c++ mfc visual-studio-2012 windows-shell

VS2010 引入了 CMFCShellTreeCtrl,它允许将文件夹浏览器树 ctrl 拖放到我们的 MFC 应用程序中.

但是,这个类似乎严重缺乏过滤功能.即它将构建容器对象列表(IShellFolder).但似乎没有办法指定 .zip 容器不应显示在文件夹树中.

它确实提供了一个可以粗略用于此目的的虚拟:

<块引用>

HRESULT CMFCShellTreeCtrl::EnumObjects(HTREEITEM hParentItem, LPSHELLFOLDER pParentFolder, LPITEMIDLIST pidlParent){ASSERT_VALID(这个);ASSERT_VALID(afxShellManager);LPENUMIDLIST pEnum = NULL;HRESULT hr = pParentFolder->EnumObjects(NULL, m_dwFlags, &pEnum);如果(失败(小时)|| pEnum == NULL){返回小时;}LPITEMIDLIST pidlTemp;DWORD dwFetched = 1;//枚举项目的 PIDL:while (SUCCEEDED(pEnum->Next(1, &pidlTemp, &dwFetched)) && dwFetched){TVITEM 电视项目;ZeroMemory(&tvItem, sizeof(tvItem));//填写此项的 TV_ITEM 结构:tvItem.mask = TVIF_PARAM |TVIF_TEXT |TVIF_图像 |TVIF_SELECTEDIMAGE |TVIF_儿童;//AddRef 父文件夹,使其指针保持有效:pParentFolder->AddRef();//将私有信息放入 lParam:LPAFX_SHELLITEMINFO pItem = (LPAFX_SHELLITEMINFO)GlobalAlloc(GPTR, sizeof(AFX_SHELLITEMINFO));确保(pItem!= NULL);pItem->pidlRel = pidlTemp;pItem->pidlFQ = afxShellManager->ConcatenateItem(pidlParent, pidlTemp);pItem->pParentFolder = pParentFolder;tvItem.lParam = (LPARAM)pItem;CString strItem = OnGetItemText(pItem);tvItem.pszText = strItem.GetBuffer(strItem.GetLength());tvItem.iImage = OnGetItemIcon(pItem, FALSE);tvItem.iSelectedImage = OnGetItemIcon(pItem, TRUE);//判断item是否有子项:DWORD dwAttribs = SFGAO_HASSUBFOLDER |SFGAO_FOLDER |SFGAO_DISPLAYATTRMASK |SFGAO_CANRENAME |SFGAO_FILESYSANCESTOR;pParentFolder->GetAttributesOf(1, (LPCITEMIDLIST*) &pidlTemp, &dwAttribs);tvItem.cChildren = (dwAttribs & (SFGAO_HASSUBFOLDER | SFGAO_FILESYSANCESTOR));//判断项目是否共享:if (dwAttribs & SFGAO_SHARE){tvItem.mask |= TVIF_STATE;tvItem.stateMask |= TVIS_OVERLAYMASK;tvItem.state |= INEXTOOVERLAYMASK(1);//1 是共享叠加图像的索引}//填写此项的 TV_INSERTSTRUCT 结构:TVINSERTSTRUCT tvInsert;tvInsert.item = tvItem;tvInsert.hInsertAfter = TVI_LAST;tvInsert.hParent = hParentItem;InsertItem(&tvInsert);dwFetched = 0;}pEnum->Release();返回 S_OK;}

我感到困惑的是缺乏区分正在枚举的对象类型的能力(或者是一种更好的方法来首先控制枚举以便过滤掉诸如这些的非文件系统对象).

可以查看被枚举项目的文本,如果它以.zip"结尾,则简单地排除它.但是,这对我来说似乎很不稳定.毕竟,可以将任意文件夹命名为 XYZ.zip(它仍然是一个文件夹,而不是 zip 存档).类似地,未来可能还会支持除 .zip 之外的其他存档类型,或者其他容器类型,但它们实际上并不是文件夹.

我不想消除网络"和计算机"之类的有效节点.只是那些有问题的,例如DownloadsFoobar.zip"

对于这个漫无边际的问题,我深表歉意.任何有助于提高我的理解力和创造性方法以了解哪些类型的对象是 Microsoft 的 shell 命名空间的一部分,以及如何有效地使用它们,我们将不胜感激!

解决方案

一个 zip 文件/文件夹会有 SFGAO_STREAM/SFGAO_DROPTARGET 和 SFGAO_FOLDER,所以如果你可以将 shell 项读取为流,那么它可能不是目录.另一种判断方法是使用 SHGetPathFromIDList+PathIsDirectory,但这仅适用于具有文件系统路径的 pidl.

还有其他类型的可浏览文件,例如保存的搜索(如果您浏览该文件,则需要永远完成枚举项目),因此您可能也想考虑如何处理这些文件.

VS2010 introduced CMFCShellTreeCtrl which allows for a folder-browser tree ctrl to be dropped into our MFC apps.

However, there seems to be a serious lack of filtering capabilities in this class. i.e. it will build the list of container-objects (IShellFolder). But there doesn't seem to be a way to specify that .zip containers should not be displayed in the folder tree.

It does supply a virtual that could be used crudely for this purpose:

HRESULT CMFCShellTreeCtrl::EnumObjects(HTREEITEM hParentItem, LPSHELLFOLDER pParentFolder, LPITEMIDLIST pidlParent)
{
  ASSERT_VALID(this);
  ASSERT_VALID(afxShellManager);

  LPENUMIDLIST pEnum = NULL;

  HRESULT hr = pParentFolder->EnumObjects(NULL, m_dwFlags, &pEnum);
  if (FAILED(hr) || pEnum == NULL)
  {
      return hr;
  }

  LPITEMIDLIST pidlTemp;
  DWORD dwFetched = 1;

  // Enumerate the item's PIDLs:
  while (SUCCEEDED(pEnum->Next(1, &pidlTemp, &dwFetched)) && dwFetched)
  {
      TVITEM tvItem;
      ZeroMemory(&tvItem, sizeof(tvItem));

      // Fill in the TV_ITEM structure for this item:
      tvItem.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_CHILDREN;

      // AddRef the parent folder so it's pointer stays valid:
      pParentFolder->AddRef();

      // Put the private information in the lParam:
      LPAFX_SHELLITEMINFO pItem = (LPAFX_SHELLITEMINFO)GlobalAlloc(GPTR, sizeof(AFX_SHELLITEMINFO));
      ENSURE(pItem != NULL);

      pItem->pidlRel = pidlTemp;
      pItem->pidlFQ = afxShellManager->ConcatenateItem(pidlParent, pidlTemp);

      pItem->pParentFolder = pParentFolder;
      tvItem.lParam = (LPARAM)pItem;

      CString strItem = OnGetItemText(pItem);
      tvItem.pszText = strItem.GetBuffer(strItem.GetLength());
      tvItem.iImage = OnGetItemIcon(pItem, FALSE);
      tvItem.iSelectedImage = OnGetItemIcon(pItem, TRUE);

      // Determine if the item has children:
      DWORD dwAttribs = SFGAO_HASSUBFOLDER | SFGAO_FOLDER | SFGAO_DISPLAYATTRMASK | SFGAO_CANRENAME | SFGAO_FILESYSANCESTOR;

      pParentFolder->GetAttributesOf(1, (LPCITEMIDLIST*) &pidlTemp, &dwAttribs);
      tvItem.cChildren = (dwAttribs & (SFGAO_HASSUBFOLDER | SFGAO_FILESYSANCESTOR));

      // Determine if the item is shared:
      if (dwAttribs & SFGAO_SHARE)
      {
          tvItem.mask |= TVIF_STATE;
          tvItem.stateMask |= TVIS_OVERLAYMASK;
          tvItem.state |= INDEXTOOVERLAYMASK(1); //1 is the index for the shared overlay image
      }

      // Fill in the TV_INSERTSTRUCT structure for this item:
      TVINSERTSTRUCT tvInsert;

      tvInsert.item = tvItem;
      tvInsert.hInsertAfter = TVI_LAST;
      tvInsert.hParent = hParentItem;

      InsertItem(&tvInsert);
      dwFetched = 0;
  }

  pEnum->Release();
  return S_OK;
}

What I am confused by is the lack of ability to distinguish what type of object this is that is being enumerated (or a better way to control the enumeration in the first place so as to filter out non-filesystem objects such as these).

It's possible to look at the text for the item being enumerated, and simply exclude it if it ends in ".zip". However, this seems wonky to me. After all, an arbitrary folder could be named XYZ.zip (which still being a folder, and not a zip archive). Similarly, there are likely other archive types than just .zip that may be yet supported in the future - or other container types which are nevertheless not really folders.

I don't want to eliminate things like "Network" and "Computer" from being valid nodes. Just ones that are problematic such as "DownloadsFoobar.zip"

I apologize for this question being rambling. Any help in improving my understanding and creative approaches to knowing what sorts of objects are part of Microsoft's shell namespace, and how that they can be gainfully used would be appreciated!

解决方案

A zip file/folder would have SFGAO_STREAM/SFGAO_DROPTARGET along with SFGAO_FOLDER, so if you can read the shell item as stream, then it probably isn't a directory. Another way to tell is to use SHGetPathFromIDList+PathIsDirectory, however this only works for pidl that have file system paths.

There are other kind of browsable files too, like a saved search (and if you browse into the file it takes like forever to finish enumerating the items), so you probably want to think about how to deal with those files too.

相关文章