2008-10-09 60 views
28

Tôi đã tạo một tiện ích sao chép trong C# (.NET 2.0 Framework) sao chép tệp, thư mục và thư mục phụ đệ quy, vv Chương trình có giao diện cho thấy tệp hiện tại đang được sao chép, số tệp hiện tại (chuỗi), tổng số tệp sẽ được sao chép và phần trăm đã hoàn thành cho các thao tác sao chép. Ngoài ra còn có một thanh tiến trình, dựa trên tập tin/tổng số tập tin hiện tại.Tôi có thể hiển thị tiến trình sao chép tệp bằng FileInfo.CopyTo() trong .NET không?

Sự cố của tôi liên quan đến việc sao chép các tệp lớn. Tôi đã không thể tìm thấy một cách để chỉ ra sự tiến bộ tổng số bản sao của một tập tin lớn (sử dụng cấu trúc lớp học hiện tại của tôi mà sử dụng phương pháp FileInfo.CopyTo). Cách giải quyết khác là tôi đã tách các hoạt động sao chép tệp và hiển thị GUI thành các chuỗi của riêng chúng và thiết lập một gợi ý trực quan để cho thấy rằng công việc đang được thực hiện. Ít nhất người dùng biết rằng chương trình không bị đóng băng và vẫn đang sao chép tệp. Sẽ tốt hơn nếu có thể hiển thị tiến độ dựa trên tổng số byte hoặc có một số loại sự kiện phát sinh từ phương thức FileInfo.CopyTo cho biết tổng số byte được sao chép từ tệp hiện tại.

Tôi biết thuộc tính FileInfo.Length, vì vậy tôi chắc chắn có một cách MacGuyver sự kiện của riêng tôi dựa trên điều này và có một trình xử lý bên GUI về những thứ đang đọc các bản cập nhật (có thể dựa trên kiểm tra thuộc tính FileInfo.Length của đối tượng đích bằng cách sử dụng một số loại timer?).

Có ai biết cách để làm điều này mà tôi đang xem. Nếu tôi có thể tránh nó, tôi không muốn viết lại lớp của tôi để sao chép byte thông qua một dòng và theo dõi nó theo cách đó (mặc dù tôi đang nghĩ rằng tôi có thể bị mắc kẹt với đi tuyến đường đó).

cảm ơn trước

PS - Tôi đang mắc kẹt với .NET framework 2.0 trở lên, vì vậy bất kỳ giải pháp mà yêu cầu tính năng có sẵn trong> = 3,0 chỉ không phải là một lựa chọn cho tôi.

PPS - Tôi mở cho các giải pháp trong bất kỳ ngôn ngữ .NET nào, không chỉ C#.

+0

bất kỳ mã nguồn mẫu đầy đủ nào? – Kiquenet

Trả lời

33

FileInfo.CopyTo về cơ bản là một trình bao bọc xung quanh lệnh gọi Win32 API "CopyFile" trong kernel32.dll. Phương thức này không hỗ trợ gọi lại tiến trình.

Tuy nhiên, phương pháp CopyFileEx không, và bạn có thể viết wrapper NET riêng của bạn xung quanh nó trong một vài phút, giống như nó được mô tả ở đây: http://www.pinvoke.net/default.aspx/kernel32.CopyFileEx

+0

Cảm ơn Gaspar. Tùy chọn này trông giống như một cách có thể để giải quyết vấn đề. Tôi sẽ xem xét nó nhiều hơn một chút. –

+1

Đối với giá trị của nó, tôi đã sử dụng thành công phương pháp này. Trong thực tế, tôi khá chắc chắn tôi đã sao chép chính xác mã đó từ pinvoke.net. agentidle bạn có thể làm cho một lớp nhỏ đẹp để bọc nó thay vì đối phó với tất cả các tham số. –

6

Đối với tình yêu của Thiên Chúa không thực hiện tập tin riêng của bạn sao chép bằng cách sử dụng suối! Cuộc gọi API Win32 CopyFile mà Gaspar đề cập có thể tận dụng lợi thế của ví dụ: DMA, trong khi tôi đặt cược đô la cho bánh rán rằng mã Will viết sẽ không đủ "thông minh" để làm điều đó.

CopyFileEx sẽ xử lý đúng hoặc bạn có thể triển khai một BackgroundWorker theo dõi kích thước ngày càng tăng của tệp đích và cập nhật thanh tiến trình bằng thông tin đó. Phương pháp thứ hai tiết kiệm cho bạn một PInvoke, nhưng trước đây có lẽ là một chút sạch hơn trong thời gian dài.

+0

Xem tệp đích bằng cách sử dụng đối tượng FileInfo và kiểm tra độ dài là một tùy chọn khác mà tôi đã xem xét. Tôi đồng ý mặc dù sử dụng phương pháp CopyFileEx có lẽ là cách tốt nhất để đi. –

5

Đối với những thứ này tôi đã rơi trở lại Shell32 (hoặc là ShellUI? Tôi không biết nữa). Điều này mang lại cho bạn một hộp thoại Windows bản địa mà người dùng được sử dụng để xem các hoạt động sao chép. Tôi đoán nó sẽ thay thế hộp thoại đã tồn tại của bạn vì vậy nó có thể không phải là câu trả lời đúng cho bạn, nhưng nó rất hữu ích để nhớ cho những "trong một pinch" kịch bản.

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

Có, bạn phải tham khảo hội đồng Microsoft.VisualBasic. Tôi đã phát triển thành love hội đồng này.

+0

Lý do duy nhất tôi không thực hiện tuyến đường này là tôi cần đảm bảo rằng người dùng không thể hủy thao tác sao chép. –

23

Tôi cũng đã sử dụng triển khai được cung cấp trong marked answer. Tuy nhiên, tôi đã tạo một trình bao bọc để cung cấp một API đẹp hơn để sử dụng từ .NET.

Cách sử dụng:

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

Thực hiện

/// <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

Đó là thực sự khá ngọt ngào ... –

+0

Nó là tốt đẹp. Tôi thực sự đã không kết thúc bằng cách sử dụng nó như tôi thấy x100 hiệu suất tốt hơn bằng cách sử dụng 'WebClient' để tải về các tập tin async. – Dennis

+0

@Dennis xem bài đăng của tôi ở đây nếu bạn có thời gian (sử dụng mã này) http://stackoverflow.com/questions/10354610/copyfileex-the-parameter-is-invalid-error – Mansfield

3

Nhờ @Gasper và @Dennis để chỉ ra phương pháp CopyFileEx. tôi đã mở rộng dennis câu trả lời với hủy bỏ bản sao

/// <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 

} 

Các khách hàng có thể tạo một đối tượng của lớp Xcopy và gọi copy/hủy bỏ phương pháp.

+0

CloseHandle (hDestinationFile) không nên được thực hiện vì hệ điều hành cũng sẽ cố gắng đóng xử lý và trả lại một ngoại lệ khi không thành công. Nên thực hiện các OnCompleted (CopyCompleteType.Succeeded ... bởi CopyInternal() – Mike

9

Tôi biết tôi hơi muộn với bữa tiệc, nhưng tôi đã thực hiện một trình bao bọc cho CopyFileEx trả về một Task và chấp nhận CancellationTokenIProgress<double>. Thật không may nó sẽ không hoạt động trong khuôn khổ .NET 2.0, nhưng đối với bất kỳ ai sử dụng 4.5, điều này cho phép bạn sử dụng từ khóa 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 
} 
+0

Tôi hỏi câu hỏi 6 năm trước, do đó, một câu trả lời cập nhật có liên quan cho thời gian chắc chắn là chào đón. –

Các vấn đề liên quan