0

So I'm doing a project in WPF right now that lets the user select a source folder and a target folder. Then there are two buttons "copy" and "move" and they either copy or move all the files from the source folder into the target folder, but how do I do that?

This is my code-behind so far:

//code for opening and selecting source and target folder
private void Btn_source_Click(object sender, RoutedEventArgs e)
{
    CommonOpenFileDialog dialog = new CommonOpenFileDialog();
    dialog.InitialDirectory = txtSource.Text;
    dialog.IsFolderPicker = true;
    if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
    {
        txtSource.Text = dialog.FileName;
    }
}

private void Btn_target_Click(object sender, RoutedEventArgs e)
{
    CommonOpenFileDialog dialog = new CommonOpenFileDialog();
    dialog.InitialDirectory = txtTarget.Text;
    dialog.IsFolderPicker = true;
    if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
    {
        txtTarget.Text = dialog.FileName;
    }
}


//
// code that copies or moves the selected files into the target folder
//


private void Btn_copy_Click(object sender, RoutedEventArgs e)
{

}

private void Btn_move_Click(object sender, RoutedEventArgs e)
{

}     
G.Dimov
  • 2,173
  • 3
  • 15
  • 40
KingOverTheHill
  • 11
  • 1
  • 10

1 Answers1

1

how do I do that?

It's not clear what it is you are actually asking. Do you want to know how to copy files? Or do you want to know how to implement the user commands in the context of WPF?

Given that you put the tag on your post, I am going to assume the latter.

The code you posted so far has a couple of serious problems, in the context of WPF, both related to the lack of the use of WPF's binding features:

  1. You are using the UI elements, such as txtSource and txtTarget, to store the results of the user interaction.
  2. You are handling the Click event directly.

Both of these problems may not prevent the program from being able to work, but they do constrain the implementation unnecessarily. You can improve your efficiency and the quality of the program by isolating the UI aspects from the underlying implementation of the program. That means:

  1. Declaring a view model to hold all of the non-UI program state.
  2. Declaring the layout of your UI in XAML.
  3. Declaring ICommand implementations for handling user commands
  4. Connecting all of the above via "{Binding}" markup elements in the XAML

Based on your requirements, here's a view model that would implement your program.:

class ViewModel : NotifyPropertyChangedBase
{

    private string _sourceDirectory;
    public string SourceDirectory
    {
        get => _sourceDirectory;
        set => _UpdateField(ref _sourceDirectory, value);
    }

    private string _targetDirectory;
    public string TargetDirectory
    {
        get => _targetDirectory;
        set => _UpdateField(ref _targetDirectory, value);
    }

    public ICommand SelectSourceCommand { get; }
    public ICommand SelectTargetCommand { get; }
    public ICommand CopyFilesCommand { get; }

    public ViewModel()
    {
        SelectSourceCommand = new DelegateCommand(() => _SelectDirectory(SourceDirectory, s => SourceDirectory = s));
        SelectTargetCommand = new DelegateCommand(() => _SelectDirectory(TargetDirectory, s => TargetDirectory = s));
        CopyFilesCommand = new DelegateCommand(() => _CopyFiles(SourceDirectory, TargetDirectory));
    }

    private void _SelectDirectory(string initial, Action<string> selected)
    {
        FolderBrowserDialog dialog = new FolderBrowserDialog();
        dialog.SelectedPath = initial;
        if (dialog.ShowDialog() == DialogResult.OK)
        {
            selected(dialog.SelectedPath);
        }
    }

    private void _CopyFiles(string source, string target)
    {
        Directory.CreateDirectory(target);

        foreach (string file in Directory.GetFiles(source))
        {
            File.Copy(file, Path.Combine(target, Path.GetFileName(file)));
        }
    }
}

Note that this view model doesn't have anything in it that's specific to the UI. In fact, you can often write the entire view model before any of the UI has been written. When done correctly, you can use the same view model type in other programs, regardless of the user interface API that they use (e.g. Winforms, console program, web frameworks like Blazor or React, you can even port it to JavaScript with relatively little effort).

With the view model in hand, then you just write up the UI in XAML, using whatever layout you prefer:

<Window x:Class="TestSO64787525FileCopyButton.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:l="clr-namespace:TestSO64787525FileCopyButton"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
  <Window.DataContext>
    <l:ViewModel/>
  </Window.DataContext>
  <Grid>
    <Grid.Resources>
      <p:Style TargetType="Button">
        <Setter Property="HorizontalAlignment" Value="Left"/>
        <Setter Property="Margin" Value="2 0"/>
      </p:Style>
    </Grid.Resources>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="Auto"/>
      <ColumnDefinition Width="Auto"/>
      <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <TextBlock Text="Source directory: " Grid.Row="0" Grid.Column="0"/>
    <TextBlock Text="Target directory: " Grid.Row="1" Grid.Column="0"/>
    <TextBlock Text="{Binding SourceDirectory}" Grid.Row="0" Grid.Column="1"/>
    <TextBlock Text="{Binding TargetDirectory}" Grid.Row="1" Grid.Column="1"/>
    <Grid Grid.Row="2" Grid.ColumnSpan="3">
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="Auto"/>
      </Grid.ColumnDefinitions>
      <Button Grid.Row="0" Grid.Column="0" Content="Select Source Directory" Command="{Binding SelectSourceCommand}"/>
      <Button Grid.Row="0" Grid.Column="1" Content="Select Target Directory" Command="{Binding SelectTargetCommand}"/>
      <Button Grid.Row="0" Grid.Column="2" Content="Copy Directory" Command="{Binding CopyFilesCommand}"/>
    </Grid>
  </Grid>
</Window>

Note that in the above, the view model properties are bound to UI elements using the "{Binding}" syntax. The two string values get bound to TextBlock elements for display of that text, while the ICommand values get bound to the Command property of the buttons, so that the defined commands are executed when the appropriate button is clicked.

Notes:

  1. I didn't want to bother installing the Windows API Code Pack library, so I switched your code to use the older FolderBrowserDialog type…the basic concepts are identical though.
  2. I didn't bother showing the "move" operation. That's simply a matter of calling File.Move() instead of File.Copy(), so I assume it's trivial for you to extend on your own the example above to include that as well.

Finally, I will note that the above strategy is a lot easier when you rely on some base classes to handle the boilerplate stuff, like implementing INotifyPropertyChanged and ICommand. Here are the classes I used for that in the above example:

class NotifyPropertyChangedBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void _UpdateField<T>(ref T field, T newValue,
        Action<T> onChangedCallback = null,
        [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, newValue))
        {
            return;
        }

        T oldValue = field;

        field = newValue;
        onChangedCallback?.Invoke(oldValue);
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

class DelegateCommand<T> : ICommand
{
    private readonly Action<T> _execute;
    protected readonly Func<T, bool> _canExecute;

    public DelegateCommand(Action<T> execute, Func<T, bool> canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public event EventHandler CanExecuteChanged;

    public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);

    public virtual bool CanExecute(object parameter) => _canExecute != null ? (parameter is T t ? _canExecute(t) : false) : true;

    public void Execute(object parameter) => _execute((T)parameter);
}

class DelegateCommand : DelegateCommand<object>
{
    public DelegateCommand(Action execute, Func<bool> canExecute = null)
        : base(o => execute(), canExecute != null ? (Func<object, bool>)(o => canExecute()) : null)
    { }

    public override bool CanExecute(object parameter) => _canExecute?.Invoke(null) ?? true;
}
Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • A nice comprehensive answer, but why are you prefixing those functions with `_` ??!! – Moo-Juice Nov 11 '20 at 18:47
  • @Moo: thanks for asking...I use underscores for all non-public members. It provides useful feedback when reading the code, as well as when navigating via the Visual Studio editing window dropdowns, to quickly distinguish between the publicly visible surface of a type and its private implementation details. A happy side-effect is that I can use the same name for fields and method parameters without having to include `this.` for the field name in methods. (Of course, other people insist on using `this.` _everywhere_, so they may not see this as a benefit...YMMV.) – Peter Duniho Nov 11 '20 at 18:52