Copie du fichier avec la barre de progression

j'ai utilisé ce code:

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();
    }
  }
}

posté le ici (le nobugz posté) dans la copie de fichiers et affichage de l'état dans la barre de progression.

je voulais continuellement augmenter la valeur de la barre de progression en copiant, surtout les gros fichiers. Ce qui se passe dans ce code d'exemple est que la valeur dans la barre de progression s'arrête sur chaque fichier copié et après qu'un fichier a été copié il augmentera alors à la taille du fichier suivant à être copier. Je voulais qu'il fonctionne comme CopyFileEx dans les fenêtres qui progressent la barre d'incrément en continu lors de la copie (Je ne peux pas utiliser CopyFileEx parce que je voulais avoir ma propre implémentation).

28
demandé sur Kromster 2011-05-18 16:25:44

6 réponses

Vous avez besoin de quelque chose comme ceci:

    public delegate void ProgressChangeDelegate(double Persentage, 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 persentage = (double)totalBytes * 100.0 / fileLength;

                        dest.Write(buffer, 0, currentBlockSize);

                        cancelFlag = false;
                        OnProgressChanged(persentage, 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;
    }

il suffit de l'exécuter dans un thread séparé et sunscribe pour OnProgressChanged événement

38
répondu Anton Semenov 2012-12-12 17:50:47

j'aime cette solution, parce que

Le moteur de copie est dans le cadre

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);
}

les Chemins d'accès UNC

cela devrait fonctionner avec les chemins UNC aussi longtemps que les permissions sont configurées. Si ce n'est pas le cas, vous obtiendrez cette erreur, auquel cas, je vote pour la route d'utilisateur de requête authentifiée.

System.UnauthorizedAccessException: accès au chemin '\testws01\c$\foo " est refusé.

ASP.NET n'est pas autorisé à accéder à la ressource demandée. Envisager de accorder des droits d'accès à la ressource ASP.NET demande identité. ASP.NET a une identité de processus de base (généralement {MACHINE}\ASPNET sur IIS 5 ou réseau sur IIS 6 et IIS 7, et l'identité du pool d'applications configuré sur IIS 7.5) qui est utilisé si l'application est à ne pas usurper l'identité. Si la demande est usurpation de l'identité à l'aide de <identity impersonate="true"/>, l'identité sera l'utilisateur anonyme (typiquement IUSR_MACHINENAME) ou l'authentifié demande utilisateur.

18
répondu toddmo 2016-10-23 17:41:58

faire votre propre logique de copie de fichier en utilisant 2 streams comme présenté par Gal est une option viable, mais son pas recommandé uniquement parce qu'il ya une opération windows profondément intégré qui est optimisé dans la fiabilité, la sécurité et la performance appelé CopyFileEx.

cela dit, dans l'article suivant: http://msdn.microsoft.com/en-us/magazine/cc163851.aspx ils font exactement ce que vous voulez, mais bien sûr, vous devez utiliser CopyFileEx

bon la chance

** EDIT ** (fixe ma réponse, mal witten)

8
répondu Polity 2011-05-18 13:59:32

vous pouvez copier des parties du flux de fichiers de chaque fichier, et mettre à jour après chaque "morceau" que vous mettez à jour. Ainsi, il sera plus continu - vous pouvez aussi facilement calculer la taille relative du "morceau" actuel que vous copiez par rapport à la taille totale du flux afin de montrer le pourcentage correct fait.

3
répondu Gal 2011-05-18 12:34:26

Voici une solution optimisée qui utilise les extensions .NET et un double-buffer Pour une meilleure performance. Une nouvelle surcharge de CopyTo est ajoutée à FileInfo avec une Action qui indique la progression seulement quand elle a changé.

cet exemple de mise en œuvre dans WPF avec une barre de progression nommée progressBar1 qui effectue l'opération de copie en arrière-plan.

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!"));
}

voici un exemple pour une application Console

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.");
  }
}

pour utiliser, créer un nouveau fichier, tel que FileInfoExtensions.cs et ajoutez ce code:

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

le double buffer fonctionne en utilisant un thread pour lire et un thread pour écrire, donc la vitesse maximale est dictée seulement par le plus lent des deux. Deux tampons sont utilisés (un double tampon), ce qui garantit que les threads de lecture et d'écriture n'utilisent jamais le même tampon en même temps.

exemple: le code lit dans le buffer 1, puis quand la lecture est terminée, une opération d'écriture commence à écrire le contenu du buffer 1. Sans attendre terminer l'écriture, le buffer est remplacé par le buffer 2 et les données sont lues dans le buffer 2 pendant que le buffer 1 est encore en cours d'écriture. Une fois la lecture terminée dans le buffer 2, Il attend que l'écriture soit terminée sur le buffer 1, commence à écrire le buffer 2, et le processus se répète. Essentiellement, 1 fil est toujours en train de lire, et on est toujours en train d'écrire.

WriteAsync overlapped I / O, qui utilise I/O ports de fin d', qui dépendent du matériel pour effectuer des opérations asynchrones plutôt que des threads, ce qui rend cela très efficace. TLDR: j'ai menti sur le fait qu'il y avait deux fils, mais le concept est le même.

2
répondu Robear 2016-03-08 01:27:24

Vous pouvez utiliser le répartiteur pour mettre à jour votre barre de progression .

UpdateProgressBarDelegate updatePbDelegate = new UpdateProgressBarDelegate(ProgressBar1.SetValue);

Dispatcher.Invoke(updatePbDelegate, System.Windows.Threading.DispatcherPriority.Background, new object[] { ProgressBar.ValueProperty, value });
0
répondu Akrem 2011-05-18 12:53:16