#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. Действительно, это тоже работает. Спасибо за ваше расследование и ваш тестовый пример!