我可以使用FileInfo.CopyTo()在.NET中显示文件复制进度?
我在c#(.NET 2.0 Framework)中创build了一个复制实用程序,用于复制文件,目录和recursion子目录等。该程序具有一个显示正在复制的当前文件的GUI,当前文件编号(序列),总数要复制的文件以及复制操作完成的百分比。 还有一个进度条,即基于当前文件/总文件。
我的问题与复制大文件有关。 我一直无法find一个方法来指示一个大文件的总复制进度(使用我当前的类结构utilitz FileInfo.CopyTo方法)。 作为一种解决方法,我已经将文件复制操作和GUI显示分离到各自的线程,并设置了一个可视提示来显示正在完成的工作。 至less用户知道程序没有被冻结,并且仍在复制文件。
能够根据字节总数显示进度或者从FileInfo.CopyTo方法触发某种types的事件将会更好,该方法指示从当前文件复制的总字节数。
我知道FileInfo.Length属性,所以我敢肯定,有一种方法MacGuyver我自己的事件是基于这个,并在阅读更新(可能基于检查FileInfo的东西的GUI一侧的处理程序。目标对象的长度属性使用某种types的定时器?)。
有没有人知道一种方法来做到这一点,我俯瞰。 如果我能避免它,我宁愿不重写我的类通过stream复制字节,并跟踪它(虽然我想我可能会坚持走这条路线)。
提前致谢
PS – 我现在坚持使用.NET 2.0框架,所以任何需要> = 3.0以上function的解决scheme都不适合我。
PPS – 我对任何.NET语言的解决scheme都很开放,不仅仅是c#。
FileInfo.CopyTo基本上是kernel32.dll中的Win32 API调用“CopyFile”的一个包装。 此方法不支持进度callback。
但是,CopyFileEx方法可以,你可以在几分钟内编写你自己的.NET包装器,就像这里描述的那样: http : //www.pinvoke.net/default.aspx/kernel32.CopyFileEx
我也使用了标记答案中提供的实现。 不过,我然后创build一个包装,以提供一个更好的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 }
我知道我晚了一点,但我做了一个CopyFileEx
的包装,返回一个Task
并接受CancellationToken
和IProgress<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 }
对于上帝的爱,不要使用stream实现自己的文件复制! Gaspar提到的Win32 CopyFile API调用可以利用例如DMA,而我敢打赌,甜甜圈代码将会写出来的代码不会是“聪明”的。
CopyFileEx会正确对待你,或者你可以实现一个BackgroundWorker来监视目标文件的大小,并使用这些信息更新进度条。 后一种方法可以为您节省PInvoke,但是从长远来看,前者可能会更清洁一些。
对于这些东西,我退回到Shell32(或者是ShellUI?我不知道了)。 这给你一个本地的Windows对话框,用户习惯看到复制操作。 我想它会取代你已经存在的对话框,所以它可能不是你的正确答案,但是记住那些“在一个紧要关头”的场景是有用的。
Microsoft.VisualBasic.FileIO.FileSystem.CopyFile( srcPath, dstPath, Microsoft.VisualBasic.FileIO.UIOption.AllDialogs, Microsoft.VisualBasic.FileIO.UICancelOption.ThrowException );
是的,您必须引用Microsoft.VisualBasic程序集。 我越来越喜欢这个大会了。
感谢@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 }
客户端可以创buildXCopy类的对象并调用复制/中止方法。