Click here to Skip to main content
15,867,771 members
Articles / All Topics

Reactive Command With Dynamic Predicates

Rate me:
Please Sign up or sign in to vote.
4.33/5 (2 votes)
4 Mar 2014CPOL3 min read 14.5K   5  
Reactive Command With Dynamic Predicates

As some of you know, I have been a firm advocate and supporter of WPF for a long time now, and I still am. However one thing I have never been that keen on is the way the ICommand implementations that you typically see when writing VM code work. This includes RelayCommand / DelegateCommand and all ones similar to that.

The problem usual stems from the way the CanExecute is wired up to the CommandManager.RequerySuggested.

This does indeed enable the ICommand to work correctly, and any ICommandTarget objects will be disabled correctly when the CanExecute method is called. All ok so far.

The real problem comes with how the CanExecute predicate is called. This is typically called a lot, and under certain conditions such as mouse move, scroll and many more operations. Using the CommandManager.RequerySuggested is in fact very chatty.

Despite the chattyness of this approach, you can still get into a situation where the ICommand implementation doesn’t behave correctly, I have seen this under strange focus conditions.

So what is the solution. Well, one approach is to manually raise the CanExecuteChanged event of ICommand instead of using the CommandManager.RequerySuggested approach. This is good and does solve the issue, but that means you are now in a situation where you need to know exactly when to raise the CanExecuteChanged event of ICommand. The thing is there may be occasions you just hadn’t thought of.

Is there another way…..Mmmm, let me think, well yes there is. We can use the Reactive Extensions for this. In fact, there is a whole library out there for MVVM based on the use of Reactive Extensions, it's called Reactive UI (which is maintained by Paul Betts) which has a very similar idea to this blog post, but they are not quite the same.

What Paul Betts has done is to use the Reactive Extensions along with an ICommand to allow the ICommand implementation to start with a single IObservable<bool> that will raise the ICommand.CanExecuteChanged event. This is cool, but what I wanted was the ability to add arbitrary predicates to the ICommand implementation, that could be added at any stage of the ICommand implementation lifecycle not just when you declared the ICommand.

Anyway the basic idea behind the code presented in this blog is that we use the Reactive Extensions to come up with a combined IObservable<bool> stream for all combined predicates for the ICommand, and then use the current result of that to raise the Command.CanExecuteChanged event. The result of which is a very responsive ICommand, and it always works, no weird focus issues.

Anyway, let's continue to look at the code, shall we.

Here is the ReactiveCommand in its entirety. The real thrust of it, is the AddPredicate method, which ensures we always have a combined predicate which will make the ICommand work correctly.

C#
public interface IReactiveCommand : ICommand
{
    IObservable<object> CommandExecutedStream { get; }
    IObservable<Exception> CommandExeceptionsStream { get; }
    void AddPredicate(IObservable<bool> predicate);
}

public class ReactiveCommand : IReactiveCommand, IDisposable
{
    private Subject<object> commandExecutedSubject = new Subject<object>();
    private Subject<Exception> commandExeceptionsSubjectStream = new Subject<Exception>();
    private List<IObservable<bool>> predicates = new List<IObservable<bool>>();
    private IObservable<bool> canExecuteObs;
    private bool canExecuteLatest = true;
    private CompositeDisposable disposables = new CompositeDisposable();

    public ReactiveCommand()
    {
        RaiseCanExecute(true);
    }

    public ReactiveCommand(IObservable<bool> initPredicate, bool initialCondition)
    {
        if (initPredicate != null)
        {
            canExecuteObs = initPredicate;
            SetupSubscriptions();
        }
        RaiseCanExecute(initialCondition);
    }

    public void AddPredicate(IObservable<bool> predicate)
    {
        disposables.Dispose();
        predicates.Add(predicate);
        this.canExecuteObs = this.canExecuteObs.CombineLatest(
                predicates.Last(), (a, b) => a && b).DistinctUntilChanged();
        SetupSubscriptions();
    }

    bool ICommand.CanExecute(object parameter)
    {
        return canExecuteLatest;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        commandExecutedSubject.OnNext(parameter);
    }

    public IObservable<object> CommandExecutedStream
    {
        get { return this.commandExecutedSubject.AsObservable(); }
    }

    public IObservable<Exception> CommandExeceptionsStream
    {
        get { return this.commandExeceptionsSubjectStream.AsObservable(); }
    }

    public void Dispose()
    {
        disposables.Dispose();
    }

    protected virtual void RaiseCanExecuteChanged(EventArgs e)
    {
        var handler = this.CanExecuteChanged;

        if (handler != null)
        {
            handler(this, e);
        }
    }

    private void RaiseCanExecute(bool value)
    {
        canExecuteLatest = value;
        this.RaiseCanExecuteChanged(EventArgs.Empty);
    }

    private void SetupSubscriptions()
    {

        disposables = new CompositeDisposable();
        disposables.Add(this.canExecuteObs.Subscribe(
            //OnNext
            x =>
            {
                RaiseCanExecute(x);
            },
            //onError
            commandExeceptionsSubjectStream.OnNext
        ));
    }
}

I hope this is easy enough to understand. The next thing to look at is any example usage of this, which is as follows:

C#
public class ViewModel : INPCBase
{
    private string title;
    private bool hasStuff;

    public ViewModel()
    {
        IObservable<bool> initPredicate = this.ObserveProperty(x => x.Title)
                 .StartWith(this.Title).Select(x => !string.IsNullOrEmpty(x)); ;
        IObservable<bool> predicate = this.ObserveProperty(x => x.HasStuff)
                 .StartWith(this.HasStuff);
        SomeCommand = new ReactiveCommand(initPredicate, false);
        SomeCommand.AddPredicate(predicate);
        SomeCommand.CommandExecutedStream.Subscribe(x =>
            {
                MessageBox.Show("Command Running");
            });
    }

    public ReactiveCommand SomeCommand { get; set; }

    public string Title
    {
        get
        {
            return this.title;
        }
        set
        {
            RaiseAndSetIfChanged(ref this.title, value, () => Title);
        }
    }

    public bool HasStuff
    {
        get
        {
            return this.hasStuff;
        }
        set
        {
            RaiseAndSetIfChanged(ref this.hasStuff, value, () => HasStuff);
        }
    }

}

This code makes use of the following helper code to pluck out an IObservable<T> from a property. Which was largely taken from Keith Woods blog.

C#
public static class ObservableExtensions
{
    public static IObservable ObserveProperty<T, TValue>(
        this T source,
            Expression<Func<T, TValue>> propertyExpression
    )
        where T : INotifyPropertyChanged
    {
        return source.ObserveProperty(propertyExpression, false);
    }

    public static IObservable ObserveProperty<T, TValue>(
        this T source,
        Expression<Func<T, TValue>> propertyExpression,
        bool observeInitialValue
    )
        where T : INotifyPropertyChanged
    {
        var memberExpression = (MemberExpression)propertyExpression.Body;

        var getter = propertyExpression.Compile();

        var observable = Observable
            .FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>(
                h => new PropertyChangedEventHandler(h),
                h => source.PropertyChanged += h,
                h => source.PropertyChanged -= h)
            .Where(x => x.EventArgs.PropertyName == memberExpression.Member.Name)
            .Select(_ => getter(source));

        if (observeInitialValue)
            return observable.Merge(Observable.Return(getter(source)));

        return observable;
    }

    public static IObservable ObservePropertyChanged(this T source)
        where T : INotifyPropertyChanged
    {
        var observable = Observable
            .FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>(
                h => new PropertyChangedEventHandler(h),
                h => source.PropertyChanged += h,
                h => source.PropertyChanged -= h)
            .Select(x => x.EventArgs.PropertyName);

        return observable;
    }

    public static IObservable ObserveCollectonChanged(this T source)
        where T : INotifyCollectionChanged
    {
        var observable = Observable
            .FromEvent<NotifyCollectionChangedEventHandler, NotifyCollectionChangedEventArgs>(
                h => new NotifyCollectionChangedEventHandler(h),
                h => source.CollectionChanged += h,
                h => source.CollectionChanged -= h)
            .Select(_ => new Unit());

        return observable;
    }

    public static IObservable ObserveCollectonChanged(
         this T source, NotifyCollectionChangedAction collectionChangeAction)
                where T : INotifyCollectionChanged
    {
        var observable = Observable
            .FromEvent<NotifyCollectionChangedEventHandler, NotifyCollectionChangedEventArgs>(
                h => new NotifyCollectionChangedEventHandler(h),
                h => source.CollectionChanged += h,
                h => source.CollectionChanged -= h)
            .Where(x => x.EventArgs.Action == collectionChangeAction)
            .Select(_ => new Unit());

        return observable;
    }
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
-- There are no messages in this forum --