更新后设置 SelectedItem SourceList/ReadOnlyObservableCollection

Set SelectedItem after updating SourceList/ReadOnlyObservableCollection

使用 ReactiveUI/DynamicData,我在更新 ItemSource 后无法弄清楚如何设置 ComboBox 的 SelectedItem。我正在使用 SourceList<T> 来保存可变集合以及 Connect/Bind/Subscribe 来更新绑定到 ComboBox.ItemSource.

ReadOnlyObservableCollection

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还没有更新...它仍然显示[=的内容22=] 在我更新之前。

第一个问题是,我什至没有从新更新的列表中获取第一项,因为它还没有更新。

第二个问题是,即使我现在设置了所选项目,当 EventYearsList 最终更新时,该选择也会丢失。由于所选项目的双向绑定(我假设),SelectedEventYear 在 EventYearsList 最终更新后设置为 null。所以即使我现在可以设置所选项目,它也会被覆盖。 (我已通过在 SelectedEventYear 属性 setter 上设置断点来确认此行为)

要么我做错了什么......要么我需要找到一种方法来(单独)挂钩一些东西,当 ReadOnlyObservableCollection 的基础源发生变化时告诉我。

所以回到最初的问题...如何在更新后将 SelectedItem 设置为 ItemSource

好吧,在扯头发和咬牙切齿的情况下,我想出了一些可行的方法。不知道这是 "good" 的方法,还是有 "better" 的方法(如果有更好的方法,我当然想听听)。

关键是我订阅了两次 SourceList 的更改。

曾经像我最初做的那样 Bind SourceList 到 view/XAML 可以绑定到的 ReadOnlyObservableCollection

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

然后做另一个简单地寻找对 SourceList 的任何更改,然后去做一些事情:

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

这将触发我的 SelectFirstEventYear() 方法,我可以在其中将 SelectedEventYear 属性 设置为第一项。

旁注:

出于某种原因,在此方法中设置 SelectedEventYear 属性(利用 RaiseAndSetIfChanged)并未触发此 WhenAny 触发:

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

我不确定为什么会这样...每当所选项目发生变化时,我还有另一个 ListBox 也需要更新,这就是 GetEventsListCommand 所做的。我最终在我的 SelectFirstEventYear() 方法中添加了对该命令的调用,一切似乎都运行良好。

通常我会这样做,因为它似乎大部分时间都能解决问题。

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

使用 Do 语句并不理想,因为您需要从 _eventYearsList 中选择一个项目。通常在响应式编程中,不建议在函数之外接触某些对象状态,因为这样做会引入并发和状态问题。但是,在这种情况下,您可能需要第一个日期,或者您可能需要迭代可观察集合,但安全措施是绑定发生在主线程上,Do 语句中的逻辑也是如此,因此您将没有并发问题。

当然还有另一种选择,您可以在 Subscribe.

中应用逻辑