更新后设置 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
.
中应用逻辑
使用 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
.