AutoRefreshOnObservable работает только один раз. Почему?

#wpf #dynamic-data #reactiveui

#wpf #динамические данные #reactiveui

Вопрос:

Я работаю над небольшой программой, в которой оцениваю, подходит ли реактивный пользовательский интерфейс для другого проекта. Пока все хорошо… На данный момент я немного запутался в функции, связанной с DynamicData. Я пытаюсь выполнить команду в MainViewWindow каждый раз, когда изменяется поле со списком в ReactiveUserControl. Все мои модели расширяют ReactiveObject, а свойства настраиваются с помощью установщика RaiseAndSetIfChanged .

В моей ViewModel ReactiveUserControl я вызываю свою команду SaveImage из ViewModel ReactiveUserControl, как описано здесь: https://reactiveui.net/docs/handbook/message-bus/#ways-to-avoid-using-messagebus

Определение ObservableCollection

 
public ObservableCollection<FileViewModel> VisibleFiles { get; protected set; }

  

Инициализируйте коллекцию, Files — это список источников

 
 WatchFiles = ReactiveCommand.Create(() =>
            {
                VisibleFiles = new ObservableCollection<FilesViewModel>(Files.Items);
VisibleFiles.ToObservableChangeSet().AutoRefreshOnObservable(doc => doc.SaveImage).Select(_ => WhenAnyFileChanged()).Switch().Subscribe<FilesViewModel>(x => {
                    Console.WriteLine("HOORAY");
                });

            });

  
 
 private IObservable<FilesViewModel> WhenAnyFileChanged()
        {
            return VisibleFiles.Select(x => x.SaveFile.Select(_ => x )).Merge();
        }

  

При первом изменении поля со списком оно оценивается правильно. Я получаю «Ура». Но каждый раз после этого вывода нет. Если я снова вызову команду Watch Files, она снова сработает один раз.
Почему это происходит, и как я могу решить это, чтобы печатать каждый раз, когда файл менял «Ура»? Я вижу, что ObservableCollection обнаруживает изменение, а также команда в ReactiveUserControl вызывается при изменении. Но метод WhenAnyFileChanged не возвращает измененный элемент после первого вызова.
Надеюсь, понятно, чего я пытаюсь достичь, и в чем проблема.

Обновление: я не знаю почему, но если я проверю набор изменений в Select(), я получу TotalChanges 10 при инициализации, что правильно. Тогда при моем первом рабочем изменении TotalChanges равен 0, но оценивается правильно. При моей следующей попытке изменения я по-прежнему получаю 0 полных изменений, но также нет правильной оценки в WhenAnyFileChanged() . Функция Refreshes() равна 1 при каждом изменении.

Обновление 2: изменение функции AutoRefreshOnObservable() на функцию AutoRefresh() обеспечивает желаемую функциональность.

Ответ №1:

Я скопировал исходный пример шины сообщений и написал модульный тест, чтобы проверить, работает ли код так, как ожидалось. Я могу подтвердить, что проблема, которую вы видите, присутствует в примере. Следующий код запускается только один раз.

 public MainViewModel()
{
    OpenDocuments = new ObservableCollection<DocumentViewModel>();

    OpenDocuments
        .ToObservableChangeSet()
        .AutoRefreshOnObservable(document => document.Close)
        .Select(_ => WhenAnyDocumentClosed())
        .Switch()
        .Subscribe(x => OpenDocuments.Remove(x), ex=>{},()=>{});
}

IObservable<DocumentViewModel> WhenAnyDocumentClosed()
{
    return OpenDocuments
        .Select(x => x.Close.Select(_ => x))
        .Merge();
}
  

И вот тест, чтобы доказать это. При второй попытке удаления происходит сбой.

 [Fact]
public void MyTest()
{
    //I added an id field to help with diagnostics / testing
    _mainViewModel.OpenDocuments.Count.Should().Be(4);
    _mainViewModel.OpenDocuments.Any(dvm => dvm.Id == "1").Should().BeTrue();

    _mainViewModel.OpenDocuments[0].Close.Execute().Subscribe();
    _mainViewModel.OpenDocuments.Count.Should().Be(3);
    _mainViewModel.OpenDocuments.Any(dvm => dvm.Id == "1").Should().BeFalse();


    _mainViewModel.OpenDocuments[0].Close.Execute().Subscribe();
    _mainViewModel.OpenDocuments.Count.Should().Be(2);
    _mainViewModel.OpenDocuments.Any(dvm => dvm.Id == "2").Should().BeFalse();
}
  

Я не уверен, почему это не удается, но наиболее оптимальным решением является использование MergeMany оператора Dynamic Data, который похож на Rx Merge , но автоматически подключает наблюдаемые объекты при добавлении элементов в базовый список и отключает их при удалении элементов. Исправление:

 public class MainViewModel : ReactiveObject
{
    public ObservableCollection<DocumentViewModel> OpenDocuments { get;}

    public MainViewModel()
    {
        OpenDocuments = new ObservableCollection<DocumentViewModel>();

        OpenDocuments
            .ToObservableChangeSet()
            .MergeMany(x => x.Close.Select(_ => x))
            .Subscribe(x => OpenDocuments.Remove(x));
    }
}
  

Запуск одних и тех же модульных тестов проходит.

Код с модульным тестированием доступен в этом gist

Комментарии:

1. Действительно, это тоже работает. Спасибо за ваше расследование и ваш тестовый пример!