我有一个(外部(模型,它暴露了一个不断变化的List(假设每两秒钟左右(。ViewModel知道该列表正在注册PropertyChange事件。该ViewModel还为UI提供了一个ObservableCollection,用于数据绑定。
+-----------------------------------------------+
| View|
| +-----------+ |
| |Listbox | |
| +-----------+ |
+-----/----------------------------------------+
||
||DataBinding
||
||
+-----||----------------------------------------+
| || ViewModel|
| +--------------------+ +-------------+|
| |ObservableCollection|<--------|ChangeHandler||
| +--------------------+ / +-------------+|
| / ^ |
+-------------------------/------------|--------+
/ |
/ |
Synchronizing Lists | PropertyChanged
|
|
+--------------------------------------|--------+
| +-----+ Model|
| |IList| |
| +-----+ |
| |
+-----------------------------------------------+
原则上,除了不断进行更新外,这种方法效果良好。每次更新时,用户都会失去选择,即每次更新时都会取消选择所有项目。这也就不足为奇了,因为WPF的ListBox"看到"一个新的列表被分配了。
因此,我们必须而不是分配一个新的ObservableCollection,而是将当前Observable Collection的内容与更新的Model合并。List
现在我的问题
- 同步列表-是否有关于如何进行此类合并的最佳实践(或框架((将新项目复制到ObservableCollection,删除丢失的项目,更新更改的项目(
- 所选项目-如何确保ListBox保留当前所选项目(除了该项目已删除的情况(
您可以从更新的模型列表中生成一个新的ObservableCollection,也可以将当前的Observable Collection与模型的同步。
如果你选择第二个,你可能需要避免的一件事是为每个同步项目触发CollectionChanged事件。看看这个ObservableCollection实现,它可以延迟通知。
至于保留当前的SelectedItem,如果ObservableCollection的实例没有更改(这是真的,因为我们正在同步集合(,并且SelectedItem实例没有删除,那么列表框应该包含该选择。然而,如果NotifyCollectionChangedEventArgs.Action是"Reset",我不确定这是否是真的。如果是这种情况,您可以使用我使用的方法,即在ViewModel中同时具有colcollection属性和SelectedItem属性。您可以在双向模式中将ViewModel的SelectedItem绑定到ListBox.SelectedItem。同步"集合"时,将SelectedItem保存在临时变量中,然后在同步后重新应用(如果未删除(。
RenéBergelt刚刚找到了一个可以准确处理问题的解决方案:
https://www.renebergelt.de/blog/2019/08/synchronizing-a-model-list-with-a-view-model-list/
/// <summary>
/// An observable collection which automatically syncs to the underlying models collection
/// </summary>
public class SyncCollection<TViewModel, TModel> : ObservableCollection<TViewModel>
{
IList<TModel> modelCollection;
Func<TViewModel, TModel> modelExtractorFunc;
/// <summary>
/// Creates a new instance of SyncCollection
/// </summary>
/// <param name="modelCollection">The list of Models to sync to</param>
/// <param name="viewModelCreatorFunc">Creates a new ViewModel instance for the given Model</param>
/// <param name="modelExtractorFunc">Returns the model which is wrapped by the given ViewModel</param>
public SyncCollection(IList<TModel> modelCollection, Func<TModel, TViewModel> viewModelCreatorFunc, Func<TViewModel, TModel> modelExtractorFunc)
{
if (modelCollection == null)
throw new ArgumentNullException("modelCollection");
if (viewModelCreatorFunc == null)
throw new ArgumentNullException("vmCreatorFunc");
if (modelExtractorFunc == null)
throw new ArgumentNullException("modelExtractorFunc");
this.modelCollection = modelCollection;
this.modelExtractorFunc = modelExtractorFunc;
// create ViewModels for all Model items in the modelCollection
foreach (var model in modelCollection)
Add(viewModelCreatorFunc(model));
CollectionChanged += SyncCollection_CollectionChanged;
}
private void SyncCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// update the modelCollection accordingly
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
for (int i = 0; i < e.NewItems.Count; i++)
modelCollection.Insert(i + e.NewStartingIndex, modelExtractorFunc((TViewModel)e.NewItems[i]));
break;
case NotifyCollectionChangedAction.Remove:
// NOTE: currently this ignores the index (works when there are no duplicates in the list)
foreach (var vm in e.OldItems.OfType<TViewModel>())
modelCollection.Remove(modelExtractorFunc(vm));
break;
case NotifyCollectionChangedAction.Replace:
throw new NotImplementedException();
case NotifyCollectionChangedAction.Move:
throw new NotImplementedException();
case NotifyCollectionChangedAction.Reset:
modelCollection.Clear();
foreach (var viewModel in this)
modelCollection.Add(modelExtractorFunc(viewModel));
break;
}
}
}
使用
// models
class Person
{
public string Name { get; set; }
public string PhoneNumber { get; set; }
}
class Contacts
{
List<Person> People { get; } = new List<Person>();
}
// corresponding view models
class PersonViewModel : ViewModelBase
{
public Person Model { get; }
}
class ContactsViewModel : ViewModelBase
{
ObservableCollection<PersonViewModel> People { get; }
}
为了同步对ObservableCollection的更改,我们使用CollectionChanged事件,使用受影响的ViewModels中提供的函数捕获Models,并对包装的模型列表执行相同的操作。对于我们之前提供的样本类,我们可以这样做:
List<Person> list = new List<Person>() { ... };
ObservableCollection<PersonViewModel> collection =
new SyncCollection<PersonViewModel, Person>(
list,
(pmodel) => new PersonViewModel(pmodel),
(pvm) => pvm.Model);
// now all changes to collection are carried through to the model list
// e.g. adding a new ViewModel will add the corresponding Model in the wrapped list, etc.
SyncCollection
在CollectionChanged处理程序中处理Model
和ViewModel
的添加/删除。