我在c#(.NET 2.0 Framework)中创建了一个复制实用程序,它复制文件,目录和递归子目录等.该程序有一个GUI,显示当前正在复制的文件,当前文件编号(序列),总数要复制的文件数和复制操作的完成百分比.还有一个进度条,它基于当前文件/总文件.
我的问题与复制大文件有关.我一直无法找到一种方法来指示大文件的总复制进度(使用我当前使用FileInfo.CopyTo方法的类结构).作为一种解决方法,我将文件复制操作和GUI显示分离到它们自己的线程,并设置一个视觉提示来显示正在完成的工作.至少用户知道程序没有被冻结并且仍在复制文件.
能够基于总字节数显示进度或者具有从FileInfo.CopyTo方法触发的某种类型的事件会更好,该方法指示从当前文件复制的总字节数.
我知道FileInfo.Length属性,所以我确信MacGuyver有一种基于此的事件,并且在GUI端有一个处理器读取更新的处理程序(可能基于检查FileInfo).使用某种类型的计时器的目标对象的长度属性?).
有谁知道我要忽视的方法.如果我可以避免它,我宁愿不重写我的类来通过流复制字节并以这种方式跟踪它(虽然我想我可能会坚持走那条路).
提前致谢
PS - 我现在一直坚持使用.NET 2.0框架,所以任何需要> = 3.0的功能的解决方案都不适合我.
PPS - 我对任何.NET语言的解决方案都很开放,而不仅仅是c#.
FileInfo.CopyTo基本上是kernel32.dll中Win32 API调用"CopyFile"的包装器.此方法不支持进度回调.
但是,CopyFileEx方法可以,并且您可以在几分钟内编写自己的.NET包装器,如下所述:http: //www.pinvoke.net/default.aspx/kernel32.CopyFileEx
我还使用了标记答案中提供的实现.然而,我创建了一个包装器,以提供从.NET使用的更好的API.
用法:
XCopy.Copy(networkFile.FullPath, temporaryFilename, true, true, (o, pce) => { worker.ReportProgress(pce.ProgressPercentage, networkFile); });
履行
////// PInvoke wrapper for CopyEx /// http://msdn.microsoft.com/en-us/library/windows/desktop/aa363852.aspx /// 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, EventHandlerhandler) { new XCopy().CopyInternal(source, destination, overwrite, nobuffering, handler); } private event EventHandler Completed; private event EventHandler 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 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
,返回a Task
并接受a CancellationToken
和IProgress
.不幸的是,它不适用于.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, IProgressprogress) { return CopyAsync(sourceFileName, destFileName, CancellationToken.None, progress); } public static Task CopyAsync(string sourceFileName, string destFileName, CancellationToken token, IProgress 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 }
对于上帝的爱,不要使用流来实现自己的文件副本!Gaspar提到的Win32 CopyFile API调用能够利用例如DMA,而我敢打赌代码Will写的甜甜圈不会足够聪明地做到这一点.
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程序集.我已经成长为喜欢这个集会.