将不断变化的模型列表同步到ViewModel的ObservableList的最佳实践



我有一个(外部(模型,它暴露了一个不断变化的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处理程序中处理ModelViewModel的添加/删除。

相关内容

  • 没有找到相关文章

最新更新