.NET相关问题 : Windows Vista中的IFileOperation

问:我有许多文件操作希望作为批处理执行,并且希望这些操作能使用标准的 Windows® 进度 UI 。我知道可以使用 System.IO 命名空间中的类逐一执行所有的文件操作,但是随后还需要创建自己的进 度 UI,这就意味着我要完成的工作比想象中要繁重得多。我注意到 Windows Vista® 中包含一个新 的 IFileOperations 接口,但是没有看到任何示例介绍如何从托管代码使用该接口。它是如何实现的?

问:我有许多文件操作希望作为批处理执行,并且希望这些操作能使用标准的 Windows® 进 度 UI。我知道可以使用 System.IO 命名空间中的类逐一执行所有的文件操作,但是随后还需要创建自己 的进度 UI,这就意味着我要完成的工作比想象中要繁重得多。我注意到 Windows Vista® 中包含一 个新的 IFileOperations 接口,但是没有看到任何示例介绍如何从托管代码使用该接口。它是如何实现 的?

答:Windows Vista 实际上包括一个新的复制引擎,完全支持您想完成的工作。不过,可能 之前已有的功能可以满足您的需要。例如,如果想复制、移动、重命名或删除单个文件或目录,可以利用 SHFileOperation(由 shell32.dll 提供),它已经由 Visual Basic® 运行库封装。如果使用的是 Visual Basic 2005,那么只需使用 My 命名空间中的功能即可,例如:

答:Windows Vista 实际 上包括一个新的复制引擎,完全支持您想完成的工作。不过,可能之前已有的功能可以满足您的需要。例 如,如果想复制、移动、重命名或删除单个文件或目录,可以利用 SHFileOperation(由 shell32.dll 提供),它已经由 Visual Basic® 运行库封装。如果使用的是 Visual Basic 2005,那么只需使用 My 命名空间中的功能即可,例如:

My.Computer.FileSystem.CopyDirectory(<br />  

sourcePath, destinationPath, UIOption.AllDialogs)

在 C# 中完成相同的任务则还需要多做 一点工作,即添加对 Microsoft.VisualBasic.dll 的引用(从 Microsoft® .NET Framework 安装目 录),并使用如下代码:

using 

Microsoft.VisualBasic.FileIO;<br />...<br />FileSystem.CopyDirectory(<br />  sourcePath, 

destinationPath, UIOption.AllDialogs);

运行后会产生进度 UI,该 UI 与从 Windows 资源 管理器执行相同的文件操作时所看到的相同。事实上,在 Windows Vista 上运行时,会自动获得新的 Window Vista 进度 UI,如图 1 所示。

图 1 Windows Vista 进 度对话框

但是,SHFileOperation 和 .NET 所提供的包装只能对一个文件系统对象(可以是目录 )进行操作,每次调用也只能进行一项操作(复制、移动、重命名或删除),而不能在同一操作内混合搭 配或操作不同的文件。这正是 IFileOperation 发挥作用的地方。IFileOperation 是 SHFileOperation 的继承者,提供了一系列的新功能,其中包括批处理大量操作的能力,即可以混合执行复制、重命名、移 动、删除,甚至是创建新项目等操作,并且它还可以对任何文件集执行操作,而不仅仅局限于同一目录。 这些只是 IFileOperation 提供的很酷的功能中的一小部分,要想更深入了解,请参阅 shellrevealed.com/blogs/shellblog/archive/2007/04/16/IFileOperation-_1320_-Part-1_3A00_- Introduction.aspx。

遗憾的是,如您所说的那样,在撰写本文时,Microsoft 尚未提供任何托管 包装以便从托管代码公开此功能。因此,我自己构建了一个。该代码可从《MSDN® 杂志》网站下载。 下面我将介绍如何使用及实现该代码,作为后半部分回答。请注意,我的包装并不打算公开 IFileOperation 的全部功能,也不是要完全抽象出所有底层数据并提供遵循所有标准 .NET 设计指南的 完美 .NET 包装;其目的仅仅是简单地公开 IFileOperation 的功能,以便您立即在以 Windows Vista 为目标的应用程序中开始运行基本功能。

我们先从 IFileOperation 接口本身开始吧。我已在图 2 中显示了针对该接口的托管定义(这基于 Windows SDK 中 ShObjIdl.idl 文件的 IDL)。您可以利用 这种接口设计安排要执行的所有操作,准备就绪后,指示该接口执行全部操作。这时,它就会开始执行, 如果它发现执行全部操作要花费相当长的时间时,会显示标准的 Windows Vista 进度对话框,其中包括 所有它正在执行的操作的说明。要安排操作,可以使用 IFileOperation 方法 CopyItem、MoveItem、 RenameItem、DeleteItem 和 NewItem。(该接口还公开这些方法的复数形式,但是由于它们的行为根据 需要通过单数形式即可模拟,并且复数形式从托管代码较难使用,我在此就不作进一步讨论了。)每种方 法只是表达了您希望操作发生,而并不会立刻实际执行操作。所有操作都已安排妥当后,就可使用 PerformOperations 方法来运行之前安排的所有操作。在调用 PerformOperations 之前可通过 IFileOperation 上的多种方法(例如 SetOwnerWindow 和 SetOperationFlags)配置有关如何执行操作 的细节。IFileOperation 还支持一个很不错的回调通知系统,使用该系统可以注册 IFileOperationProgressSink 并为所有类型的事件接收回调,包括操作前和操作后通知、进度更新等。 可以使用 IFileOperation 的 Advise 方法注册接收器(完成时使用 Unadvise 取消注册),这会让此接 收器能用于执行的所有操作,或者也可以使用每一单数操作方法的最后一个参数为单个操作注册接收器。 IFileOperationProgressSink 的托管定义如图 3 所示。(这同样基于 Windows SDK 中的 ShObjIdl.idl 文件。)

Figure3IFileOperationProgressSink

[ComImport]
[Guid("04b0f1a7-9490-44bc-96e1-4296a31252e2")]
[InterfaceType (ComInterfaceType.InterfaceIsIUnknown)]
public interface IFileOperationProgressSink
{
  void StartOperations();
  void FinishOperations(uint hrResult);
  void PreRenameItem(uint dwFlags, IShellItem psiItem,
    [MarshalAs(UnmanagedType.LPWStr)] string pszNewName);
  void PostRenameItem(uint dwFlags, IShellItem psiItem,
     [MarshalAs(UnmanagedType.LPWStr)] string pszNewName,
    uint hrRename, IShellItem psiNewlyCreated);
  void PreMoveItem(uint dwFlags, IShellItem psiItem, IShellItem
     psiDestinationFolder,
    [MarshalAs(UnmanagedType.LPWStr)] string pszNewName);
  void PostMoveItem(uint dwFlags, IShellItem psiItem,
     IShellItem psiDestinationFolder,
    [MarshalAs(UnmanagedType.LPWStr)] string pszNewName,
    uint hrMove, IShellItem psiNewlyCreated);
  void PreCopyItem (uint dwFlags, IShellItem psiItem,
    IShellItem psiDestinationFolder,
     [MarshalAs(UnmanagedType.LPWStr)] string pszNewName);
  void PostCopyItem(uint dwFlags, IShellItem psiItem,
    IShellItem psiDestinationFolder,
    [MarshalAs (UnmanagedType.LPWStr)] string pszNewName,
    uint hrCopy, IShellItem psiNewlyCreated);
  void PreDeleteItem(uint dwFlags, IShellItem psiItem);
  void PostDeleteItem(uint dwFlags, IShellItem psiItem, uint hrDelete,
    IShellItem psiNewlyCreated);
  void PreNewItem(uint dwFlags, IShellItem psiDestinationFolder,
    [MarshalAs(UnmanagedType.LPWStr)] string pszNewName);
  void PostNewItem(uint dwFlags, IShellItem psiDestinationFolder,
    [MarshalAs(UnmanagedType.LPWStr)] string pszNewName,
    [MarshalAs(UnmanagedType.LPWStr)] string pszTemplateName,
    uint dwFileAttributes, uint hrNew, IShellItem psiNewItem);
  void UpdateProgress(uint iWorkTotal, uint iWorkSoFar);
  void ResetTimer();
  void PauseTimer();
  void ResumeTimer();
}

Figure2 IFileOperation

[ComImport]
[Guid("947aab5f-0a5c-4c13-b4d6-4bf7836fc9f8")]
[InterfaceType (ComInterfaceType.InterfaceIsIUnknown)]
internal interface IFileOperation
{
   uint Advise(IFileOperationProgressSink pfops);
  void Unadvise(uint dwCookie);
   void SetOperationFlags(FileOperationFlags dwOperationFlags);
  void SetProgressMessage (
    [MarshalAs(UnmanagedType.LPWStr)] string pszMessage);
  void SetProgressDialog(
    [MarshalAs(UnmanagedType.Interface)] object popd);
  void SetProperties(
    [MarshalAs(UnmanagedType.Interface)] object pproparray);
   void SetOwnerWindow(uint hwndParent);
  void ApplyPropertiesToItem(IShellItem psiItem);
  void ApplyPropertiesToItems(
    [MarshalAs (UnmanagedType.Interface)] object punkItems);
  void RenameItem(IShellItem psiItem,
    [MarshalAs(UnmanagedType.LPWStr)] string pszNewName,
     IFileOperationProgressSink pfopsItem);
  void RenameItems(
    [MarshalAs (UnmanagedType.Interface)] object pUnkItems,
    [MarshalAs(UnmanagedType.LPWStr)] string pszNewName);
  void MoveItem(
    IShellItem psiItem,
     IShellItem psiDestinationFolder,
    [MarshalAs(UnmanagedType.LPWStr)] string pszNewName,
    IFileOperationProgressSink pfopsItem);
  void MoveItems(
     [MarshalAs(UnmanagedType.Interface)] object punkItems,
    IShellItem psiDestinationFolder);
  void CopyItem(
    IShellItem psiItem,
     IShellItem psiDestinationFolder,
    [MarshalAs(UnmanagedType.LPWStr)] string pszCopyName,
    IFileOperationProgressSink pfopsItem);
  void CopyItems(
     [MarshalAs(UnmanagedType.Interface)] object punkItems,
    IShellItem psiDestinationFolder);
  void DeleteItem(
    IShellItem psiItem,
     IFileOperationProgressSink pfopsItem);
  void DeleteItems(
    [MarshalAs (UnmanagedType.Interface)] object punkItems);
  uint NewItem(
    IShellItem psiDestinationFolder,
    FileAttributes dwFileAttributes,
    [MarshalAs (UnmanagedType.LPWStr)] string pszName,
    [MarshalAs(UnmanagedType.LPWStr)] string pszTemplateName,
    IFileOperationProgressSink pfopsItem);
  void PerformOperations();
  [return: MarshalAs(UnmanagedType.Bool)]
  bool GetAnyOperationsAborted();
}

在深入讨论如何直接从托管代码使用这些接口之前,我们先看一下在它们周围实现的包装。我的 FileOperation 类的概要如图 4 所示。作为使用此类的示例,让我们假设有一个 C:\test 目录,其中包 含两个文件和一个目录:test1.txt、test2.txt 和 SampleDir。我想将第一个文件复制到 copy.text, 删除第二个文件,然后将此目录移至 NewDir;我可以使用以下代码实现这个目标:

Figure 4 托管 FileOperation 包 装

class FileOperation : IDisposable
{
  public FileOperation() : this (null);
  public FileOperation(FileOperationProgressSink callbackSink) :
     this(callbackSink, null);
  public FileOperation(
    FileOperationProgressSink callbackSink, IWin32Window owner);
  public void CopyItem(
    string source, string destination, string newName);
  public void MoveItem(
    string source, string destination, string newName);
  public void RenameItem(string source, string newName);
  public void DeleteItem(string source);
  public void NewItem(
     string folderName, string name, FileAttributes attrs);
  public void PerformOperations();
  public void Dispose();
}

using(FileOperation fileOp = new FileOperation())
{
fileOp.CopyItem(@"C:\test\test1.txt", @"C:\test", @"copy.txt");
fileOp.DeleteItem(@"C:\test\test2.txt");
fileOp.MoveItem(@"C:\test\SampleDir", @"C:\test", @"NewDir");
fileOp.PerformOperations();
}

如果我想接收各个操作的回调通知,可以通过 FileOperation 的构造函数注册一个进度 接收器。(此 处使用的 FileOperationProgressSink 类是我所提供的 IFileOperationProgressSink 的默认实现,可 追踪所有事件,稍后我会对此作详细说明。)

using(FileOperation fileOp =
  new FileOperation(new FileOperationProgressSink()))
{
  ...
}

如果这是在 Windows 窗体应用程序上运行,并且我想将进度窗口与我当前的窗体相关联,则可以使用 如下代码:

 

Form. form. = this;
using(FileOperation fileOp = new FileOperation(null, form)) 
{
  ...
}

很简单,是吧?让我们看看这是如何实现的。IFileOperation 是一个 COM 接口,可通过 CLSID_FileOperation 标识的 COM 组件在 Windows Vista 中实现,我们可以使用如下代码创建一个实例 :

private static readonly Guid CLSID_FileOperation =
  new Guid("3ad05575-8857- 4850-9277-11b85bdb8e09");
private static readonly Type FileOperationType =
   Type.GetTypeFromCLSID(CLSID_FileOperation);
public FileOperation(
   FileOperationProgressSink callbackSink, IWin32Window owner)
{
  _fileOperation = (IFileOperation)
    Activator.CreateInstance(FileOperationType);
  ...
}

创建的 IFileOperation 存储在 _fileOperation 成员变量中,然后就可用于类中的所有操作。例如 ,CopyItem 方法的实现如下所示:

public void CopyItem(string source, string destination, string newName)
{
   ThrowIfDisposed();
  using (ComReleaser sourceItem =
     CreateShellItem(source))
  using (ComReleaser destinationItem =
     CreateShellItem(destination))
  {
    _fileOperation.CopyItem(
       sourceItem.Item, destinationItem.Item, newName, null);
  }
}

先暂时忽略 ThrowIfDisposed 和 ComReleaser,CopyItem 首先会为源路径创建 IShellItem 实例, 为目标文件夹创建 IShellItem 实例,然后将它们(连同项目的新名称)传递到 IFileOperation 的 CopyItem 方法。FileOperation 上的所有操作方法都是通过上述方法实现,PerformOperations 只是委 派给底层 IFileOperation 上的相同方法:

public void PerformOperations()
{
  ThrowIfDisposed();
   _fileOperation.PerformOperations();
}

CopyItem 实现中显示的 ComReleaser 类是一个小帮助器类,它可以让我们更轻松地使用 Marshal.FinalReleaseComObject 来处理 COM 对象(这些对象最终都会被垃圾收集器清理,但是鉴于它 们的创建频率,我选择的办法是确保不再使用它们时即将其处理掉)。类型的构建函数接受 COM 对象, 并将其缓存到私有成员变量中;随后其 Dispose 方法会释放该对象。它还会公开 Item 属性,该属性会 返回对象,这使得可以在上述类似的情形中轻松使用它。ComReleaser 如图 5 所示。

Figure 5 用于释放 COM 对象的帮助器类

class ComReleaser : IDisposable where T : class
{
  private T _obj;
  public ComReleaser(T obj)
  {
    if (obj == null) throw new ArgumentNullException("obj");
    if (!obj.GetType().IsCOMObject)
       throw new ArgumentOutOfRangeException("obj");
    _obj = obj;
  }
  public T Item { get { return _obj; } }
  public void Dispose()
  {
    if (_obj != null)
    {
      Marshal.FinalReleaseComObject(_obj);
      _obj = null;
    }
  }
}

为了获得给定文件或目录路径的 ComReleaser,我利用了在 Windows Vista 中从 shell32.dll 导出的 SHCreateItemFromParsingName 函数。您可以为此函数提供文件系统对象的路径, 然后让其返回相应的 IShellItem;之后我的 CreateShellItem 方法只需使用 ComReleaser 包装此 IShellItem 即可:

private static ComReleaser CreateShellItem(string path)
{
  return new ComReleaser((IShellItem)
     SHCreateItemFromParsingName(path, null, ref _shellItemGuid));
}
[DllImport ("shell32.dll", SetLastError=true,
      CharSet=CharSet.Unicode, PreserveSig=false)]
[return: MarshalAs(UnmanagedType.Interface)]
private static extern object SHCreateItemFromParsingName(
  [MarshalAs(UnmanagedType.LPWStr)] string pszPath,
  IBindCtx pbc, ref Guid riid);

现在所有剩下要做的就是 FileOperationProgressSink 实现。FileOperationProgressSink 类实现图 3 中显示的 IFileOperationProgressSink 接口:

public class FileOperationProgressSink : IFileOperationProgressSink
{
  ...
}

它的实现方法是为接口的每一方法提供虚拟方法实现。这样,如果要提供自定义行为以响应事件,只 需从 FileOperationProgressSink 派生即可。例如,PreRenameItem 是在重命名项目之前调用的方法:

 

public virtual void PreRenameItem(
  uint dwFlags, IShellItem psiItem, string pszNewName)
{
#if DEBUG
  string message = string.Format("Renaming {0} to {1}",
    item.GetDisplayName(SIGDN.SIGDN_NORMALDISPLAY), pszNewName);
   Debug.WriteLine(message);
#endif
}

但是,也可以在自己的进度接收器中以任何选择的方式覆盖它:

public class MyProgressSink : FileOperationProgressSink
{
  public virtual void PreRenameItem(
    uint dwFlags, IShellItem psiItem, string pszNewName)
   {
    Console.WriteLine("Goodbye, {0}", item.GetDisplayName(0));
  }
}

然后,当实例化 FileOperation 类时,只需提供接收器实例:

using(FileOperation fileOp = new FileOperation(new MyProgressSink()))
{
   ...
}

总而言之,Windows Vista 团队在 IFileOperation API 的设计方面做得相当出色,使得从托管代码 使用它时非常简便。但是请注意,我在此处未介绍到的功能还有许许多多。例如,Windows Vista 中的 IFileOperation 不仅支持每操作进度接收器(我未介绍),而且还支持通过多次调用 IFileOperation::Advise(每个要注册的接收器调用一次),为所有操作提供多个接收器。此外, IFileOperation 还支持许多操作标记,用于控制目录递归、显示哪些对话框以及如何显示、如何处理冲 突等,这一点我也未提及。它还公开了我没有为其提供包装的其他一些操作,例如与修改项目属性相关的 操作(通过 SetProperties、ApplyPropertiesToItem 和 ApplyPropertiesToItems 方法)。此外, IFileOperation 还提供与所执行的每一操作相关的详细错误信息。如果您需要获得任何这些功能,最简 单的办法就是从《MSDN 杂志》网站下载我所提供的代码,并根据实际需要进行修改。

代码下载

猜你喜欢

转载自blog.csdn.net/bruce135lee/article/details/80916426