【问题标题】:How to combine multiple IObservable<bool> to form composite bool subscription value如何组合多个 IObservable<bool> 组成复合 bool 订阅值
【发布时间】:2013-10-03 15:53:42
【问题描述】:

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

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

我在试图弄清楚如何连接它以使任何谓词都应该导致 ICommand.CanExecute 产生一个新值时遇到了一些困难。

暂时忘记实际的 ICommand 实现(因为我的问题更多是关于 Rx 方面的事情),谁能建议我如何连接一堆谓词( IObservable)创建他们的流更改,但也将协同工作以创建我也可以订阅的整体最终布尔值。结束值将是当前谓词流值的逻辑与。

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

我知道我可以合并流,但这不是我所追求的行为,因为这只是来自已合并的输入流的最新值,我也知道我可以合并最新,这也不完全是正确,因为它只会在所有组合流产生值时才会产生。

我想要的是要组合的流,因此任何更改都会通知订阅者,但我也想知道组合谓词 IObservable 流的逻辑与现在是什么,这样我就可以驱动 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);
        }
    }

}

【问题讨论】:

    标签: wpf system.reactive


    【解决方案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() 将停止返回重复的连续值。

    显然,将 BehaviorSubjects 替换为您的属性 observables。

    【讨论】:

    • Lee 我确实尝试过 CombineLatest/DistinctUntilChanged 组合,但这并不意味着您必须等待所有输入流产生。老实说,我尝试了一些我可能会感到困惑的事情。我会在早上试一试。我相信你是对的。你是上次。我看到你现在也离开了加拿大银行。喜欢新地方吗?至于我,我肯定会进入 Rx,它会让你上瘾,你在这里想一下,然后它突然在那里很好。我认为这没问题。
    • 啊哈,我想我知道为什么我尝试使用 CombineLatest 没有按预期工作。从您的博客“CombineLatest 扩展方法允许您从两个序列中获取最新值,并使用给定函数将这些值转换为结果序列的值。每个输入序列都有缓存的最后一个值,如 Replay(1)。一次两个序列都产生了至少一个值,每次序列产生一个值时,每个序列的最新输出都会传递给 resultSelector 函数......”所以我认为我需要使用 StartsWith(false) 作为初始值
    • 我明天还是会试一试,让你知道,一旦我知道你的答案是正确的,就给你投票(肯定会;-))
    • 好吧,终于明白了,我认为这取决于我如何存储组合流,我认为我还需要重新订阅。我已经在下面发布了工作代码,也许如果你想给它一次,那会很好。
    【解决方案2】:

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

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

    这是一个如何连接它的示例

    这是一个示例视图模型

    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>
    

    【讨论】:

    • 我假设您已经从现有的 sn-ps/库中提取了大部分代码!否则那是一堆代码,只是做 ICommand 做的事情很简单。 ;-)
    • 我还要补充一点,将 StartWith 换成 DRY :this.ObserveProperty(x =&gt; x.Title).StartWith(this.Title).Select(x =&gt; !string.IsNullOrEmpty(x));
    • Lee 我完全明白你所说的 DRY 评论是什么意思。我会修改的。至于一堆代码注释,我不确定我是否遵循那个。我想我需要大部分的东西。您认为可以从中删除什么。只是好奇想知道。它的大部分用于观察属性的辅助工具,也许你是这个意思?
    • 是的,帮手的东西。 ReactiveCommand 可能是对 ReactiveUI 的重新实现,而其他东西看起来像是来自 Rxx。
    • 助手的东西从这里到处拼凑在一起(主要是 Keith woods 博客),反应式 UI 确实有反应式命令,但我希望能够添加它不允许的谓词,所以这就是这就是一切,现在一切都很好,再次感谢
    猜你喜欢
    • 1970-01-01
    • 2012-12-08
    • 2011-01-08
    • 1970-01-01
    • 1970-01-01
    • 2012-11-11
    • 1970-01-01
    • 1970-01-01
    • 2013-03-09
    相关资源
    最近更新 更多