0

I have a view model which looks like:

public sealed class MyViewModel: INotifyPropertyChanged
{
    public bool ShowSuccess 
    {
        get { return _success; } 
        set 
        { 
            _success = value; 
            PropertyChanged?.Invoke( ... );
        } 
    }

    public ICommand TestCommand 
    {
      get
      {
        _test = _test ?? new MyTestCommand();
        return _test;
      }
    }
}

and the command

public sealed class MyTestCommand : ICommand
{
    public bool CanExecute(object parameter)
    {
      return true;
    }

    public void Execute(object parameter)
    {
       // do stuff
    }
}

and in xaml

 <Button Command="{Binding TestCommand}" Content="Test" />

I want to update ShowSuccess property after executing Execute from MyTestCommand.

How to achieve that ?

Thanks

PS: I'm still newbie in WPF, just learned MVVM and custom command

dhilmathy
  • 2,800
  • 2
  • 21
  • 29
Snake Eyes
  • 16,287
  • 34
  • 113
  • 221

2 Answers2

0

You can use DelegateCommand for this. Which helps you use one Generic class for all commands instead of creating individual Command classes.

public sealed class MyViewModel : INotifyPropertyChanged
{
    private ICommand _test;
    private bool _success;

    public bool ShowSuccess
    {
        get { return _success; }
        set
        {
            _success = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ShowSuccess)));
        }
    }

    public ICommand TestCommand
    {
        get
        {
            _test = _test ?? new DelegateCommand((arg) => ShowSuccess = true);
            return _test;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

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

    public event EventHandler CanExecuteChanged;

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

    public DelegateCommand(Action<object> execute,
                   Predicate<object> canExecute)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

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

        return _canExecute(parameter);
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    public void RaiseCanExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}
dhilmathy
  • 2,800
  • 2
  • 21
  • 29
0

I strongly recomment taking a look at ReactiveUI - UI framework based around Rx - you don't have to use all of its features, like built in dependency injection or view location, but it's also very cool. It's not worth to reinvent the wheel in this case.

It has an implementation of ICommand that not only supports asynchronous work out of the box, it also allows commands to return stuff (VERY usefull) and takes care of disabling buttons when executing.

It also comes with DynamicData - a cure to all problems related to collections.

So, most basic sample would be:

TestCommand = ReactiveCommand.CreateFromTask<int>(async paramater  =>{
   var result = await DoStuff(parameter); // ConfigureAwait(false) might be helpful in more complex scenarios

   return result + 5;
}

TestCommand.Log(this) // there is some customization available here
   .Subscribe(x => SomeVmProperty = x;); // this always runs on Dispatcher out of the box

TestCommand.ThrownExceptions.Log(this).Subscribe(ex => HandleError(ex));

this.WhenAnyValue(x => x.SearchText) // every time property changes
   .Throttle(TimeSpan.FromMilliseconds(150)) // wait 150 ms after the last change
   .Select(x => SearchText) 
   .InvokeCommand(Search); // we pass SearchText as a parameter to Search command, error handling is done by subscribing to Search.ThrownExceptions. This will also automatically disable all buttons bound to Search command

What is even more useful, I think, is being able to subscribe in the View code behind.

// LoginView.xaml.cs

ViewModel.Login.Where(x => !x.Success).Subscribe(_ =>{
   PasswordBox.Clear();
   PasswordBox.Focus();
});
Krzysztof Skowronek
  • 2,796
  • 1
  • 13
  • 29