1

I have the following method that is executed in a button click:

 private void CopyDirectoriesAndFiles(string source, string target, string[] excludedFolders)
        {

            foreach (string dir in Directory.GetDirectories(source, "*", System.IO.SearchOption.AllDirectories))
                if (!excludedFolders.Contains(dir))
                    Directory.CreateDirectory(target + dir.Substring(source.Length));

            foreach (string file_name in Directory.GetFiles(source, "*.*", System.IO.SearchOption.AllDirectories))
                if (!File.Exists(Path.Combine(target + file_name.Substring(source.Length))))
                    File.Copy(file_name, target + file_name.Substring(source.Length));

        }

The button click has some other methods, but they don't take very long to run, but even so, how can I show and update a progress bar for each even that is run. I put a textbox, but it only writes to the textbox once it is finished with everything. My button order may looks like this:

InitializeStuff();

CopyFiles();

CleanUp();

A progress bar is not absolutely necessary, although nice. It would be great if I could get my textbox to update at each time a method completed instead of at the very end.

Xaisoft
  • 45,655
  • 87
  • 279
  • 432

2 Answers2

5

Here's a complete working model using MVVM:

The View:

<Window x:Class="CopyFiles.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
        xmlns:model="clr-namespace:CopyFiles">

    <Window.DataContext>
        <model:CopyModel />
    </Window.DataContext>

    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter"/>
    </Window.Resources>

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"></ColumnDefinition>
            <ColumnDefinition Width="*"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
        </Grid.RowDefinitions>


        <Label Grid.Row="0" Grid.Column="0" Name="sourceLabel">Source</Label>
        <TextBox Text="{Binding Source, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Grid.Row="0" Grid.Column="1" Name="sourceTextBox" Margin="5"/>

        <Label Grid.Row="1" Grid.Column="0"  Name="destinationLabel">Destination</Label>
        <TextBox Text="{Binding Destination, Mode =TwoWay, UpdateSourceTrigger=PropertyChanged}"  Grid.Row="1" Grid.Column="1" Name="destinationTextBox" Margin="5" />

        <Button Command="{Binding CopyCommand}" Grid.Row="2" Grid.ColumnSpan="2" Content="Copy" Name="copyButton" Width="40" HorizontalAlignment="Center"  Margin="5"/>

        <ProgressBar Visibility="{Binding CopyInProgress, Converter={StaticResource booleanToVisibilityConverter}}" Value="{Binding Progress}" Grid.Row="3" Grid.ColumnSpan="2"  Height="20" Name="copyProgressBar" Margin="5" />
    </Grid>
</Window>

The ViewModel:

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using Microsoft.Practices.Prism.Commands;

namespace CopyFiles
{
    public class CopyModel: INotifyPropertyChanged
    {

        private string source;
        private string destination;
        private bool copyInProgress;
        private int progress;
        private ObservableCollection<string> excludedDirectories;

        public CopyModel()
        {
            this.CopyCommand = new DelegateCommand(ExecuteCopy, CanCopy);
            this.excludedDirectories = new ObservableCollection<string>();
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public string Source
        {
            get { return source; }
            set
            {
                source = value;
                RaisePropertyChanged("Source");
                CopyCommand.RaiseCanExecuteChanged();
            }
        }

        public string Destination
        {
            get { return destination; }
            set
            {
                destination = value;
                RaisePropertyChanged("Destination");
                CopyCommand.RaiseCanExecuteChanged();
            }
        }

        public bool CopyInProgress
        {
            get { return copyInProgress; }
            set
            {
                copyInProgress = value;
                RaisePropertyChanged("CopyInProgress");
                CopyCommand.RaiseCanExecuteChanged();
            }
        }

        public int Progress
        {
            get { return progress; }
            set
            {
                progress = value;
                RaisePropertyChanged("Progress");
            }
        }

        public ObservableCollection<string> ExcludedDirectories
        {
            get { return excludedDirectories; }
            set 
            { 
                excludedDirectories = value;
                RaisePropertyChanged("ExcludedDirectories");
            }
        }


        public DelegateCommand CopyCommand { get; set; }

        public bool CanCopy()
        {
            return (!string.IsNullOrEmpty(Source) &&
                    !string.IsNullOrEmpty(Destination) &&
                    !CopyInProgress);
        }

        public void ExecuteCopy()
        {
            BackgroundWorker copyWorker = new BackgroundWorker();
            copyWorker.DoWork +=new DoWorkEventHandler(copyWorker_DoWork);
            copyWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(copyWorker_RunWorkerCompleted);
            copyWorker.ProgressChanged += new ProgressChangedEventHandler(copyWorker_ProgressChanged);
            copyWorker.WorkerReportsProgress = true;
            copyWorker.RunWorkerAsync();
        }

        private void RaisePropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            if(handler != null) 
            {
                var eventArgs = new PropertyChangedEventArgs(propertyName);
                handler(this, eventArgs);
            }
        }

        private void copyWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            var worker = sender as BackgroundWorker;
            this.CopyInProgress = true;
            worker.ReportProgress(0);

            var directories = Directory.GetDirectories(source, "*", System.IO.SearchOption.AllDirectories);
            var files = Directory.GetFiles(source, "*.*", System.IO.SearchOption.AllDirectories);
            var total = directories.Length + files.Length;
            int complete = 0;

            foreach (string dir in directories)
            {
                if (!ExcludedDirectories.Contains(dir))
                    Directory.CreateDirectory(destination + dir.Substring(source.Length));
                complete++;
                worker.ReportProgress(CalculateProgress(total, complete));
            }

            foreach (string file_name in files)
            {
                if (!File.Exists(Path.Combine(destination + file_name.Substring(source.Length))))
                    File.Copy(file_name, destination + file_name.Substring(source.Length));
                complete++;
                worker.ReportProgress(CalculateProgress(total, complete));
            }
        }

        private static int CalculateProgress(int total, int complete)
        {
            // avoid divide by zero error
            if (total == 0) return 0;
            // calculate percentage complete
            var result = (double)complete / (double)total;
            var percentage = result * 100.0;
            // make sure result is within bounds and return as integer;
            return Math.Max(0,Math.Min(100,(int)percentage));
        }

        private void copyWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.Progress = e.ProgressPercentage;
        }

        private void copyWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.CopyInProgress = false;
        }
    }
}
sga101
  • 1,904
  • 13
  • 12
  • Cool, I will give this a shot. One thing I want to ask is that this will report the progress of the files copied, but what about the other steps, for example. It probably takes a millisecond to initialize some data, but I want to display this as progress too, so the file copying doesn't really start at 0 percent and doesn't really end at 100, the last thing I do is delete a directory. How would you display this to the user or would you even bother and just display the most lengthy process? – Xaisoft Oct 02 '12 at 01:37
  • Also, would you mind explaining some of the code especially the ObservableCollection, DelegateCommand, and Prism.Commands – Xaisoft Oct 02 '12 at 01:39
  • You can report progress at any point during the work carried out by the background worker. You just have to report a value between 0 and 100. So, for example, you could add 1000 to the total number of files and directories, and then report progress as 1000 / total when your initialisation step is complete. You will need to tweak the value to get a reasonable result. – sga101 Oct 02 '12 at 13:50
  • If you bind to a regular list of items, the interface won't update when you add or remove items, because the list doesn't notify the view with the changes. The Observable Collection is designed to solve this problem: it implements INotifyCollectionChanged so you don't have to. See [MSDN](http://msdn.microsoft.com/en-us/library/ms668604.aspx#remarksToggle) for more details – sga101 Oct 02 '12 at 13:58
  • Routed commands are a convenient way of registering commands in your view model, which allows you to reuse the viewmodel with different views without duplicating the command logic. The prism library contains (among many other very useful things) a powerful implementation of routed commands called Delegate commands. These allow you to hook up a command button to a method which determines if it is enabled and a method which is executed when it is clicked. – sga101 Oct 02 '12 at 14:02
2

I have the following method that is executed in a button click:

It shouldn't be. This will freeze your UI for too long.

Use a Backgroundworker. [1], [2] and [3]

Community
  • 1
  • 1
H H
  • 263,252
  • 30
  • 330
  • 514
  • I have seen BackgroundWorker, but I am not sure how to implement in my code. – Xaisoft Oct 01 '12 at 19:11
  • Th InterWeb is filled with tutorials. I added a few links, but do try Google. – H H Oct 01 '12 at 19:13
  • I got it to work, the only issue I would say I have is reporting progress when I don't know how many files I am dealing with. I have a number of methods I call, so for now, I just set the progress min/max to the number of methods and I report the progress after each step is complete. – Xaisoft Oct 01 '12 at 20:29
  • Is it still possible to keep the UI locked while still updating it? – Xaisoft Oct 01 '12 at 20:35
  • You are using GetFiles() so you can easily get a Count. With the more efficient EnumerateFiles you would have to fudge. – H H Oct 01 '12 at 20:54
  • Locking a UI is different and better than freezing it. Keep open a Dialog Window while the Bgw runs. – H H Oct 01 '12 at 20:54
  • If it's inefficient to get a full-info progressbar displayed, just throw up a marquee/indeterminate bar. It's better than nothing and it doesn't hang the ui. – Greg D Oct 01 '12 at 21:19
  • @HenkHolterman - What do you mean by "EnumerateFiles you would have to fudge" – Xaisoft Oct 02 '12 at 01:33
  • @GregD - Greg, can you post in an answer what you mean? – Xaisoft Oct 02 '12 at 01:33
  • Just set IsIndeterminate (http://msdn.microsoft.com/en-us/library/system.windows.controls.progressbar.isindeterminate.aspx) to true on your progress bar. – Greg D Oct 02 '12 at 11:58
  • @Xaisoft - EnumerateFiles() has 'deferred execution' so that you do not know the number of files up front. With GetFiles() you can get a Count. – H H Oct 02 '12 at 12:02
  • @HenkHolterman - I'm a little confused on what you mean when you with GetFiles(), I can get a Count and with EnumerateFiles(), I can get a Count. I understand that EnumerateFiles() can get the Count up front, but are you saying with GetFiles(), I can't get the Count up front. – Xaisoft Oct 02 '12 at 13:26
  • No, I said it the other way around. Do notice that `Count()` is very slow compared to just `Count`. – H H Oct 02 '12 at 13:48
  • Ok, I understand now, I never really paid attention to the speed between Count() and Count – Xaisoft Oct 02 '12 at 13:49
  • With GetFiles the property is probably called `Length`, not Count. – H H Oct 02 '12 at 13:51