ReactiveCommand отключен после перехода на 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 bound, которое в основном является свойствами пользовательского интерфейса. Это происходит во многих местах проекта, поэтому нет никакой ошибки. Есть ли что-нибудь различное между этими двумя способами создания 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 нет, но вместо этого используется Dofunction:

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

Похоже, что-то нужно создать экземпляр - что-то нужно вызвать

либо CanExecuteObservable, либо CanExecute для создания экземпляра объекта canExecute. Почему он не создается, когда вы привязываете его к кнопке?

После отладки источников ReactiveUI я точно знаю, что происходит. Do - ленивая функция, поэтому, пока не будет вызвана connect функция, обработчик не будет выполняться. Это означает, что canExecuteLatest будет ложным, когда команда привязана к кнопке и при вызове функции CanExecute, поэтому кнопка остается отключенной.

Воспроизводимый пример (обратите внимание, что он работает, когда я делаю тот же пример с 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. Проблема в том, что обновления между созданием команды и привязкой ее к кнопке игнорируются, потому что соединение не было вызвано, поэтому изменения просто не отражаются.


person MistyK    schedule 10.05.2016    source источник
comment
Вы правы, что подписка на наблюдаемый объект «может выполняться» является ленивой, но наблюдаемый объект, созданный с использованием this.WhenAny, всегда должен возвращать начальное значение, поэтому это не должно быть важным. Я думаю, вам нужно привести некоторые подробности, желательно минимально воспроизводимый пример.   -  person Charles Mager    schedule 10.05.2016
comment
Спасибо, Чарльз, за ​​ответ. Я не могу воспроизвести это для меньшего примера. Мне интересно, что когда я подписываюсь на canExecute observable и последнее значение истинно, то команда все равно не активируется. Меня смущает то, как это работает.   -  person MistyK    schedule 10.05.2016
comment
@CharlesMager Я обновил исходное сообщение   -  person MistyK    schedule 10.05.2016
comment
Я не уверен, что ваш анализ верен - при вызове CanExecute наблюдаемое подключается. Если первым результатом вашего наблюдаемого является true, тогда он будет отличаться от canExecuteLatest и будет обновлен / событие будет вызвано / будет возвращено true. Вы сами это проверили, позвонив CanExecute, и он вернул true. Вся «привязка» вызывает этот метод для установки включенного состояния.   -  person Charles Mager    schedule 10.05.2016
comment
Я думаю, вам нужно будет предоставить минимальный воспроизводимый пример, как я предлагал ранее. Я бы не ожидал, что выражение, использующее WhenAny, вернет «много ложных слов, за которыми следует много истин» - вероятно, что-то в способе определения наблюдаемого объекта объясняет проблему.   -  person Charles Mager    schedule 10.05.2016
comment
если вы посмотрите на источники ReactiveUI, вы можете заметить, что canExecute не подписан, но вместо этого они используют функцию Do (в конструкторе ReactiveCommand), а после этого они используют публикацию, которая создает экземпляр подключаемого наблюдаемого. Пока вы не вызовете «connect» для этого объекта, все сообщения функции «Do» будут игнорироваться, поэтому canExecuteLatest останется ложным.   -  person MistyK    schedule 10.05.2016
comment
Акт подключения подписывается на базовый наблюдаемый объект - он не подписан до тех пор, поэтому никакие сообщения не будут «игнорироваться», если только ваш наблюдаемый объект не является «горячим» (т.е. он производит значения, даже если никто не подписан). Это сложно сказать, поскольку вы не указали для этого код, но этого не следует ожидать от простого WhenAny.   -  person Charles Mager    schedule 10.05.2016
comment
Это не совсем просто WhenAny - это более сложно. Я постараюсь привести пример   -  person MistyK    schedule 10.05.2016
comment
@CharlesMager См. Редактирование - кстати, спасибо за помощь, я знаю, что должен был предоставить этот пример раньше, извините за это   -  person MistyK    schedule 10.05.2016
comment
Да, теперь вы можете сразу определить проблему! Ответить на входящий ...   -  person Charles Mager    schedule 10.05.2016


Ответы (1)


Проблема в вашем примере заключается в том, что событие Changed на ReactiveList<T> по сути является горячим наблюдаемым. Он производит изменения, которые происходят, даже если ни один наблюдатель не подписан. Когда наблюдатель подписывается, любые предыдущие изменения будут пропущены.

В результате подписчик на CanRunSave не получит начальное значение. Первое полученное значение будет результатом первого изменения ReactiveList (например, следующего добавления / удаления) после подписки.

В результате лени в ReactiveCommand любые изменения в списке перед вызовом CanExecute (когда наблюдаемый объект подписан) будут пропущены. При подписке начального значения не будет, поэтому состояние команды «можно выполнить» будет по умолчанию false, пока список не будет изменен.

Исправление на удивление простое - убедитесь, что для подписки задана начальная стоимость. Вы можете сделать это с помощью StartWith:

private IObservable<bool> CanRunSave(object arg)
{
    return ReactiveList.Changed.Select(_ => Unit.Default)
        .StartWith(Unit.Default)
        .Select(_ => CanSave());
}
person Charles Mager    schedule 10.05.2016
comment
Спасибо, дружище, я не знал о горячих и холодных подписках. Как я понимаю, благодаря методу StartWith, когда что-то начинает подписываться на наблюдаемый объект, оно получает начальное значение, поэтому условие будет переоценено, это правильно? - person MistyK; 10.05.2016
comment
Да, изменение заключается в создании последовательности, которая всегда имеет начальное значение, а затем любые изменения из списка. Затем он вызывает CanSave для получения логического значения. В результате при подписке вы сразу получаете значение результата CanSave, за которым следует новый результат при каждом изменении списка. - person Charles Mager; 10.05.2016
comment
StartWith очень похож на Observable.Return(initial).Concat(changes) - посмотрите здесь. - person Charles Mager; 10.05.2016
comment
Великолепно, это действительно просто, надо было раньше прочитать документацию :) Спасибо! - person MistyK; 10.05.2016