2008-10-09 42 views
28

我在C#(.NET 2.0框架)中创建了一个拷贝文件,目录和递归子目录等的拷贝实用程序。该程序有一个GUI,显示当前正在拷贝的文件,当前文件编号(序列),要复制的文件总数和完成复制操作的百分比。还有一个进度条,它基于当前文件/总文件。可以使用FileInfo.CopyTo()在.NET中显示文件复制进度吗?

我的问题与复制大文件有关。我一直无法找到一种方式来指示一个大文件的整个复制进度(使用我当前使用的FileInfo.CopyTo方法的类结构)。作为一种解决方法,我已将文件复制操作和GUI显示分离到各自的线程,并设置了一个可视提示以显示正在完成工作。至少用户知道该程序没有被冻结,并且仍在复制文件。

如果能够根据字节总数显示进度或者从FileInfo.CopyTo方法触发某种类型的事件,该方法指示从当前文件复制的字节总数将会更好。

我知道FileInfo.Length属性,所以我肯定有一种方法MacGuyver我自己的事件是基于此,并在读取更新的东西的GUI一侧有一个处理程序(可能基于使用某种类型的计时器检查目标对象的FileInfo.Length属性?)。

有没有人知道一种方法来做到这一点,我俯瞰。如果我可以避免它,我宁愿不重写我的课程通过流复制字节并跟踪它(尽管我想我可能会坚持走那条路线)。

在此先感谢

PS - 我卡与.NET 2.0框架现在,这样就要求在> = 3.0的可用功能的任何解决方案仅是不是我的选择。

PPS - 我对任何.NET语言版本的解决方案都很开放,不仅仅是c#。

+0

任何完整的示例源代码? – Kiquenet 2011-09-24 16:18:12

回答

33

FileInfo.CopyTo基本上是kernel32.dll中的Win32 API调用“CopyFile”的一个包装。此方法不支持进度回调。

但是,CopyFileEx方法不会,你可以写身边你自己的.NET包装几分钟,就像是这里描述: http://www.pinvoke.net/default.aspx/kernel32.CopyFileEx

+0

谢谢加斯帕尔。这个选项看起来像解决这个问题的一种可能的方法。我会再看一看。 – 2008-10-09 15:40:15

+1

为了什么值得我成功地使用这种方法。事实上,我很确定我从pinvoke.net那里抄袭了这些非常有用的代码。 agentidle你可以做一个很好的小班来包装它,而不是处理所有的参数。 – 2008-10-09 15:44:51

6

为了上帝的爱没有实现自己的文件使用流复制! Gaspar提到的Win32 CopyFile API调用可以充分利用DMA,但我敢打赌,甜甜圈代码将写出的代码不会“聪明”到足以做到这一点。

CopyFileEx会正确对待你,或者你可以实现一个BackgroundWorker来监视目标文件的大小,并使用该信息更新进度条。后一种方法可以为您节省一笔PInvoke,但从长远来看,前者可能会更清洁一些。

+0

使用FileInfo对象观察目标文件并检查长度是我考虑的另一个选项。我同意,使用CopyFileEx方法可能是最好的方法。 – 2008-10-09 18:12:28

5

对于这些类型的东西,我退回到Shell32(或者它是ShellUI?我不知道了)。这给你一个本地Windows对话框,用户习惯看到复制操作。我想它会取代你已经存在的对话框,因此它可能不是你的正确答案,但是记住那些“处于紧要关头”的场景是有用的。

Microsoft.VisualBasic.FileIO.FileSystem.CopyFile(
    srcPath, 
    dstPath, 
    Microsoft.VisualBasic.FileIO.UIOption.AllDialogs,  
    Microsoft.VisualBasic.FileIO.UICancelOption.ThrowException 
); 

是的,您必须引用Microsoft.VisualBasic程序集。我已经成长为love这个组件。

+0

我没有采取这种方式的唯一原因是我需要确保用户无法取消复制操作。 – 2008-10-10 12:35:07

23

我还使用了marked answer中提供的实现。不过,我然后创建了一个包装,以提供一个更好的API来使用.NET。

用法:

XCopy.Copy(networkFile.FullPath, temporaryFilename, true, true, (o, pce) => 
{ 
    worker.ReportProgress(pce.ProgressPercentage, networkFile); 
}); 

实施

/// <summary> 
/// PInvoke wrapper for CopyEx 
/// http://msdn.microsoft.com/en-us/library/windows/desktop/aa363852.aspx 
/// </summary> 
public class XCopy 
{ 
    public static void Copy(string source, string destination, bool overwrite, bool nobuffering) 
    { 
     new XCopy().CopyInternal(source, destination, overwrite, nobuffering, null);    
    } 

    public static void Copy(string source, string destination, bool overwrite, bool nobuffering, EventHandler<ProgressChangedEventArgs> handler) 
    {    
     new XCopy().CopyInternal(source, destination, overwrite, nobuffering, handler);    
    } 

    private event EventHandler Completed; 
    private event EventHandler<ProgressChangedEventArgs> ProgressChanged; 

    private int IsCancelled; 
    private int FilePercentCompleted; 
    private string Source; 
    private string Destination;   

    private XCopy() 
    { 
     IsCancelled = 0; 
    } 

    private void CopyInternal(string source, string destination, bool overwrite, bool nobuffering, EventHandler<ProgressChangedEventArgs> handler) 
    { 
     try 
     { 
      CopyFileFlags copyFileFlags = CopyFileFlags.COPY_FILE_RESTARTABLE; 
      if (!overwrite) 
       copyFileFlags |= CopyFileFlags.COPY_FILE_FAIL_IF_EXISTS; 

      if (nobuffering) 
       copyFileFlags |= CopyFileFlags.COPY_FILE_NO_BUFFERING; 

      Source = source; 
      Destination = destination; 

      if (handler != null) 
       ProgressChanged += handler; 

      bool result = CopyFileEx(Source, Destination, new CopyProgressRoutine(CopyProgressHandler), IntPtr.Zero, ref IsCancelled, copyFileFlags); 
      if (!result) 
       throw new Win32Exception(Marshal.GetLastWin32Error()); 
     } 
     catch (Exception) 
     { 
      if (handler != null) 
       ProgressChanged -= handler; 

      throw; 
     } 
    } 

    private void OnProgressChanged(double percent) 
    { 
     // only raise an event when progress has changed 
     if ((int)percent > FilePercentCompleted) 
     { 
      FilePercentCompleted = (int)percent; 

      var handler = ProgressChanged; 
      if (handler != null) 
       handler(this, new ProgressChangedEventArgs((int)FilePercentCompleted, null)); 
     } 
    } 

    private void OnCompleted() 
    { 
     var handler = Completed; 
     if (handler != null) 
      handler(this, EventArgs.Empty); 
    } 

    #region PInvoke 

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] 
    [return: MarshalAs(UnmanagedType.Bool)] 
    private static extern bool CopyFileEx(string lpExistingFileName, string lpNewFileName, CopyProgressRoutine lpProgressRoutine, IntPtr lpData, ref Int32 pbCancel, CopyFileFlags dwCopyFlags); 

    private delegate CopyProgressResult CopyProgressRoutine(long TotalFileSize, long TotalBytesTransferred, long StreamSize, long StreamBytesTransferred, uint dwStreamNumber, CopyProgressCallbackReason dwCallbackReason, 
                IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData); 

    private enum CopyProgressResult : uint 
    { 
     PROGRESS_CONTINUE = 0, 
     PROGRESS_CANCEL = 1, 
     PROGRESS_STOP = 2, 
     PROGRESS_QUIET = 3 
    } 

    private enum CopyProgressCallbackReason : uint 
    { 
     CALLBACK_CHUNK_FINISHED = 0x00000000, 
     CALLBACK_STREAM_SWITCH = 0x00000001 
    } 

    [Flags] 
    private enum CopyFileFlags : uint 
    { 
     COPY_FILE_FAIL_IF_EXISTS = 0x00000001, 
     COPY_FILE_NO_BUFFERING = 0x00001000, 
     COPY_FILE_RESTARTABLE = 0x00000002, 
     COPY_FILE_OPEN_SOURCE_FOR_WRITE = 0x00000004, 
     COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x00000008 
    } 

    private CopyProgressResult CopyProgressHandler(long total, long transferred, long streamSize, long streamByteTrans, uint dwStreamNumber, 
                CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData) 
    { 
     if (reason == CopyProgressCallbackReason.CALLBACK_CHUNK_FINISHED) 
      OnProgressChanged((transferred/(double)total) * 100.0); 

     if (transferred >= total) 
      OnCompleted(); 

     return CopyProgressResult.PROGRESS_CONTINUE; 
    } 

    #endregion 

} 
+0

这其实很漂亮... – 2011-12-01 13:21:53

+0

很好。我实际上并没有最终使用它,因为我发现使用WebClient来下载文件异步时x100有更好的性能。 – Dennis 2011-12-01 16:40:58

3

感谢@Gasper和@Dennis您指出CopyFileEx方法。 我已经扩展丹尼斯的回答与中止副本

/// <summary> 
    /// Type indicates how the copy gets completed. 
    /// </summary> 
    internal enum CopyCompletedType 
    { 
     Succeeded, 
     Aborted, 
     Exception 
    } 

/// <summary> 
/// Event arguments for file copy 
/// </summary> 
internal class FileCopyEventArgs : EventArgs 
{ 
    /// <summary> 
    /// Constructor 
    /// </summary> 
    /// <param name="type">type of the copy completed type enum</param> 
    /// <param name="exception">exception if any</param> 
    public FileCopyEventArgs(CopyCompletedType type, Exception exception) 
    { 
     Type = type; 
     Exception = exception; 
    } 

    /// <summary> 
    /// Type of the copy completed type 
    /// </summary> 
    public CopyCompletedType Type 
    { 
     get; 
     private set; 

    } 

    /// <summary> 
    /// Exception if any happend during copy. 
    /// </summary> 
    public Exception Exception 
    { 
     get; 
     private set; 
    } 

} 

/// <summary> 
/// PInvoke wrapper for CopyEx 
/// http://msdn.microsoft.com/en-us/library/windows/desktop/aa363852.aspx 
/// </summary> 
internal class XCopy 
{ 

    private int IsCancelled; 
    private int FilePercentCompleted; 

    public XCopy() 
    { 
     IsCancelled = 0; 
    } 

    /// <summary> 
    /// Copies the file asynchronously 
    /// </summary> 
    /// <param name="source">the source path</param> 
    /// <param name="destination">the destination path</param> 
    /// <param name="nobuffering">Bufferig status</param> 
    /// <param name="handler">Event handler to do file copy.</param> 
    public void CopyAsync(string source, string destination, bool nobuffering) 
    { 
     try 
     { 
      //since we needed an async copy .. 
      Action action = new Action(
       () => CopyInternal(source, destination, nobuffering) 
        ); 
      Task task = new Task(action); 
      task.Start(); 
     } 
     catch (AggregateException ex) 
     { 
      //handle the inner exception since exception thrown from task are wrapped in 
      //aggreate exception. 
      OnCompleted(CopyCompletedType.Exception, ex.InnerException); 
     } 
     catch (Exception ex) 
     { 
      OnCompleted(CopyCompletedType.Exception, ex); 
     } 
    } 

    /// <summary> 
    /// Event which will notify the subscribers if the copy gets completed 
    /// There are three scenarios in which completed event will be thrown when 
    /// 1.Copy succeeded 
    /// 2.Copy aborted. 
    /// 3.Any exception occured. 
    /// These information can be obtained from the Event args. 
    /// </summary> 
    public event EventHandler<FileCopyEventArgs> Completed; 
    /// <summary> 
    /// Event which will notify the subscribers if there is any progress change while copying. 
    /// This will indicate the progress percentage in its event args. 
    /// </summary> 
    public event EventHandler<ProgressChangedEventArgs> ProgressChanged; 

    /// <summary> 
    /// Aborts the copy asynchronously and throws Completed event when done. 
    /// User may not want to wait for completed event in case of Abort since 
    /// the event will tell that copy has been aborted. 
    /// </summary> 
    public void AbortCopyAsync() 
    { 
     Trace.WriteLine("Aborting the copy"); 
     //setting this will cancel an operation since we pass the 
     //reference to copyfileex and it will periodically check for this. 
     //otherwise also We can check for iscancelled on onprogresschanged and return 
     //Progress_cancelled . 
     IsCancelled = 1; 

     Action completedEvent = new Action(() => 
      { 
       //wait for some time because we ll not know when IsCancelled is set , at what time windows stops copying. 
       //so after sometime this may become valid . 
       Thread.Sleep(500); 
       //do we need to wait for some time and send completed event. 
       OnCompleted(CopyCompletedType.Aborted); 
       //reset the value , otherwise if we try to copy again since value is 1 , 
       //it thinks that its aborted and wont allow to copy. 
       IsCancelled = 0; 
      }); 

     Task completedTask = new Task(completedEvent); 
     completedTask.Start(); 
    } 


    /// <summary> 
    /// Copies the file using asynchronos task 
    /// </summary> 
    /// <param name="source">the source path</param> 
    /// <param name="destination">the destination path</param> 
    /// <param name="nobuffering">Buffering status</param> 
    /// <param name="handler">Delegate to handle Progress changed</param> 
    private void CopyInternal(string source, string destination, bool nobuffering) 
    { 
     CopyFileFlags copyFileFlags = CopyFileFlags.COPY_FILE_RESTARTABLE; 

     if (nobuffering) 
     { 
      copyFileFlags |= CopyFileFlags.COPY_FILE_NO_BUFFERING; 
     } 

     try 
     { 
      Trace.WriteLine("File copy started with Source: " + source + " and destination: " + destination); 
      //call win32 api. 
      bool result = CopyFileEx(source, destination, new CopyProgressRoutine(CopyProgressHandler), IntPtr.Zero, ref IsCancelled, copyFileFlags); 
      if (!result) 
      { 
       //when ever we get the result as false it means some error occured so get the last win 32 error. 
       throw new Win32Exception(Marshal.GetLastWin32Error()); 
      } 
     } 
     catch (Exception ex) 
     { 
      //the mesage will contain the requested operation was aborted when the file copy 
      //was cancelled. so we explicitly check for that and do a graceful exit 
      if (ex.Message.Contains("aborted")) 
      { 
       Trace.WriteLine("Copy aborted."); 
      } 
      else 
      { 
       OnCompleted(CopyCompletedType.Exception, ex.InnerException); 
      } 
     } 
    } 

    private void OnProgressChanged(double percent) 
    { 
     // only raise an event when progress has changed 
     if ((int)percent > FilePercentCompleted) 
     { 
      FilePercentCompleted = (int)percent; 

      var handler = ProgressChanged; 
      if (handler != null) 
      { 
       handler(this, new ProgressChangedEventArgs((int)FilePercentCompleted, null)); 
      } 
     } 
    } 

    private void OnCompleted(CopyCompletedType type, Exception exception = null) 
    { 
     var handler = Completed; 
     if (handler != null) 
     { 
      handler(this, new FileCopyEventArgs(type, exception)); 
     } 
    } 

    #region PInvoke 

    /// <summary> 
    /// Delegate which will be called by Win32 API for progress change 
    /// </summary> 
    /// <param name="total">the total size</param> 
    /// <param name="transferred">the transferrred size</param> 
    /// <param name="streamSize">size of the stream</param> 
    /// <param name="streamByteTrans"></param> 
    /// <param name="dwStreamNumber">stream number</param> 
    /// <param name="reason">reason for callback</param> 
    /// <param name="hSourceFile">the source file handle</param> 
    /// <param name="hDestinationFile">the destination file handle</param> 
    /// <param name="lpData">data passed by users</param> 
    /// <returns>indicating whether to continue or do somthing else.</returns> 
    private CopyProgressResult CopyProgressHandler(long total, long transferred, long streamSize, long streamByteTrans, uint dwStreamNumber, 
                CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData) 
    { 
     //when a chunk is finished call the progress changed. 
     if (reason == CopyProgressCallbackReason.CALLBACK_CHUNK_FINISHED) 
     { 
      OnProgressChanged((transferred/(double)total) * 100.0); 
     } 

     //transfer completed 
     if (transferred >= total) 
     { 
      if (CloseHandle(hDestinationFile)) 
      { 
       OnCompleted(CopyCompletedType.Succeeded, null); 
      } 
      else 
      { 
       OnCompleted(CopyCompletedType.Exception, 
        new System.IO.IOException("Unable to close the file handle")); 
      } 
     } 

     return CopyProgressResult.PROGRESS_CONTINUE; 
    } 
    [System.Runtime.InteropServices.DllImport("Kernel32")] 
    private extern static Boolean CloseHandle(IntPtr handle); 

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] 
    [return: MarshalAs(UnmanagedType.Bool)] 
    private static extern bool CopyFileEx(string lpExistingFileName, string lpNewFileName, CopyProgressRoutine lpProgressRoutine, IntPtr lpData, ref Int32 pbCancel, CopyFileFlags dwCopyFlags); 

    private delegate CopyProgressResult CopyProgressRoutine(long TotalFileSize, long TotalBytesTransferred, long StreamSize, long StreamBytesTransferred, uint dwStreamNumber, CopyProgressCallbackReason dwCallbackReason, 
                IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData); 

    private enum CopyProgressResult : uint 
    { 
     PROGRESS_CONTINUE = 0, 
     PROGRESS_CANCEL = 1, 
     PROGRESS_STOP = 2, 
     PROGRESS_QUIET = 3 
    } 

    private enum CopyProgressCallbackReason : uint 
    { 
     CALLBACK_CHUNK_FINISHED = 0x00000000, 
     CALLBACK_STREAM_SWITCH = 0x00000001 
    } 

    [Flags] 
    private enum CopyFileFlags : uint 
    { 
     COPY_FILE_FAIL_IF_EXISTS = 0x00000001, 
     COPY_FILE_NO_BUFFERING = 0x00001000, 
     COPY_FILE_RESTARTABLE = 0x00000002, 
     COPY_FILE_OPEN_SOURCE_FOR_WRITE = 0x00000004, 
     COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x00000008 
    } 

    #endregion 

} 

客户可以创建的XCopy类和调用拷贝的对象/终止方法。

9

我知道我有点迟到了,但我为CopyFileEx的包装,它返回一个Task并接受CancellationTokenIProgress<double>。不幸的是,它不能在.NET 2.0框架中工作,但对于使用4.5的人来说,这允许你使用await关键字。

public static class FileEx 
{ 
    public static Task CopyAsync(string sourceFileName, string destFileName) 
    { 
     return CopyAsync(sourceFileName, destFileName, CancellationToken.None); 
    } 

    public static Task CopyAsync(string sourceFileName, string destFileName, CancellationToken token) 
    { 
     return CopyAsync(sourceFileName, destFileName, token, null); 
    } 

    public static Task CopyAsync(string sourceFileName, string destFileName, IProgress<double> progress) 
    { 
     return CopyAsync(sourceFileName, destFileName, CancellationToken.None, progress); 
    } 

    public static Task CopyAsync(string sourceFileName, string destFileName, CancellationToken token, IProgress<double> progress) 
    { 
     int pbCancel = 0; 
     CopyProgressRoutine copyProgressHandler; 
     if (progress != null) 
     { 
      copyProgressHandler = (total, transferred, streamSize, streamByteTrans, dwStreamNumber, reason, hSourceFile, hDestinationFile, lpData) => 
      { 
       progress.Report((double)transferred/total * 100); 
       return CopyProgressResult.PROGRESS_CONTINUE; 
      }; 
     } 
     else 
     { 
      copyProgressHandler = EmptyCopyProgressHandler; 
     } 
     token.ThrowIfCancellationRequested(); 
     var ctr = token.Register(() => pbCancel = 1); 
     var copyTask = Task.Run(() => 
     { 
      try 
      { 
       CopyFileEx(sourceFileName, destFileName, copyProgressHandler, IntPtr.Zero, ref pbCancel, CopyFileFlags.COPY_FILE_RESTARTABLE); 
       token.ThrowIfCancellationRequested(); 
      } 
      finally 
      { 
       ctr.Dispose(); 
      } 
     }, token); 
     return copyTask; 
    } 

    private static CopyProgressResult EmptyCopyProgressHandler(long total, long transferred, long streamSize, long streamByteTrans, uint dwStreamNumber, CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData) 
    { 
     return CopyProgressResult.PROGRESS_CONTINUE; 
    } 

    #region DLL Import 

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] 
    [return: MarshalAs(UnmanagedType.Bool)] 
    private static extern bool CopyFileEx(string lpExistingFileName, string lpNewFileName, 
     CopyProgressRoutine lpProgressRoutine, IntPtr lpData, ref Int32 pbCancel, 
     CopyFileFlags dwCopyFlags); 

    delegate CopyProgressResult CopyProgressRoutine(
     long totalFileSize, 
     long totalBytesTransferred, 
     long streamSize, 
     long streamBytesTransferred, 
     uint dwStreamNumber, 
     CopyProgressCallbackReason dwCallbackReason, 
     IntPtr hSourceFile, 
     IntPtr hDestinationFile, 
     IntPtr lpData); 

    enum CopyProgressResult : uint 
    { 
     PROGRESS_CONTINUE = 0, 
     PROGRESS_CANCEL = 1, 
     PROGRESS_STOP = 2, 
     PROGRESS_QUIET = 3 
    } 

    enum CopyProgressCallbackReason : uint 
    { 
     CALLBACK_CHUNK_FINISHED = 0x00000000, 
     CALLBACK_STREAM_SWITCH = 0x00000001 
    } 

    [Flags] 
    enum CopyFileFlags : uint 
    { 
     COPY_FILE_FAIL_IF_EXISTS = 0x00000001, 
     COPY_FILE_RESTARTABLE = 0x00000002, 
     COPY_FILE_OPEN_SOURCE_FOR_WRITE = 0x00000004, 
     COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x00000008 
    } 

    #endregion 
} 
相关问题