1

I am a wpf newb so this question may be trivial. I am trying to copy a file from one folder to another. I would like to show a progressbar during the copy process.

My code is like this:

if (!System.IO.File.Exists(basemapDest))
{
    await Copier.CopyFiles(new Dictionary<string, string>
    {
        {basemapSrc, basemapDest},
    }, prog => prgBaseMap.Value = prog);
}

public static class Copier
{
    public static async Task CopyFiles(Dictionary<string, string> files, Action<int> progressCallback)
    {
        for (var x = 0; x < files.Count; x++)
        {
            var item = files.ElementAt(x);
            var from = item.Key;
            var to = item.Value;

            using (var outStream = new FileStream(to, FileMode.Create, FileAccess.Write, FileShare.Read))
            {
                using (var inStream = new FileStream(from, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    long fileLength = from.Length;
                    await inStream.CopyToAsync(outStream);
                }
            }

            progressCallback((int)((x + 1) / files.Count) * 100);
        }
    }
}

My XAML Code:

<StackPanel>
    <ProgressBar x:Name="prgBaseMap" Height="10" Visibility="Collapsed"/>
</StackPanel>

While this works for reporting a file is copied it doesn't show progress while I am doing the copy. What am I doing wrong ?

*** Edit, this is not a copy of stream.copyto with progress bar reporting

the referenced question is using a BackgroundWorker which these days is considered by many people to be obsolete. This question is about using the new asynchronous model of .NET. I hope the provided solution proves useful to others as well.

Community
  • 1
  • 1
w2olves
  • 2,229
  • 10
  • 33
  • 60
  • What is `prgBaseMap`? Do you miss `INotifyPropertyChanged` implementation on that class? – torvin Nov 16 '15 at 00:19
  • 1
    What kind of progress do you want displayed? The code you posted will update the progress after each file is copied. You are only copying one file, so I would not expect to see any useful progress display on the screen. If you want progress to be updated _while_ a file is being copied, you'll have to copy using more explicit means and update the progress as the smaller chunks of the file are read and written to the new file. – Peter Duniho Nov 16 '15 at 00:19
  • Unfortunately, it's not very clear what you're asking. Please provide [a good, _minimal_, _complete_ code example](http://stackoverflow.com/help/mcve) that reliably reproduces the problem, along with a precise description of what that code does and how that's different from what you want it to do. – Peter Duniho Nov 16 '15 at 00:21
  • PeterDuniho, @torvin, you are both correct, perhaps my question isnt the most elaborate. The Progress I would like to show is while a file is being copied, in this part of the code: await nStream.CopyToAsync(outStream); I have added my Xaml declaration of prgBaseMap Basically, I would like to show a Progress indicator as a file is being downloaded on how much is done estimated time etc – w2olves Nov 16 '15 at 00:24
  • Possible duplicate of [stream.copyto with progress bar reporting](http://stackoverflow.com/questions/22857713/stream-copyto-with-progress-bar-reporting) – Patrick Klug Nov 16 '15 at 00:33
  • @PatrickKlug, the referenced question is using a `BackgroundWorker` which these days is considered by many people to be obsolete. OP is using the new asynchronous model of .NET. It makes sense in this case to provide a solution that is based on such new model. – Yacoub Massad Nov 16 '15 at 01:19
  • @YacoubMassad fair enough. – Patrick Klug Nov 16 '15 at 01:32

1 Answers1

9

Here is a solution that allows you to display progress as files are being copied:

public static class Copier
{
    public static async Task CopyFiles(Dictionary<string, string> files, Action<double> progressCallback)
    {
        long total_size = files.Keys.Select(x => new FileInfo(x).Length).Sum();

        long total_read = 0;

        double progress_size = 10000.0;

        foreach(var item in files)
        {
            long total_read_for_file = 0;

            var from = item.Key;
            var to = item.Value;

            using (var outStream = new FileStream(to, FileMode.Create, FileAccess.Write, FileShare.Read))
            {
                using (var inStream = new FileStream(from, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    await CopyStream(inStream , outStream, x =>
                    {
                        total_read_for_file = x;
                        progressCallback(((total_read + total_read_for_file)/ (double)total_size) * progress_size);
                    } );
                }
            }

            total_read += total_read_for_file;
        }
    }

    public static async Task CopyStream(Stream from, Stream to, Action<long> progress)
    {
        int buffer_size = 10240;

        byte[] buffer = new byte[buffer_size];

        long total_read = 0;

        while (total_read < from.Length)
        {
            int read = await from.ReadAsync(buffer, 0, buffer_size);

            await to.WriteAsync(buffer, 0, read);

            total_read += read;

            progress(total_read);
        }
    }

}

And you can use it like this:

var dictionary = new Dictionary<string, string>
{
    {"c:\\source_file1.dat", "c:\\destination_file1.dat"},
    {"c:\\source_file2.dat", "c:\\destination_file2.dat"},
};

prgBaseMap.Maximum = 10000.0;

await Copier.CopyFiles(dictionary, prog => prgBaseMap.Value = prog);

This solution works by manually copying the file contents 10k bytes at a time (The CopyStream method). And each time it updates the progress bar.

At the start, it sums the total length of the source files to be able calculate a relative progress.

The CopyFiles method will report the progress to caller by calling it back with a progress relative to 10000.0. This is why the progress bar needs to have a maximum to 10000.0.

Instead of using a double value of 10000.0, you can use the sum of lengths of input files. This allows you to report also on the total number of copied bytes.

You would have to calculate the sum of lengths in the caller in this case.

Yacoub Massad
  • 27,509
  • 2
  • 36
  • 62
  • Thank you, this works like a charm. I was merely expecting a pointer or hint, a fully working solution with an excellent explanation really made my day. Thank you and I hope this is helpful to anyone else who is interested in async copying files in the future. – w2olves Nov 16 '15 at 15:49