Установите SelectedItem после обновления SourceList / ReadOnlyObservableCollection - PullRequest
0 голосов
/ 06 марта 2020

Используя ReactiveUI / DynamicData, я не могу понять, как установить SelectedItem ComboBox после обновления ItemSource. Я использую SourceList<T> для хранения изменяемой коллекции вместе с Connect / Bind / Subscribe для обновления ReadOnlyObservableCollection, который связан с ComboBox.ItemSource.

ComboBox связан так в представлении:

            this.OneWayBind(ViewModel, vm => vm.EventYearsList, v => v.EventYearsComboBox.ItemsSource).DisposeWith(d);
            this.Bind(ViewModel, vm => vm.SelectedEventYear, v => v.EventYearsComboBox.SelectedItem).DisposeWith(d);

Во ViewModel я определяю ReactiveCommand для обновления SourceList, SourceList для хранения данных, возвращаемых из сервисного уровня, ReadOnlyObservableCollection, который связан на ComboBox.ItemSource и свойство для хранения SelectedItem.

    public ReactiveCommand<Unit, Unit> GetEventYearsListCommand { get; }

    private readonly SourceList<short> _eventYearsSourceList = new SourceList<short>();

    private readonly ReadOnlyObservableCollection<short> _eventYearsList;
    public ReadOnlyObservableCollection<short> EventYearsList => _eventYearsList;

    private short? _selectedEventYear;
    public short? SelectedEventYear
    {
        get => _selectedEventYear;
        set => this.RaiseAndSetIfChanged(ref _selectedEventYear, value);
    }

В конструкторе ViewModel я настроил подписки:

        GetEventYearsListCommand = ReactiveCommand.CreateFromTask(ExecuteGetEventYearsListCommand);
        GetEventYearsListCommand.ThrownExceptions.Subscribe(ex => Trace.WriteLine(ex.ToString()));

        _eventYearsSourceList
            .Connect()
            .ObserveOn(RxApp.MainThreadScheduler)
            .Bind(out _eventYearsList)
            .Subscribe();

        // when a series is selected, this fires off the command to update the event years
        this.WhenAny(x => x.SelectedSeries, _ => Unit.Default)
            .ObserveOn(RxApp.MainThreadScheduler)
            .InvokeCommand(GetEventYearsListCommand);

Обратите внимание, что эта команда вызывается, когда пользователь меняет выбранный элемент на другой ListBox ...

И, наконец, команда для обновления SourceList выглядит следующим образом:

   private async Task ExecuteGetEventYearsListCommand()
    {
        var eventYearsList = new List<short>();
        if (SelectedSeries != null)
        {
            eventYearsList = await seriesApi.QueryEventYears(SelectedSeries.OrgSeriesPath);
        }

        _eventYearsSourceList.Edit(list =>
        {
            list.Clear();
            if (eventYearsList != null) list.AddRange(eventYearsList);
        });

        // This trace shows the correct number of items in the list
        Trace.WriteLine($"_eventYearsSourceList count = {_eventYearsSourceList.Count}");

        // But here, I don't get the right count, EventYearsList has not been updated yet!!!
        Trace.WriteLine($"EventYearsList count = {EventYearsList.Count}");

        // and this check isn't even valid, because EventYearsList hasn't been updated...
        if (EventYearsList?.Count > 0)
        {
            SelectedEventYear = EventYearsList.First();
        }
    }

Итак, глядя на команда непосредственно выше ... Я обновил SourceList, но ReadOnlyObservableCollection еще не обновился ... Он по-прежнему показывает содержимое SourceList до моего обновления.

Первая проблема, я даже не получаю первый элемент из недавно обновленного списка, потому что он еще не был обновлен.

T Вторая проблема заключается в том, что, даже если я установлю выбранный элемент прямо сейчас, этот выбор теряется, когда EventYearsList наконец обновляется. Из-за привязки twoway к выбранному элементу (я предполагаю), SelectedEventYear устанавливается равным нулю после того, как EventYearsList наконец обновляется. Так что даже если бы я мог установить выбранный элемент прямо сейчас, он был бы перезаписан. (Я подтвердил это поведение, установив точку останова в установщике свойств SelectedEventYear)

Либо я делаю что-то не так ... Или мне нужно найти способ (отдельно) подключить что-то, что говорит мне, когда основной источник для ReadOnlyObservableCollection изменился.

Итак, вернемся к первоначальному вопросу ... Как мне установить SelectedItem ПОСЛЕ обновления на ItemSource?

Ответы [ 2 ]

1 голос
/ 11 марта 2020

Обычно я делал бы что-то подобное, так как это, по-видимому, решало проблему большую часть времени.

    _eventYearsSourceList
        .Connect()
        .ObserveOn(RxApp.MainThreadScheduler)
        .Bind(out _eventYearsList)
        .Do(_=> \\some logic to set the selected item) 
        .Subscribe();

Использование оператора Do не идеально, так как вам нужно будет выбрать элемент из _eventYearsList. Как правило, в реактивном программировании касаться какого-либо состояния объекта за пределами функции не рекомендуется, поскольку это может привести к проблемам параллелизма и состояния. Тем не менее, в этом случае вам может понадобиться первая дата или, возможно, вам потребуется выполнить итерацию наблюдаемой коллекции, но надежная защита заключается в том, что привязка происходит в главном потоке, как и логика c внутри оператора Do, так что вы будете не имеет проблем с параллелизмом.

Другой вариант, конечно, это то, что вы можете применить логи c в Subscribe.

0 голосов
/ 11 марта 2020

Хорошо, поэтому с большим количеством волос и скрежетом зубов, я понял, что работает. Не знаю, если это «хороший» способ сделать это, или если есть «лучший» способ (я, конечно, хотел бы услышать о лучшем способе, если он существует).

Ключ был в том, что я подписаться на изменения SourceList дважды.

Как я делал изначально, Bind SourceList к ReadOnlyObservableCollection, к которому может быть привязано представление / XAML:

        _eventYearsSourceList
            .Connect()
            .ObserveOn(RxApp.MainThreadScheduler)
            .Bind(out _eventYearsList)
            .Subscribe();

Затем выполните другой, который просто ищет любое изменение в SourceList, а затем идет и делает что-то:

        _eventYearsSourceList
            .Connect()
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(async _ => await SelectFirstEventYear());

Это вызовет срабатывание моего метода SelectFirstEventYear(), где я могу установить SelectedEventYear свойство к первому элементу.

Side Примечание:

По какой-то причине установка свойства SelectedEventYear (которое использует RaiseAndSetIfChanged) в этом методе не сработала WhenAny to fire:

        this.WhenAny(x => x.SelectedEventYear, _ => Unit.Default)
            .ObserveOn(RxApp.MainThreadScheduler)
            .InvokeCommand(GetEventsListCommand);

Я не уверен, почему это так ... Каждый раз, когда изменяется выбранный элемент, у меня есть еще один ListBox, который также необходимо обновить, и это то, что GetEventsListCommand делает. В итоге я добавил вызов этой команды в свой метод SelectFirstEventYear(), и все, кажется, играет хорошо.

...