如何组合多个IObservable <bool>以形成复合bool订阅值</b​​ool>

时间:2013-10-03 15:53:42

标签: wpf system.reactive

我想为WPF创建一种基于Rx的ICommand。我想做的是能够通过组合任意数量的IObservable&lt; bool&gt;来控制CanExecute。流。

我想要工作的方式是我想使用所有谓词的最新组合逻辑AND值,并使用它来控制bool ICommand.CanExecute(对象参数)方法实现。我不想等待所有谓词产生,它应该使用任何一个源谓词流OnNexts(产生一个值)。

我试图找出如何连接它以使任何谓词都应该导致ICommand.CanExecute产生新值时,我有点困惑。

忘记实际的ICommand实现一分钟(因为我的问题更多是关于Rx方面的事情),任何人都可以建议我如何连接一堆谓词(IObservable&lt; bool&gt;),每当底层的东西产生创建他们的流更改,但也将协同工作以创建我可以订阅的整体结束bool值。结束值将是当前谓词流值的逻辑AND。

我希望我不需要订阅所有的谓词流,并希望在RX中有一个很酷的操作符,我可能会忽略它。

我知道我可以合并流,但这不是我所追求的行为,因为这只是已经合并的输入流的最新值,我也知道我可以CombineLatest,再次这不是很正确,因为它只会在所有组合流产生值时产生。

我想要的是要组合的流,因此任何更改都会通知订阅者,但我也想知道组合谓词IObservable&lt; bool&gt;的逻辑AND。流是现在,这样我就可以从这个整体组合值中驱动ICommand.CanExecute。

我希望这是有道理的。

这是一些骨架代码(我留下了一些注释掉的代码,显示了我的Rx Command背后的想法,因为它可能有助于说明我想要工作的内容)

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

    public ViewModel()
    {
        //Initialise some command with 1st predicate, and 
        // initial CanExecute value
        //SomeCommand = new ReactiveCommand(
        //    this.ObserveProperty(x => x.Title)
        //        .Select(x => !string.IsNullOrEmpty(x)), false);
        //SomeCommand.AddPredicate(this.ObserveProperty(x => x.HasStuff));
        //SomeCommand.CommandExecutedStream.Subscribe(x =>
        //    {
        //        MessageBox.Show("Command Running");
        //    });

        IObservable<bool> obsPred = this.ObserveProperty(x => x.Title)
          .Select(x => !string.IsNullOrEmpty(x))
          .StartWith(!string.IsNullOrEmpty(this.Title));
        IObservable<bool> obsPred2 = this.ObserveProperty(x => 
          x.HasStuff).StartWith(this.HasStuff);


        obsPred.Merge(obsPred2).Subscribe(x =>
            {
                //How do I get this to fire whenever obsPred OR 
                //obsPred2 fire OnNext, but also get a combined value (bool) 
                //of the AND of obsPred & obsPred2 (bearing in mind I may 
                //want more than 2 predicates, it should cope with any number of
                //IObservable<bool> predicates
            });
    }

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

}

2 个答案:

答案 0 :(得分:1)

您正在寻找CombineLatest运营商

ISubject<bool> obsPred  = new BehaviorSubject<bool>(false);
ISubject<bool> obsPred2 = new BehaviorSubject<bool>(false);

Observable.CombineLatest(obsPred, obsPred2, (a, b)=>a&&b)
        .DistinctUntilChanged()
        .Dump();

obsPred.OnNext(true);
obsPred2.OnNext(true);
obsPred2.OnNext(true);
obsPred.OnNext(true);

obsPred.OnNext(false);

这将输出

False
True
False

使用DistinctUntilChanged()将停止返回重复的连续值。

显然,为你的财产可观察性换掉了BehaviorSubject

答案 1 :(得分:1)

好的,这就是我成功完成这项工作的方式

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(IObservable<bool> initPredicate, bool initialCondition)
    {
        if (initPredicate != null)
        {
            canExecuteObs = initPredicate;
            SetupSubscriptions();
        }
        RaiseCanExecute(initialCondition);
    }


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


    public ReactiveCommand()
    {
         RaiseCanExecute(true);
    }


    private void SetupSubscriptions()
    {

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



    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(); }
    }


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

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

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

我在哪里使用以下帮助

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

    public static IObservable<TValue> 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<string> ObservePropertyChanged<T>(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;
    }
}

这是一个如何连线的例子

以下是ViewModel

的示例
public class ViewModel : INPCBase
{
    private string title;
    private bool hasStuff;

    public ViewModel()
    {
        IObservable<bool> initPredicate = this.ObserveProperty(x => x.Title).Select(x => !string.IsNullOrEmpty(x)).StartWith(!string.IsNullOrEmpty(this.Title));
        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);
        }
    }

}

这是一个示例视图

<Window x:Class="RxCommand.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <StackPanel Orientation="Horizontal" Height="60" VerticalAlignment="Top">
            <CheckBox IsChecked="{Binding HasStuff, Mode=TwoWay}" Margin="10"></CheckBox>
            <TextBox Text="{Binding Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="150" Margin="10"></TextBox>
            <Button Command="{Binding SomeCommand}" Width="150" Margin="10"></Button>


        </StackPanel>
    </Grid>
</Window>