Эффективное добавление диапазона значений в ObservableCollection
У меня есть ObservableCollection
элементов, которые привязаны к элементу управления списком в моем представлении.
У меня есть ситуация, когда мне нужно добавить кусок значений в начало коллекции.
Документация Collection<T>.Insert
определяет каждую вставку как операцию O (n), и каждая вставка также генерирует уведомление CollectionChanged
.
Поэтому мне идеально хотелось бы вставить весь диапазон элементов за один ход, что означает только одну перетасовку основного списка и, надеюсь, одно уведомление CollectionChanged
(предположительно "reset" ).
Collection<T>
не предоставляет никакого способа для этого. List<T>
имеет InsertRange()
, но IList<T>
, который Collection<T>
предоставляет через его свойство Items
.
Есть ли вообще способ сделать это?
Ответы
Ответ 1
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);
}
Ответ 2
Чтобы сделать приведенный выше ответ полезным без вывода нового базового класса с использованием отражения, вот пример:
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)});
}
Ответ 3
Этот ответ не показывал мне новые записи в 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[]"));
}
}
Ответ 4
Пример:
Желаемые этапы 0,10,20,30,40,50,60,70,80,90,100
- > min = 0, max = 100, шаги = 11
static int min = 0;
static int max = 100;
static int steps = 11;
private ObservableCollection<string> restartDelayTimeList = new ObservableCollection<string> (
Enumerable.Range(0, steps).Select(l1 => (min + (max - min) * ((double)l1 / (steps - 1))).ToString())
);