31

ファイル、ディレクトリ、再帰的なサブディレクトリなどをコピーするコピー ユーティリティを C# (.NET 2.0 Framework) で作成しました。プログラムには、コピー中の現在のファイル、現在のファイル番号 (シーケンス)、総数を示す GUI があります。コピーするファイルの数と、コピー操作の完了率。現在のファイル/合計ファイルに基づく進行状況バーもあります。

私の問題は、大きなファイルのコピーに関連しています。大きなファイルの全体的なコピーの進行状況を示す方法を見つけることができませんでした (FileInfo.CopyTo メソッドを利用する現在のクラス構造を使用しています)。回避策として、ファイル コピー操作と GUI 表示をそれぞれのスレッドに分離し、作業が行われていることを示す視覚的な合図を設定しました。少なくともユーザーは、プログラムがフリーズしておらず、まだファイルをコピーしていることを認識しています。

合計バイト数に基づいて進行状況を表示したり、現在のファイルからコピーされた合計バイト数を示す FileInfo.CopyTo メソッドから発火するある種のイベントを作成したりできると便利です。

私は FileInfo.Length プロパティを認識しているので、MacGuyver にこれに基づいて独自のイベントを作成し、GUI 側で更新を読み取るハンドラーを持つ方法があると確信しています (おそらく FileInfo.Length のチェックに基づいています)。ある種のタイマーを使用した宛先オブジェクトの長さプロパティ?)。

私が見落としているこれを行う方法を知っている人はいますか。それを避けることができれば、ストリームを介してバイトをコピーしてそのように追跡するようにクラスを書き直したくありません(ただし、そのルートに行き詰まる可能性があると考えています)。

PS - 私は今のところ .NET 2.0 フレームワークに固執しているので、>= 3.0 でのみ利用可能な機能を必要とするソリューションは、私にとって選択肢ではありません。

PPS - 私は、c# だけでなく、さまざまな .NET 言語のソリューションに対してオープンです。

4

7 に答える 7

37

FileInfo.CopyToは、基本的に、kernel32.dllのWin32API呼び出し「CopyFile」のラッパーです。このメソッドは、進行状況のコールバックをサポートしていません。

ただし、CopyFileExメソッドは機能し、 http ://www.pinvoke.net/default.aspx/kernel32.CopyFileExで説明されているように、数分で独自の.NETラッパーを作成できます。

于 2008-10-09T15:34:07.487 に答える
28

マークされた回答で提供されている実装も使用しました。しかし、その後、.NET から使用できるより優れた API を提供するラッパーを作成しました。

使用法:

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

}
于 2011-12-01T13:10:31.880 に答える
12

私はパーティーに少し遅れていることを知ってCopyFileExいますが、 a を返し、 aおよびTaskを受け入れるラッパーを作成しました。残念ながら、.NET 2.0 フレームワークでは機能しませんが、4.5 を使用している場合は、このキーワードを使用できます。CancellationTokenIProgress<double>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
}
于 2014-11-27T22:30:38.417 に答える
9

神への愛のために、ストリームを使用して独自のファイル コピーを実装しないでください。Gaspar が言及した Win32 CopyFile API 呼び出しは、たとえば DMA を利用することができますが、Will が記述したコードがそれを行うのに十分なほど「スマート」ではないことをドーナツに賭けます。

CopyFileEx は正しく処理します。または、ターゲット ファイルのサイズの増大を監視し、その情報を使用してプログレス バーを更新する BackgroundWorker を実装することもできます。後者の方法では PInvoke を節約できますが、長い目で見れば前者の方がおそらく少しクリーンです。

于 2008-10-09T16:52:04.413 に答える
5

このようなことのために、私は Shell32 に戻ってきました (それとも ShellUI ですか? もうわかりません)。これにより、ユーザーがコピー操作で見慣れているネイティブの Windows ダイアログが表示されます。既存のダイアログを置き換えるので、正しい答えではないかもしれませんが、「ピンチ」のシナリオを覚えておくと便利です。

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

はい、Microsoft.VisualBasic アセンブリを参照する必要があります。この集まりがますます好きになりました。

于 2008-10-10T11:08:06.697 に答える