0

Brand new to WPF and MVVM. My application has a button to browse for a file and a start button to perform a task with the selected file. I only want the start button available to the user if they have selected a file using the browse button. I am using the file path property in the ViewModel as the CommandParameter but this does not work. Any help appreciated.

XAML

<Button Grid.Column="1" Grid.Row="2" Content="Browse File" Margin="0,0,5,5" Command="{Binding Path=CommandBrowseFile}" ></Button> 
    <Button Grid.Column="1" Grid.Row="3" Grid.ColumnSpan="2" Content="Start" Margin="0,0,0,5" Command="{Binding Path=CommandStart}" CommandParameter="{Binding Path=FilePath}"></Button>

ViewModel

public class MyViewModel : ViewModelBase
{
    public RelayCommand CommandBrowseFile { get; private set; }
    public RelayCommand CommandStart { get; private set; }
    private string _filePath;

    public MyViewModel()
    {
        //L5XPath = "test";
        CommandBrowseFile = new RelayCommand(BrowseFile);
        CommandStart = new RelayCommand(Start, CanStart);
        
    }

    public string FilePath
    {
        get { return _filePath; }
        set
        {
            _filePath = value;
        }
    }

    public void BrowseFile(object message)
    {
        // Configure open file dialog box
        OpenFileDialog dlg = new OpenFileDialog();
        dlg.FileName = "Document"; // Default file name
        dlg.DefaultExt = ".l5x"; // Default file extension
        dlg.Filter = "l5x files (.l5x)|*.l5x"; // Filter files by extension

        // Show open file dialog box
        Nullable<bool> result = dlg.ShowDialog();

        // Process open file dialog box results

        FilePath = result == true ? dlg.FileName : null;
    }

    public void Start(object message)
    {
        //Do something
    }

    public bool CanStart(object message)
    {
        return message != null ? true : false;
    }
}

RelayCommand

public class RelayCommand : ICommand
{
    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;

    public event EventHandler CanExecuteChanged        
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }    

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
        {
            throw new NullReferenceException("execute");
        }

        else
        {
            _execute = execute;
            _canExecute = canExecute;
        }            
    }

    //public void RaiseCanExecuteChanged()
    //{
    //    if (CanExecuteChanged != null)
    //        CanExecuteChanged(this, new EventArgs());
    //}

    public RelayCommand(Action<object> execute) : this(execute, null)
    {

    }

    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute(parameter);
    }

    public void Execute(object parameter)
    {
        _execute.Invoke(parameter);
    }
}
James
  • 87
  • 1
  • 9

3 Answers3

0

I would modify CanStart method and call OnPropertyChanged() in FilePath's setter.

public bool CanStart(object message)
{
    return !string.IsNullOrEmpty(FilePath);
}

public string FilePath
{
    get { return _filePath; }
    set
    {
        _filePath = value;
       OnPropertyChanged(); // method in your ViewModelBase
       CommandStart.RaiseCanExecuteChanged();
    }
}

Also uncomment RaiseCanExecuteChanged and call this method on CommandStart command when FilePath is updated to notify UI whether the command is enabled or not.

public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
    if (CanExecuteChanged != null)
        CanExecuteChanged(this, new EventArgs());
}
user2250152
  • 14,658
  • 4
  • 33
  • 57
  • Why does my command parameter for the start button always have a null value even when the binding property is not null? – James Feb 08 '21 at 12:15
  • @James Trying to reproduce and it's working fine. Do you see any binding errors in output window? – user2250152 Feb 08 '21 at 12:45
  • There is nothing showing in my XAML Binding Failures output – James Feb 08 '21 at 12:58
  • No errors in the XAML Binding Failures window. Your solution works, I was just hoping to understand why mine does not. – James Feb 08 '21 at 13:13
0

You have not used CommandStart.RaiseCanExecuteChanged(); And this command invokes the CanStart() function, where you must check the _filePath value to see if it is empty or not

public string FilePath
{
     get { return _filePath; }
     set
     {
         _filePath = value;
         CommandStart.RaiseCanExecuteChanged();
     }
}
        
public bool CanStart(object message)
{
    return _filePath != null ? true : false;
}

in RelayCommand

public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
    if (CanExecuteChanged != null)
        CanExecuteChanged(this, EventArgs.Empty);
}
Meysam Asadi
  • 6,438
  • 3
  • 7
  • 17
  • Thanks, your solution works but I was interested in understanding why mine does not. – James Feb 08 '21 at 13:10
  • I did not understand this line CommandManager.RequerySuggested so I used my own code and added a description to the answer – Meysam Asadi Feb 08 '21 at 13:23
0

You have to call the PropertyChanged event on the property being used within the command parameter.

public string FilePath
    {
        get { return _filePath; }
        set
        {
            _filePath = value;
            NotifyPropertyChanged("FilePath");
        }
    }
James
  • 87
  • 1
  • 9