65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 IShellFolder 重写 DirectoryInfo

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (17投票s)

2009年8月22日

LGPL3

9分钟阅读

viewsIcon

182003

downloadIcon

1625

本文介绍 DirectoryInfoEx 如何使用 IShellFolder 来使用 C# 列出特殊/虚拟目录。

引言

DirectoryInfo 是一个表示磁盘上文件夹的类,它适用于列出文件系统条目,但不能用于表示特殊文件夹(例如,不存在于磁盘上的虚拟文件夹)。您可能需要使用 IShellFolder 来枚举这些目录。DirectoryInfoEx 是为了支持这些文件夹而编写的。

该项目基于 Steven Roebert 的 C# 文件浏览器代码和文章进行重写。他的代码比我的项目做得更多,他的文章强调了如何充分利用 shell,一旦您有了完整的实现(例如,上下文菜单、shell 拖放、预览处理程序等),但缺乏关于如何创建一个的文档。我重写了他代码的一部分(核心部分)来学习它是如何工作的,本文将解释如何在 C# 中使用 IShellFolder 接口执行基本的文件操作。

由于我知识的不足,以及 DirectoryInfo / FileInfo 的性质,新的类比 CShellItem 简单得多。

如何使用

基本上,它与 System.IO.DirectoryInfo 的接口相同,它仅支持 x86/x64 桌面应用程序(不支持 WinRT)

您可以通过new DirectoryInfoEx() 来构建一个条目,支持的参数包括:

  • Path - 磁盘路径(例如 C:\)或 Shell guid 路径(例如 Desktop = ::{00021400-0000-0000-C000-000000000046})
  • CSIDL - 用于标识特殊目录,已废弃,因为它是不可更改的。
  • Environment.SpecialFolder - CSIDL 的 .Net 枚举,已废弃。
  • KnownFolder - 已知文件夹标识符,可由软件供应商更改。
  • KnownFolderIds - DirectoryInfoEx 的 KnownFolderIds 枚举。

要获取子项,DirectoryInfoEx 包含用于所有文件系统条目的 GetFileSystemInfos()GetFileSystemInfosAsync()EnumerateFileSystemInfos() 方法,以及仅用于目录或文件的 Get/EnumerateDirectories/Files(Async) 方法。

FileInfoEx 支持 Open() 来打开和/或写入 FileStreamEx,这是一个 Stream,其他辅助方法包括 OpenText()OpenRead()AppendText()

对于基于静态的操作,有 DirectoryExFileEx 静态类,并且由于路径(例如 GUID)的变化,请使用 PathEx 代替。

目录

  • 获取 PIDL
  • IShellFolder 接口
  • IStorage 接口
  • IStream 接口
  • 我的 DirectoryInfoEx 实现
  • 演示

获取 PIDL

Shell 命名空间中的文件夹和文件可以通过 PIDL ITEMIDLIST)定位,就像文件夹路径(或 DisplayName)一样,有相对 PIDL 和完整 PIDL ,就像相对路径(abc.txt)和完整路径(c:\abc.txt)一样。

要从字符串路径获取 PIDL ,您可以使用 Desktop 的 IShellFolder.ParseDisplayName()(见下文)。

ShellAPI.SFGAO pdwAttributes = 0;
DesktopShellFolder.ParseDisplayName(IntPtr.Zero, IntPtr.Zero,
     path, ref pchEaten, out pidlPtr, ref pdwAttributes);
PIDL pidl = new PIDL(pidlPtr, false); 

从 CSIDL 获取 PIDL

对于特殊目录(例如,DRIVES 等虚拟目录,或 PROFILE 等特殊文件系统目录),CSIDL 枚举中有一个列表,您可以使用 SHGetSpecialFolderLocation() 来获取其 PIDL。

int RetVal = ShellAPI.SHGetSpecialFolderLocation(IntPtr.Zero, csidl, out ptrAddr);
if (ptrAddr != IntPtr.Zero)
{
  pidl = new PIDL(ptrAddr, false);
  return pidl;
}

从 KnownFolder 获取 PIDL

由于新增了目录,基于 int 的 CSIDL 在 Windows Vista 中被 IKnownFolder 取代,

public interface IKnownFolder
{
    void GetId(...); //Return Guid of KnownFolder, see here.
    void GetCategory(...); //Return Fixed, Virtual, Common and PerUser    
    //Get IShellItem which replaced PIDL, 
    //but Current version DirectoryInfoEx (1.0.27) still uses PIDL.
    void GetShellItem(..., Guid interfaceGuid, out object shellItem); 
    //Get PIDL, so this is used instead.
    int GetIDList(...,out IntPtr itemIdentifierListPointer);
    //Get and SetPath
    void GetPath(...); 
    void SetPath(...);
    //Return a definition structure with more information of that KnownFolder.
    void GetFolderDefinition(...);
    ...
}

IKnownFolderManager 可以将 IKnownFolder 与路径、pidl、csidl 相互转换。

public interface IKnownFolderManager
{
    void FolderIdFromCsidl(int Csidl, out Guid knownFolderID);
    void FolderIdToCsidl(Guid id, out int Csidl);         
    void GetFolderIds(out IntPtr folders, out UInt32 count);
    void GetFolder(Guid id, IKnownFolder knownFolder);
    void GetFolderByName(string canonicalName, out IKnownFolder knowFolder);
    void FindFolderFromPath(string path,KnownFolderFindMode mode, out IKnownFolder knownFolder);
    void FindFolderFromIDList(IntPtr pidl, out IKnownFolder knownFolder); 
    ...
}

DirectoryInfoEx 基于 pidl,因此如果使用 IKnownFolder 或 KnownFolderIds 构建,它将使用 IKnownFolder.GetIDList() 方法来获取 PIDL 作为条目的 ID。

KnownFolderIds 是一个手动准备的枚举,包含在 此页面(最高至 Windows 8.1)上找到的所有 200 个 KnownFolders。因此,您可以使用以下方式初始化 DirectoryInfoEx:

string[] txtFiles = new DirectoryInfoEx(KnownFolderIds.DocumentsLibrary)
    .EnumerateFiles("*.txt", SearchOption.TopDirectoryOnly)
    .Select(fi => fi.FullName)
    .ToArray(); 

 

获取 IShellFolder 接口

Desktop 是所有 shell 命名空间文件夹的根,您可以使用 SHGetDesktopFolder() 来获取 **Desktop** 的 IShellFolder 接口。

IntPtr ptrShellFolder = IntPtr.Zero;

if (ShellAPI.SHGetDesktopFolder(out ptrShellFolder) == ShellAPI.S_OK)
  iShellFolder = (IShellFolder)Marshal.GetTypedObjectForIUnknown(ptrShellFolder,
     typeof(IShellFolder)); 

至于其他目录(包括非文件目录,例如 **MyComputer**),您可以使用 BindToObject()

if (Parent.ShellFolder.BindToObject(pidl.Ptr, IntPtr.Zero,
   ref ShellAPI.IID_IShellFolder, out ptrShellFolder) == ShellAPI.S_OK)

iShellFolder = (IShellFolder)Marshal.GetTypedObjectForIUnknown(ptrShellFolder,
     typeof(IShellFolder)); 

IShell Interface

它包含管理文件夹的方法,例如:

  • 获取子项列表(子文件夹/子文件),例如:
    static ShellAPI.SHCONTF folderflag = ShellAPI.SHCONTF.FOLDERS |
     ShellAPI.SHCONTF.INCLUDEHIDDEN;
     /* Specify to include folders only */
    
    if (iShellFolder.EnumObjects(IntPtr.Zero, folderflag, out ptrEnum)
        == ShellAPI.S_OK) //return the pointer of IEnumIDList
    {
       IEnumIDList IEnum = (IEnumIDList)Marshal.GetTypedObjectForIUnknown(ptrEnum,
         typeof(IEnumIDList));
       IntPtr pidlSubItem;
       int celtFetched;
    
       while (IEnum.Next(1, out pidlSubItem, out celtFetched) == ShellAPI.S_OK
         && celtFetched == 1)
           dirList.Add(new PIDL(pidlSubItem, false));
    /*  Add PIDL of each subdirectory to dirList, noted that in normal case you
     *  should release pidlSubItem but Steven Roebert's PIDL class is a IDisposable
     *  class which will dispose it for you.
     *  Also noted that the pidl is relative instead of full.
     *  Use GetDisplayNameOf() (see below) to get it's name. */
    
       if (IEnum != null) //Release resource
       {
          Marshal.ReleaseComObject(IEnum);
          Marshal.Release(ptrEnum);
        }
      } 
  • 使用 GetDisplayNameOf()PIDL 转换回可读路径。
    IntPtr ptrStr = Marshal.AllocCoTaskMem(ShellAPI.MAX_PATH * 2 + 4);
    Marshal.WriteInt32(ptrStr, 0, 0);
    StringBuilder buf = new StringBuilder(ShellAPI.MAX_PATH);
    
    try
    {
      /*  uflags is a SHGNO enum that allow you to get different folder names,
       *  e.g. "My Documents"(SHGNO.NORMAL) folder is named as
       *       "Documents"(SHGNO.FORPARSING) in file system.
       *  StrRetToBuf() convert STRRET structure to
       *  buffer usable by StringBuilder */
    if (iShellFolder.GetDisplayNameOf(pidl, uFlags, ptrStr) == ShellAPI.S_OK)
        ShellAPI.StrRetToBuf(ptrStr, pidl, buf, ShellAPI.MAX_PATH);
    }
    finally
    {
      if (ptrStr != IntPtr.Zero)
        Marshal.FreeCoTaskMem(ptrStr);
      ptrStr = IntPtr.Zero;
    }
    Console.WriteLine(buf.ToString()); 
  • 检索子文件夹的 IShellFolder / IStorage 接口。

    使用 SHBindToParent()

    IntPtr pidlLast = IntPtr.Zero;
    retVal = ShellAPI.SHBindToParent(dir.PIDLRel.Ptr, ShellAPI.IID_IStorage,
      out storagePtr, ref pidlLast); 

    BindToStorage()

    retVal = dir.Parent.ShellFolder.BindToStorage(
                        dir.PIDLRel.Ptr, IntPtr.Zero, ref ShellAPI.IID_IStorage,
                        out storagePtr);
    /* Beside IID_IStorage interface, there is IID_IStream and
       IID_IPropertySetStorage as well. */
    
    if ((retVal == ShellAPI.S_OK))
    {
      IStorage storage = (IStorage)Marshal.GetTypedObjectForIUnknown(storagePtr,
      typeof(IStorage));
      /* Your work here, free the pointer and interface when done. */
    } 

IStorage Interface

它包含用于在文件夹中创建或管理项/子项的方法,例如:

  • 重命名、移动或复制文件,使用 PIDL 作为参数。例如:
    SrcStorage.MoveElementTo(SourceFileName, DestStorage,
       DestFilename, ShellAPI.STGMOVE.MOVE) != ShellAPI.S_OK) 
  • 删除文件
    ParentStorage.DestroyElement(name); 
  • 读/写文件内容
    /* FileStreamEx class is a customized IDisposable Stream class,
     * which uses IStream interface of a file. */
    FileStreamEx stream = new FileStreamEx(path, mode, access);
    StreamReader sr = new StreamReader(stream);
    Console.WriteLine(sr.ReadToEnd()); 

IStream Interface

它允许您读/写数据到流对象。

  • 要获取 IStream 接口,请使用 OpenStream()(读/写)或 CreateStream()(创建新)。
    if (parentStorage.OpenStream(filename, IntPtr.Zero, grfmode, 0,
      out streamPtr) == ShellAPI.S_OK)
        stream = (IStream)Marshal.GetTypedObjectForIUnknown(streamPtr,
          typeof(IStream)); 
  • IStream 包含 seek()read()write() 等方法。
  • 如果流对象被释放,则认为它已关闭。

所有接口对象在使用完毕后都应被释放。

我的 DirectoryInfoEx 实现

该实现比 CShellItem 简单,但是,由于 FileSystemInfoEx 的 PIDL 被公开(通过 PIDLRel PIDL 属性),您可以实现自定义操作(例如,提取图标、上下文菜单)。IShellFolder IStorage (按需生成)也在 DirectoryInfoEx 中公开,它们在处置时会自动销毁,请勿自行释放它们。

DirectoryInfoEx FileInfoEx 都继承自 FileSystemInfoEx,它们用于枚举(列出)子项,DirectoryInfoEx 包含 GetFiles() GetDirectories() 方法用于此目的。要修改文件,请使用 FileEx 类来管理文件(DirectoryEx 尚未实现,目前请使用 System.IO.Directory ),并使用 FileStreamEx 类来读写文件。

DirectoryInfoEx (和 FileInfoEx)的构造函数接受 Path 或 PIDLDirectoryInfoEx 中定义了许多特殊目录,包括 DesktopDirectoryMyComputerDirectoryCurrentUserDirectorySharedDirectory NetworkDirectory。对于其他特殊目录,您可以通过调用 DirectoryInfoEx.CSIDLtoPIDL() 来获取其 PIDL

以及一个演示

此演示是一个简单的 WPF 应用程序,它列出了桌面下的子目录。图标是使用 SHGetFileInfo() 获取的,该函数接受一个完整的 PIDL 参数。

1.0.27 - 此演示已删除,目前请查看 FileExplorer

问题

  • 需要花费时间来实现 IShellItem 来替换 ItemIDList (PIDL)。

参考文献

历史记录

  • 09-08-23 版本 0.2
    • 更新了演示。
  • 09-11-01 版本 0.3
    • 演示不再加载网络内容,编辑转换器以禁用此更改。
    • 添加了 DirectoryEx(静态类)。
    • PIDL 类现在是 IDisposable 并且自动释放。还添加了新的内部类 ShellFolderStorage,它们执行相同的功能。
    • 性能得到改进,不再从桌面目录构造。(见上文)
    • DirectoryInfoExFileInfoEx 现在可序列化。
  • 09-11-01 版本 0.4
    • 修复了缓存未生效的问题。
  • 09-11-04 版本 0.5
    • 即使指定的路径不存在,DirectoryInfoEx/FileInfoEx 也能正常工作(Exists == false,您必须在尝试使用它之前调用 Create()Refresh())。
    • 添加了 Refresh()Create()MoveTo()Delete()CreateSubdirectory() Open() 和相关的实例方法。
    • 构造函数支持环境变量(例如 %temp%)
    • 测试项目。
  • 09-11-07 版本 0.6
    • 添加了上下文菜单支持(ContextMenuWrapper)。
    • 更新了演示(上下文菜单)。
    • 添加了 FileSystemWatcherEx 类。
    • 修复了由 EnumFiles()(由 GetFiles()GetFileSystemInfos() 使用)创建的 FileInfoEx 返回错误的父目录。
  • 09-11-08 版本 0.7
    • 修复了所有 FileInfoEx 的根目录等于 c:\Users\{User}\Desktop 而不是 GUID 的问题。
    • 更新了演示(上下文菜单多选)。
  • 09-11-16 版本 0.8
    • 修复了在同一目录中无法重命名项的问题。
    • 修复了 ContextMenuWrapper 在弹出时未返回 OnHover 消息的问题。
    • 添加了 QueryMenuItemsEventArgs.Command,以便正确返回用户查询项。
    • 更新了演示(添加了状态栏)。
  • 09-12-06 版本 0.9
    • 修复了 DirectoryInfoEx.EmuFiles 中的小拼写错误(if (iShellFolder != null))。
    • 修复了 DirectoryEx.Copy 未递归复制目录的问题(它目前只复制一个空文件夹)。
    • 修复了 DIrectoryEx.Move(以及可能的 FileEx)无法正常工作的问题。
    • 修复了 DirectoryInfoEx.Delete() 中的错误运算符 (new),应为 override。
  • 10-01-04 版本 0.10
    • 修复了 FileSystemInfoEx.getParentIShellFolder() 方法在 Desktop 目录中直接项的 pidl 生成 ArgumentException 的问题(由 _desktopShellFolder.BindToObject({Desktop's PIDL},...); 引起)。
    • 修复了 FileSystemInfoEx.Delete() 调用时返回 NotImplementException 的问题。
    • 修复了 DirectoryEx/FileEx.Exists 未检查它是目录/文件的问题。
    • 修复了 FileSystemInfoEx.refresh() 方法未更新属性的问题。
    • FileSystemInfoExDirectoryInfoExFileInfoEx 类中实现了 IClonable 接口。
    • ContextMenuWrapper 类添加了 BeforeInvoke 事件。
    • 添加了双击文件列表时的运行行为。
    • 添加了 FileSystemWatcherEx.Filter
  • 10-01-20 版本 0.11
    • 添加:演示中的 DirectoryTree;现在可以正确刷新更改。(在 GetDirectoriesConverterEx 类中使用 FileSystemWatcherEx 类实现了一个 ObservableCollection。)
    • 添加:ContextMenuWrapper.OnQueryMenuItems.QueryContextMenu2 / QueryContextMenu3 属性。
    • 添加:ContextMenuWrapper.OnBeforePopup 事件。
    • 添加:ContextMenuWrapper.OnQueryMenuItems 事件;现在支持多级菜单(例如:@"Tools\Add")。
    • 添加:ContextMenuWrapper.OnQueryMenuItems 事件;现在支持 GrayedItems / HiddenItems。
  • 10-02-15 版本 0.12
    • 修复:桌面下的用户/共享目录的全名现在是其 GUID 而不是其文件路径。
    • 修复:PIDL、PIDLRel、ShellFolder、Storage 属性按需生成,以避免跨线程问题。
    • 添加:PathEx 类,用于处理 PIDL 相关的路径。
  • 10-03-14 版本 0.13
    • 修复:FileSystemWaterEx 忽略了删除目录事件。
    • 修复:删除了 PIDL 中的 IDisposable,因为它导致了 AccessViolationException,用户必须通过调用 Free() 方法来释放。
  • 10-03-16 版本 0.14
    • 修复:FileSystemInfoEx 现在存储 PIDL/Rel 的副本,在调用属性时返回其副本(以避免 AccessViolation)。
    • 修复:FileSystemInfoEx 在构造时记录 PIDL,因为某些路径无法解析(例如,EntireNetwork)。
    • 添加:允许文件夹列表,以便列出非文件祖先目录(例如,回收站)。
  • 10-03-18 版本 0.15
    • 修复:在几个位置未释放 ShellFolder/PIDL。
    • (请注意,PIDL/ShellFolder/Store 不再存储在 FileSystemInfoEx 中 => 用户必须自行释放。)
  • 10-03-19 版本 0.16
    • 添加:IShellFolder2 接口和 ShellFolder2 类。
    • 添加:ExtraPropertiesProvider,它可以列出额外的文件属性/列表列(例如,ExtraPropertiesProvider.GetProperty(file, ref ImageSummaryInformation.BitDepth);)。
    • 修复:getRelPIDL() 在使用字符串构造的 File/DirInfoEx 中无法返回正确的值。(尝试返回一个已释放的指针。)
    • 修复:在两个位置未释放 ShellFolder
  • 2010-04-26 版本 0.17
    • DirectoryInfoEx 中添加了 this 运算符。
    • BeforePopup 中添加了 DefaultItem DefaultCommand
    • 添加了 WorkSpawner,它可以生成 ListWorkCopyWorkMoveWork DeleteWork 来执行响应式的多线程操作。
    • 修复了一些 XP 系统无法创建共享目录的问题。(由 cwharmon)
    • 删除了 DirectoryInfoEx 文件/目录的列表缓存(_cachedFileList),因为它在文件过多时会变慢(以及旧的 EnumDirs/EnumFiles 实现)。
    • 添加了 DirectoryInfoEx/DirectoryEx.EnumerateFiles/EnumerateDirectories/EnumerateFileSystemInfos() 方法,其功能类似于 .Net4 中的方法(添加了 CancelDelegate 以使其可取消)。
    • 添加了 FileEx.ReadLines/ReadAllLines() 方法。
    • 添加了 IOTools.CopyFile() 方法,支持取消。
    • 添加了 FileSystemInfoEx.FromString() 方法。
  • 2010-05-25 版本 0.18
    • 现在可以使用 WPF 文件浏览器用户控件(DirectoryTree FileList)。
    • 修复了 DirectoryInfoEx.EnumerateDirectories 在列出网络目录时返回文件的错误。
    • 添加了 ExComparer 类,它允许对 FileSystemInfoEx 条目数组进行排序。
    • 修复了 DriveInfoEx 返回错误的 TotalSizeDriveInfoEx 构造函数现在接受完整的驱动器名称("C" 和 "C:\" 都接受)。
    • (已移除)添加了一个检查以忽略未枚举的项,这样 DirectoryInfoEx.EnumerateDirectories 就不会返回某些系统目录(例如 C:\MSOCache)。
    • 添加了 IOTools.GetRelativePath。记录了 IOTools 的静态方法。
    • 添加了 IOTools.ShellFolderToCSIDL() 和 CSIDLToShellFolder() 静态方法。
    • 添加了 DirectoryInfoEx 的构造函数,它支持 Environment.ShellFolder
    • 修复了 DirectoryInfoEx.EnumerateFiles 忽略 SearchPattern 的问题。
    • 修复了在某些情况下上下文菜单消失的问题。(由 cwharmon)
    • 更新了 DirectoryInfoEx/FileInfoEx 列表代码以提高速度。
    • 为所有 Work、WorkBase 添加了进度对话框。IsProgressDialogEnabled ProgressDialog
  • 2010-07-17 版本 0.19
    • 添加了 FileTypeInfoProvider
    • 修复了获取 C:\{User}\Desktop 的存储时出现的 ArgumentException
  • 2010-08-22 版本 0.20
    • 修复了 ShellProgressDialog 关闭后仍然运行的问题。
    • ExtraPropertiesProvider 中添加了 LinkSummaryInformation
  • 2010-11-04 版本 0.21
    • CustomMenuStructure 类进行了小更新。
    • 修复了 FileSystemInfoEx.getRelativePIDL() 和 getParentPIDL() 返回的 relPIDL 不是克隆,如果尝试释放将导致崩溃。(例如,在 DirectoryEx.Exists() 中)。
  • 2011-03-01 版本 0.22
    • 添加了 ImageExtractor,它使用 IExtractImage 来生成缩略图。
    • 添加了 PreviewHelperPreviewControl,它们是 IPreviewHandler
    • 修复了 Library.ms 目录下的目录的非法 PIDL。
  • 2013-12-16 版本 0.24
    • 将 FileSystemInfoEx.PIDL/PidlRel 的访问权限更改为扩展方法(RequestPIDL/RelPIDL)。
    • 修复了列表时的内存泄漏。
  • 2013-12-24 版本 0.25
    • 修复了 OpenWithInfo.GetExecutablePath()
  • 2014-11-10 版本 1.0.26
    • 修复了与 x64 模式相关的两个崩溃。
    • 添加了 DirectoryInfoEx.ShellFolderType (Environment.SpecialFolder)。
  • 2014-11-19 版本 1.0.27
    • 添加了 DirectoryInfoEx.GetFileSystemInfosAsync()、GetDirectoriesAsync() 和 GetFilesAsync() 异步方法。
    • 添加了 KnownFolder 支持请参阅 MSDN)。
© . All rights reserved.