迁移到6.5.0后,ReactiveCommand被禁用

时间:2016-05-10 08:17:05

标签: c# wpf reactiveui

我从反应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函数之前,处理程序不会被执行。这意味着当命令绑定到按钮并且调用canExecuteLatest函数时CanExecute将为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添加内容,按钮仍然处于禁用状态。 问题是创建命令并将其绑定到按钮之间的更新被忽略,因为没有调用连接,因此根本不会反映更改。

1 个答案:

答案 0 :(得分:1)

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

结果是CanRunSave的订阅者获得任何初始值。收到的第一个值是订阅后第一次更改ReactiveList(例如下次添加/删除)的结果。

由于ReactiveCommand中的懒惰,在 CanExecute之前调用列表的任何更改(即可订阅observable时)都将被遗漏。在订阅时,将没有初始值,因此在更改列表之前,命令的'可执行'状态将是false的默认值。

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

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