迁移到 6.5.0 后禁用 ReactiveCommand

ReactiveCommand disabled after migration to 6.5.0

我正在从反应式 4.5 迁移到 6.5.0,我遇到了一些问题。我有一个 WPF 应用程序,其按钮绑定到 ReactiveCommand。以前我是这样使用 ReactiveCommand 构造函数的:

 _runProcessCommand = new ReactiveCommand(CanRunProcess(null));
 _runProcessCommand.Subscribe(RunImpl);

 public IObservable<bool> CanRunProcess(object arg)
{
    return this.WhenAny( ... )
}

现在我把它改成:

_runProcessCommand = ReactiveCommand.Create(CanRunProcess(null));
_runProcessCommand..Subscribe(RunImpl);

所以我希望行为应该完全相同,但事实并非如此。我的按钮被禁用,直到我从 WhenAny in CanRunProcess 更改一些基本上是 UI 的属性。它发生在项目中的许多地方,所以没有错误。这两种创建 ReactiveCommand 的方式有什么不同吗?如何达到相同的结果?有趣的是,当我订阅 CanExecuteObservable 时,它​​按预期工作:

_runProcessCommand.CanExecuteObservable.Subscribe(x =>
            {
               Debug.WriteLine(x);
            });

当我显式调用 CanExecute 时也是如此:

  var c = _runProcessCommand.CanExecute(null);

我想这可能与某个地方的懒惰有关,但我不明白为什么会这样,因为按钮应该调用 CanExecute 来获取当前初始值。

当我订阅 CanRunProcess 时,我得到很多错误,然后是很多正确,最后一个值是正确的,我怀疑应该启用该命令。

  CanRunProcess(null).Subscribe(x =>
            {
                Debug.WriteLine(x);
            });

编辑: 我已经下载了 ReactiveUI 资源,我注意到没有订阅 canExecute 而是使用 Do 函数:

 this.canExecute = canExecute.CombineLatest(isExecuting.StartWith(false), (ce, ie) => ce && !ie)
                .Catch<bool, Exception>(ex => {
                    exceptions.OnNext(ex);
                    return Observable.Return(false);
                })
                .Do(x => {
                    var fireCanExecuteChanged = (canExecuteLatest != x);
                    canExecuteLatest = x;

                    if (fireCanExecuteChanged) {
                        this.raiseCanExecuteChanged(EventArgs.Empty);
                    }
                })
                .Publish();

看起来需要实例化它 - 需要调用

CanExecuteObservableCanExecute 实例化 canExecute 对象。为什么在绑定到按钮时没有创建它?

调试 ReactiveUI 源后,我确切地知道会发生什么。 Do 是惰性函数,因此在调用 connect 函数之前不会执行处理程序。这意味着当命令绑定到按钮和调用 CanExecute 函数时 canExecuteLatest 将为 false,因此按钮保持禁用状态。

可重现的例子(注意当我用 WhenAny 做同样的例子时它有效):

 public class MainViewModel : ReactiveObject
    {
        private ReactiveCommand<object> _saveCommand;
        private string _testProperty;
        private ReactiveList<string> _ReactiveList;

        public ReactiveCommand<object> SaveCommand
        {
            get
            {
                return _saveCommand;
            }
            set { this.RaiseAndSetIfChanged(ref _saveCommand, value); }
        }


        public ReactiveList<string> ReactiveList
        {
            get
            {
                return _ReactiveList;
            }
            set { this.RaiseAndSetIfChanged(ref _ReactiveList, value); }
        }


        public MainViewModel()
        {
            ReactiveList = new ReactiveList<string>();
            ReactiveList.ChangeTrackingEnabled = true;

            SaveCommand = ReactiveCommand.Create(CanRunSave(null));
            SaveCommand.Subscribe(Hello);

            // SaveCommand.CanExecute(null); adding this line will invoke connect so the next line will run CanSave and enable the button. 

            ReactiveList.Add("sad");
        }

        public void Hello(object obj)
        {

        }

        private IObservable<bool> CanRunSave(object arg)
        {
            return ReactiveList.Changed.Select(x => CanSave());
        }

        private bool CanSave()
        {
            return ReactiveList.Any();
        }
    }



<Window x:Class="WpfApplication8.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>
        <Button Content="test" Command="{Binding SaveCommand}" />
    </Grid>
</Window>


public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new MainViewModel();
        }
    }

即使我向 ReactiveList 添加了一些内容,按钮仍然被禁用。 问题是创建命令和将其绑定到按钮之间的更新被忽略,因为没有调用连接,所以根本不会反映更改。

您示例中的问题是 ReactiveList<T> 上的 Changed 事件本质上是 hot observable。即使没有观察者订阅,它也会产生变化。当观察者订阅时,之前的任何更改都将被遗漏。

这样做的结果是 CanRunSave 的订户将 不会 获得任何初始值。收到的第一个值将是 ReactiveList 第一次更改的结果(例如下一个 addition/removal)after 订阅。

由于 ReactiveCommand 中的惰性, CanExecute 之前对列表的任何更改都会被调用(这是订阅可观察对象时) 将被错过。在订阅时,将没有初始值,因此命令的 'can execute' 状态将是默认的 false 直到列表被更改。

修复非常简单 - 确保订阅有一个初始值。您可以使用 StartWith:

private IObservable<bool> CanRunSave(object arg)
{
    return ReactiveList.Changed.Select(_ => Unit.Default)
        .StartWith(Unit.Default)
        .Select(_ => CanSave());
}