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

问题描述 投票:0回答:3

我有一个(外部)模型公开了一个不断变化的列表(假设每两秒左右)。 ViewModel 知道该列表正在注册 PropertyChange 事件。该 ViewModel 还为 UI 提供了一个 ObservableCollection 以进行数据绑定。

+-----------------------------------------------+
|                                           View|
| +-----------+                                 |
| |Listbox    |                                 |
| +-----------+                                 |
+-----/\----------------------------------------+
      ||
      ||DataBinding
      ||
      ||
+-----||----------------------------------------+
|     ||                               ViewModel|
| +--------------------+         +-------------+|
| |ObservableCollection|<--------|ChangeHandler||
| +--------------------+    /    +-------------+|
|                          /           ^        |
+-------------------------/------------|--------+
                         /             |
                        /              |
           Synchronizing Lists         | PropertyChanged
                                       |
                                       |
+--------------------------------------|--------+
|                                  +-----+ Model|
|                                  |IList|      |
|                                  +-----+      |
|                                               |
+-----------------------------------------------+

除了不断进行更新之外,原则上运行良好。每次更新时,用户都会失去他的选择,即每次更新时所有项目都将被取消选择。 这并不奇怪,因为 WPF 的 ListBox“看到”分配了一个新列表。

所以,事情一定是我们不是分配一个新的ObservableCollection,而是将当前ObservableCollection的内容与更新后的Model.List合并

现在是我的问题

  • 同步列表 - 是否有关于如何进行此类合并的最佳实践(或框架) (将新项目复制到 ObservableCollection,删除丢失的项目,更新更改的项目)
  • 选定的项目 - 如何确保列表框保留当前选定的项目(除了该项目被删除的情况)
wpf c#-4.0 user-interface observablecollection synchronization
3个回答
3
投票

您可以从更新的模型列表中生成新的 ObservableCollection,也可以将当前的 ObservableCollection 与模型的 ObservableCollection 同步。

如果您选择第二种,您可能需要避免的一件事是为每个同步项目触发 CollectionChanged 事件。看一下这个 ObservableCollection 实现,它能够推迟通知。

对于保留当前的 SelectedItem,如果 ObservableCollection 的实例未更改(这是真的,因为我们正在同步集合)并且 SelectedItem 实例未删除,则列表框应保留选择。但是,如果 NotifyCollectionChangedEventArgs.Action 为“重置”,我不确定这是否属实。如果是这种情况,您可以使用我使用的方法,即在 ViewModel 中同时具有集合属性和 SelectedItem 属性。您可以在 TwoWay 模式下将 ViewModel 的 SelectedItem 绑定到 ListBox.SelectedItem。同步集合时,您可以将 SelectedItem 保存在临时变量中,然后在同步后重新应用它(如果未删除)。


0
投票

刚刚找到了 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 事件,使用受影响的 ViewModel 中提供的函数捕获模型,并对包装的模型列表执行相同的操作。对于之前提供的示例类,我们可以这样使用:

 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
处理
Model
ViewModel
在 CollectionChanged 处理程序中添加/删除。


0
投票

能够绑定到 ObservableCollection 的 WPF 控件已经具有此代码,因为它们使屏幕上显示的控件集合与 ObservableCollection 中的更改保持同步。 尝试在此处查看“OnMapChanged”方法https://github.com/dotnet/wpf/blob/main/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/ItemContainerGenerator.cs

© www.soinside.com 2019 - 2024. All rights reserved.