2008-10-09 45 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 
} 
相關問題