36

私はこのコードを使用しました:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
using System.IO;

namespace WindowsApplication1 {
  public partial class Form1 : Form {
    // Class to report progress
    private class UIProgress {
      public UIProgress(string name_, long bytes_, long maxbytes_) {
        name = name_; bytes = bytes_; maxbytes = maxbytes_;
      }
      public string name;
      public long bytes;
      public long maxbytes;
    }
    // Class to report exception {
    private class UIError {
      public UIError(Exception ex, string path_) {
        msg = ex.Message; path = path_; result = DialogResult.Cancel;
      }
      public string msg;
      public string path;
      public DialogResult result;
    }
    private BackgroundWorker mCopier;
    private delegate void ProgressChanged(UIProgress info);
    private delegate void CopyError(UIError err);
    private ProgressChanged OnChange;
    private CopyError OnError;

    public Form1() {
      InitializeComponent();
      mCopier = new BackgroundWorker();
      mCopier.DoWork += Copier_DoWork;
      mCopier.RunWorkerCompleted += Copier_RunWorkerCompleted;
      mCopier.WorkerSupportsCancellation = true;
      OnChange += Copier_ProgressChanged;
      OnError += Copier_Error;
      button1.Click += button1_Click;
      ChangeUI(false);
    }

    private void Copier_DoWork(object sender, DoWorkEventArgs e) {
      // Create list of files to copy
      string[] theExtensions = { "*.jpg", "*.jpeg", "*.bmp", "*.png", "*.gif" };
      List<FileInfo> files = new List<FileInfo>();
      string path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
      DirectoryInfo dir = new DirectoryInfo(path);
      long maxbytes = 0;
      foreach (string ext in theExtensions) {
        FileInfo[] folder = dir.GetFiles(ext, SearchOption.AllDirectories);
        foreach (FileInfo file in folder) {
          if ((file.Attributes & FileAttributes.Directory) != 0) continue;
          files.Add(file);
          maxbytes += file.Length;
        }
      }
      // Copy files
      long bytes = 0;
      foreach (FileInfo file in files) {
        try {
          this.BeginInvoke(OnChange, new object[] { new UIProgress(file.Name, bytes, maxbytes) });
          File.Copy(file.FullName, @"c:\temp\" + file.Name, true);
        }
        catch (Exception ex) {
          UIError err = new UIError(ex, file.FullName); 
          this.Invoke(OnError, new object[] { err });
          if (err.result == DialogResult.Cancel) break;
        }
        bytes += file.Length;
      }
    }
    private void Copier_ProgressChanged(UIProgress info) {
      // Update progress
      progressBar1.Value = (int)(100.0 * info.bytes / info.maxbytes);
      label1.Text = "Copying " + info.name;
    }
    private void Copier_Error(UIError err) {
      // Error handler
      string msg = string.Format("Error copying file {0}\n{1}\nClick OK to continue copying files", err.path, err.msg);
      err.result = MessageBox.Show(msg, "Copy error", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation);
    }
    private void Copier_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
      // Operation completed, update UI
      ChangeUI(false);
    }
    private void ChangeUI(bool docopy) {
      label1.Visible = docopy;
      progressBar1.Visible = docopy;
      button1.Text = docopy ? "Cancel" : "Copy";
      label1.Text = "Starting copy...";
      progressBar1.Value = 0;
    }
    private void button1_Click(object sender, EventArgs e) {
      bool docopy = button1.Text == "Copy";
      ChangeUI(docopy);
      if (docopy) mCopier.RunWorkerAsync();
      else mCopier.CancelAsync();
    }
  }
}

ここに投稿された(nobugzが投稿したもの)ファイルのコピーと進行中のステータスバーの表示。

特に大きなファイルをコピーしている間、プログレスバーの値を継続的にインクリメントしたかったのです。このサンプルコードで行われることは、進行中の値バーがコピーされるすべてのファイルで停止し、1つのファイルがコピーされた後、コピーされる次のファイルのサイズに増分することです。CopyFileExコピー時にプログレスバーが継続的に増加するWindowsのように機能させたいと思いました(CopyFileEx独自の実装が必要だったため、使用できません)。

4

6 に答える 6

51

次のようなものが必要です。

    public delegate void ProgressChangeDelegate(double Percentage, ref bool Cancel);
    public delegate void Completedelegate();

    class CustomFileCopier
    {
        public CustomFileCopier(string Source, string Dest)
        {
            this.SourceFilePath = Source;
            this.DestFilePath = Dest;

            OnProgressChanged += delegate { };
            OnComplete += delegate { };
        }

        public void Copy()
        {
            byte[] buffer = new byte[1024 * 1024]; // 1MB buffer
            bool cancelFlag = false;

            using (FileStream source = new FileStream(SourceFilePath, FileMode.Open, FileAccess.Read))
            {
                long fileLength = source.Length;
                using (FileStream dest = new FileStream(DestFilePath, FileMode.CreateNew, FileAccess.Write))
                {
                    long totalBytes = 0;
                    int currentBlockSize = 0;

                    while ((currentBlockSize = source.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        totalBytes += currentBlockSize;
                        double percentage = (double)totalBytes * 100.0 / fileLength;

                        dest.Write(buffer, 0, currentBlockSize);

                        cancelFlag = false;
                        OnProgressChanged(percentage, ref cancelFlag);

                        if (cancelFlag == true)
                        {
                            // Delete dest file here
                            break;
                        }
                    }
                }
            }

            OnComplete();
        }

        public string SourceFilePath { get; set; }
        public string DestFilePath { get; set; }

        public event ProgressChangeDelegate OnProgressChanged;
        public event Completedelegate OnComplete;
    }

OnProgressChanged別のスレッドで実行し、イベントを購読するだけです。

于 2011-05-19T07:42:56.500 に答える
27

私はこのソリューションが好きです。

コピーエンジンはフレームワークにあります

public delegate void IntDelegate(int Int);

public static event IntDelegate FileCopyProgress;
public static void CopyFileWithProgress(string source, string destination)
{
    var webClient = new WebClient();
    webClient.DownloadProgressChanged += DownloadProgress;
    webClient.DownloadFileAsync(new Uri(source), destination);
}

private static void DownloadProgress(object sender, DownloadProgressChangedEventArgs e)
{
    if(FileCopyProgress != null)
        FileCopyProgress(e.ProgressPercentage);
}

UNC パス

アクセス許可が設定されている限り、これは UNC パスで機能するはずです。そうでない場合、このエラーが発生します。その場合、認証されたリクエスト ユーザー ルートに投票します。

System.UnauthorizedAccessException: パス '\testws01\c$\foo' へのアクセスが拒否されました。

ASP.NET は、要求されたリソースへのアクセスを許可されていません。リソースへのアクセス権を ASP.NET 要求 ID に付与することを検討してください。ASP.NET には、アプリケーションが偽装されていない場合に使用されるベース プロセス ID (通常、IIS 5 または IIS 6 および IIS 7 のネットワーク サービスの {MACHINE}\ASPNET、および IIS 7.5 の構成済みアプリケーション プール ID) があります。アプリケーションが を介して偽装している場合<identity impersonate="true"/>、ID は匿名ユーザー (通常は IUSR_MACHINENAME) または認証された要求ユーザーになります。

于 2013-11-03T16:45:51.337 に答える
8

Gal によって提示された 2 つのストリームを使用して独自のファイル コピー ロジックを作成することは実行可能なオプションですが、CopyFileEx という名前の信頼性、セキュリティ、およびパフォーマンスが最適化された、深く統合された Windows 操作があるという理由だけではお勧めできません。

とはいえ、次の記事で: http://msdn.microsoft.com/en-us/magazine/cc163851.aspx彼らはあなたが望むことを正確に行いますが、もちろん CopyFileEx を使用する必要があります

幸運を

**編集**(私の答えを修正しました、ひどく機知に富んでいます)

于 2011-05-18T12:59:45.513 に答える
6

これは、パフォーマンスを向上させるために .NET 拡張機能とダブル バッファーを利用する最適化されたソリューションです。CopyTo の新しいオーバーロードが、変更された場合にのみ進行状況を示す Action と共に FileInfo に追加されます。

バックグラウンドでコピー操作を実行する progressBar1 という名前のプログレス バーを使用した WPF でのこのサンプル実装。

private FileInfo _source = new FileInfo(@"C:\file.bin");
private FileInfo _destination = new FileInfo(@"C:\file2.bin");

private void CopyFile()
{
  if(_destination.Exists)
    _destination.Delete();

  Task.Run(()=>{
    _source.CopyTo(_destination, x=>Dispatcher.Invoke(()=>progressBar1.Value = x));
  }).GetAwaiter().OnCompleted(() => MessageBox.Show("File Copied!"));
}

コンソール アプリケーションの例を次に示します。

class Program
{
  static void Main(string[] args)
  {
    var _source = new FileInfo(@"C:\Temp\bigfile.rar");
    var _destination = new FileInfo(@"C:\Temp\bigfile2.rar");

    if (_destination.Exists) _destination.Delete();

    _source.CopyTo(_destination, x => Console.WriteLine($"{x}% Complete"));
    Console.WriteLine("File Copied.");
  }
}

使用するには、FileInfoExtensions.cs などの新しいファイルを作成し、次のコードを追加します。

public static class FileInfoExtensions
{
  public static void CopyTo(this FileInfo file, FileInfo destination, Action<int> progressCallback)
  {
    const int bufferSize = 1024 * 1024;  //1MB
    byte[] buffer = new byte[bufferSize], buffer2 = new byte[bufferSize];
    bool swap = false;
    int progress = 0, reportedProgress = 0, read = 0;
    long len = file.Length;
    float flen = len;
    Task writer = null;

    using (var source = file.OpenRead())
    using (var dest = destination.OpenWrite())
    {
      dest.SetLength(source.Length);
      for (long size = 0; size < len; size += read)
      {
        if ((progress = ((int)((size / flen) * 100))) != reportedProgress)
          progressCallback(reportedProgress = progress);
        read = source.Read(swap ? buffer : buffer2, 0, bufferSize);
        writer?.Wait();  // if < .NET4 // if (writer != null) writer.Wait(); 
        writer = dest.WriteAsync(swap ? buffer : buffer2, 0, read);
        swap = !swap;
      }
      writer?.Wait();  //Fixed - Thanks @sam-hocevar
    }
  }
}

ダブル バッファは、読み取りに 1 つのスレッド、書き込みに 1 つのスレッドを使用することで機能するため、最大速度は 2 つのうち遅い方によってのみ決定されます。2 つのバッファー (ダブル バッファー) が使用されるため、読み取りスレッドと書き込みスレッドが同時に同じバッファーを使用することはありません。

例: コードはバッファー 1 に読み取り、読み取りが完了すると、書き込み操作がバッファー 1 の内容の書き込みを開始します。書かれています。バッファ 2 で読み取りが完了すると、バッファ 1 で書き込みが完了するまで待機し、バッファ 2 への書き込みを開始し、プロセスが繰り返されます。基本的に、1 つのスレッドが常に読み取りを行い、1 つのスレッドが常に書き込みを行います。

WriteAsyncは、スレッドではなくハードウェアに依存して非同期操作を実行するI/O 完了ポートを利用するオーバーラップ I/Oを使用し、これを非常に効率的にします。TLDR: スレッドが 2 つあると嘘をつきましたが、コンセプトは同じです。

于 2014-10-24T21:00:33.547 に答える
3

各ファイルからファイル ストリームの一部をコピーし、更新する「チャンク」ごとに更新できます。したがって、より連続的になります。完了した正しいパーセンテージを表示するために、ストリームの合計サイズに対する現在コピーしている「チャンク」の相対的なサイズを簡単に計算することもできます。

于 2011-05-18T12:34:26.843 に答える
0

Dispatcher を使用して ProgressBar を更新できます。

UpdateProgressBarDelegate updatePbDelegate = new UpdateProgressBarDelegate(ProgressBar1.SetValue);

Dispatcher.Invoke(updatePbDelegate, System.Windows.Threading.DispatcherPriority.Background, new object[] { ProgressBar.ValueProperty, value });
于 2011-05-18T12:33:46.900 に答える