web-dev-qa-db-ja.com

ObservableCollectionに値の範囲を効率的に追加する

私のビューのリストコントロールにバインドされているObservableCollectionのアイテムがあります。

コレクションの先頭に値のチャンクを追加する必要がある状況があります。 _Collection<T>.Insert_のドキュメントでは、各挿入をO(n)操作として指定しており、各挿入はCollectionChanged通知も生成します。

したがって、理想的には、全範囲のアイテムを1回の移動で挿入します。つまり、基になるリストの1つのシャッフルと、できれば1つのCollectionChanged通知(おそらく「リセット」)を挿入します。

_Collection<T>_は、これを行うためのメソッドを公開していません。 _List<T>_にはInsertRange()がありますが、_IList<T>_は、Itemsプロパティを介して公開する_Collection<T>_にはありません。

これを行う方法はありますか?

28
GazTheDestroyer

ObservableCollectionは保護されたItemsプロパティを公開します。これは、通知セマンティクスなしで基礎となるコレクションです。これは、ObservableCollectionを継承することで、希望どおりのコレクションを構築できることを意味します。

class RangeEnabledObservableCollection<T> : ObservableCollection<T>
{
    public void InsertRange(IEnumerable<T> items) 
    {
        this.CheckReentrancy();
        foreach(var item in items)
            this.Items.Add(item);
        this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
}

使用法:

void Main()
{
    var collection = new RangeEnabledObservableCollection<int>();
    collection.CollectionChanged += (s,e) => Console.WriteLine("Collection changed");
    collection.InsertRange(Enumerable.Range(0,100));
    Console.WriteLine("Collection contains {0} items.", collection.Count);  
}
52
driis

上記の答えをリフレクションを使用して新しい基本クラスを導出せずに役立つようにするために、以下に例を示します。

public static void InsertRange<T>(this ObservableCollection<T> collection, IEnumerable<T> items)
{
  var enumerable = items as List<T> ?? items.ToList();
  if (collection == null || items == null || !enumerable.Any())
  {
    return;
  }

  Type type = collection.GetType();

  type.InvokeMember("CheckReentrancy", BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic, null, collection, null);
  var itemsProp = type.BaseType.GetProperty("Items", BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance);
  var privateItems = itemsProp.GetValue(collection) as IList<T>;
  foreach (var item in enumerable)
  {
    privateItems.Add(item);
  }

  type.InvokeMember("OnPropertyChanged", BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic, null,
    collection, new object[] { new PropertyChangedEventArgs("Count") });

  type.InvokeMember("OnPropertyChanged", BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic, null,
    collection, new object[] { new PropertyChangedEventArgs("Item[]") });

  type.InvokeMember("OnCollectionChanged", BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic, null, 
    collection, new object[]{ new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)});
}
7
outbred

この answer では、DataGridの新しいエントリは表示されませんでした。このOnCollectionChangedは私にとってはうまくいきます:

public class SilentObservableCollection<T> : ObservableCollection<T>
{
    public void AddRange(IEnumerable<T> enumerable)
    {
        CheckReentrancy();

        int startIndex = Count;

        foreach (var item in enumerable)
            Items.Add(item);

        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<T>(enumerable), startIndex));
        OnPropertyChanged(new PropertyChangedEventArgs("Count"));
        OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
    }
}
3
Thomas Christof