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. Thing is there may be occassions 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, its 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 a 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 arbitary 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 lets 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

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:

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

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;
    }
}
Advertisements

One thought on “Reactive Command With Dynamic Predicates

  1. Usman says:

    hi, Unable to compile observable extensions class, many references exceptions. I just copy paste in code in library and cannot find references used in extensions methods, still missing something?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: