w2olves
w2olves

Reputation: 2329

WPF Showing progress bar during async file copy

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.

Upvotes: 1

Views: 6898

Answers (1)

Yacoub Massad
Yacoub Massad

Reputation: 27861

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.

Upvotes: 9

Related Questions