替换整个ObservableCollection与另一个ObservableCollection。

12
public class Alpha
{
    public ObservableCollection<Beta> Items { get; set; }

    public Alpha()
    {
        Items = new ObservableCollection<Beta>();
    }

    public void DoSomething()
    {
        Items = GetNewItems();  // whenever I do this, Items gets a new referene, 
                                // so every WPF binding (e.g. datagrids) are broken
    }

    public ObservableCollection<Beta> GetNewItems()
    {
         var ret = new ObservableCollection<Beta>();
         // some logic for getting some items from somewhere, and populating ret
         return ret;
    }
}

我如何替换Items的整个内容为GetNewItems()的返回值,同时不会:

  1. 破坏绑定关系。

  2. 需要循环遍历items并将它们逐个复制到其他集合中?


嘿,@user2270404,这是用.NET 4.5完成的吗? - Tyler Morrow
@The Red Lou:不,这是在4.0中完成的。 - Adam Szabo
我只是想让你知道,我根据反馈更新了我的答案,如果你有兴趣可以看一下。我找到了其他实现方法并提供了示例,但第三种方法需要 .NET 4.5。 - Tyler Morrow
@The Red Lou:非常好,谢谢! - Adam Szabo
3个回答

12
你有几个选项:
  1. 实现INotifyPropertyChanged接口,以便通知UI Items的值已更改。这不利用ObservableCollection上实现的INotifyCollectionChanged。它会工作,但它失去了使用ObservableCollection的初衷。不建议此方法但它可以工作。
  2. 使用ObservableCollection的Add / Remove / Modify / Update方法来修改它,并与Dispatcher一起使用。
    • 注意:没有Dispatcher,你将会得到一个NotSupportedException,因为CollectionView不支持从与Dispatcher线程不同的线程对它们的SourceCollection进行更改。
  3. 使用ObservableCollection的Add / Remove / Modify / Update方法来修改它,并与BindingOperations.EnableCollectionSynchronization一起使用。 推荐
    • 注意:仅在.NET 4.5中可用。
    • 这是使用Dispatcher的替代方法,同时避免了NotSupportedException。
    • 示例

针对你的问题,第2和第3个选项转换为清除现有项目(Clear()),然后添加(Add())由任何你想要的方法返回的项目 - 参见#3的示例。关键是必须使用Dispatcher(2)或调用BindingOperations.EnableCollectionSynchronization来完成清除和所有添加。祝你好运!

参考:Reed Copsey答案-StackOverflow


4
您也可以创建自己的类,它将扩展ObservableCollection,在这里是一个带有排序通知的示例: https://github.com/jamesmontemagno/mvvm-helpers/blob/master/MvvmHelpers/ObservableRangeCollection.cs 我正在使用比上面更简单的实现(我尚未比较通知方面)。
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using BaseLibrary.Properties;

namespace BaseLibrary
{
/// <summary> 
/// Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed. 
/// </summary> 
/// <typeparam name="T"></typeparam> 
public class ObservableCollectionEx<T> : ObservableCollection<T>
{
    //INotifyPropertyChanged interited from ObservableCollection<T>
    #region INotifyPropertyChanged

    protected override event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    public void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion INotifyPropertyChanged

    /// <summary> 
    /// Adds the elements of the specified collection to the end of the ObservableCollection(Of T). 
    /// </summary> 
    public void AddRange(IEnumerable<T> collection)
    {
        if (collection == null) throw new ArgumentNullException(nameof(collection));

        foreach (var i in collection) Items.Add(i);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    /// <summary> 
    /// Removes the first occurence of each item in the specified collection from ObservableCollection(Of T). 
    /// </summary> 
    public void RemoveRange(IEnumerable<T> collection)
    {
        if (collection == null) throw new ArgumentNullException(nameof(collection));

        foreach (var i in collection) Items.Remove(i);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    /// <summary> 
    /// Clears the current collection and replaces it with the specified item. 
    /// </summary> 
    public void Replace(T item)
    {
        Replace(new T[] { item });
    }

    /// <summary> 
    /// Replaces all elements in existing collection with specified collection of the ObservableCollection(Of T). 
    /// </summary> 
    public void Replace(IEnumerable<T> collection)
    {
        if (collection == null) throw new ArgumentNullException(nameof(collection));

        Items.Clear();
        foreach (var i in collection) Items.Add(i);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    /// <summary> 
    /// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class. 
    /// </summary> 
    public ObservableCollectionEx()
        : base() { }

    /// <summary> 
    /// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection. 
    /// </summary> 
    /// <param name="collection">collection: The collection from which the elements are copied.</param> 
    /// <exception cref="System.ArgumentNullException">The collection parameter cannot be null.</exception> 
    public ObservableCollectionEx(IEnumerable<T> collection)
        : base(collection) { }
}

}


2

ObservableCollection实现了INotifyCollectionChanged接口,当添加或删除项目时,它会更新绑定。在这里所需要的就是清除列表以触发CollectionChanged事件。

public void GetNewItems()
{
     Items.Clear();

     // some logic for getting some items from somewhere, and populating ret

}

1
注意:这可能会引发NotSupportedException异常。 - Tyler Morrow
@TheRedLou 你能解释一下吗?我不知道这个。 - jamesSampica
@TheRedLou 这个问题中没有提到非 UI 线程的任何内容。 - jamesSampica
@TheRedLou 在这个问题中哪里说我们正在处理非UI线程? - jamesSampica
@TheRedLou,你说的话毫无意义。你需要回去重新审视一下非UI线程和其他基本原则的含义。基于此,你不应该对一个完全正确的答案进行投票。 - jamesSampica
让我们在聊天中继续这个讨论 - jamesSampica

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接