我有
ObservableCollection
项绑定到我视图中的列表控件。
我遇到了一种情况,我需要将一大块值添加到集合的开头。
Collection<T>.Insert
文档将每个插入指定为 O(n) 操作,并且每个插入还会生成一个 CollectionChanged
通知。
因此,理想情况下,我希望一次性插入整个范围的项目,这意味着只需对基础列表进行一次洗牌,并希望有一个
CollectionChanged
通知(大概是“重置”)。
Collection<T>
没有公开任何执行此操作的方法。 List<T>
具有 InsertRange()
,但是 IList<T>
,即 Collection<T>
通过其 Items
属性公开却没有。
有什么办法可以做到这一点吗?
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);
}
为了使上述答案有用而无需使用反射派生新的基类,这里有一个示例:
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)});
}
这个 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[]"));
}
}
10 年后,现在我必须在项目中使用 C#,并且我不希望触发多个
OnCollectionChanged
。我已将 @outbred 的答案修改为扩展类。
用途:
var stuff = new ObservableCollection<Stuff>() {...};
...
// will trigger only 1 OnCollectionChanged event
stuff.ReplaceCollection(newThings);
// equivalent without the extension methods
// stuff.Clear(); // triggers 1 OnCollectionChanged
// foreach (var thing in newThings)
// stuff.Add(thing); // triggers multiple OnCollectionChanged
ObservableCollectionExtensions.cs
:
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
namespace System.Collections.ObjectModel
{
public static class ObservableCollectionExtensions
{
private static BindingFlags ProtectedMember = BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic;
private static BindingFlags ProtectedProperty = BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic;
/// <summary>
/// Insert a collection without triggering OnCollectionChanged event
/// </summary>
private static void InsertWithoutNotify<T>(this ObservableCollection<T> collection, IEnumerable<T> items, int index = -1)
{
if (collection == null || items == null || !items.Any()) return;
Type type = collection.GetType();
type.InvokeMember("CheckReentrancy", ProtectedMember, null, collection, null);
PropertyInfo itemsProp = type.BaseType.GetProperty("Items", ProtectedProperty);
IList<T> protectedItems = itemsProp.GetValue(collection) as IList<T>;
// Behave the same as Add if no index is being passed
int start = index > -1 ? index : protectedItems.Count();
int end = items.Count();
for (int i = 0; i < end; i++)
{
protectedItems.Insert(start + i, items.ElementAt(i));
}
type.InvokeMember("OnPropertyChanged", ProtectedMember, null,
collection, new object[] { new PropertyChangedEventArgs("Count") });
type.InvokeMember("OnPropertyChanged", ProtectedMember, null,
collection, new object[] { new PropertyChangedEventArgs("Item[]") });
}
public static void AddRange<T>(this ObservableCollection<T> collection, IEnumerable<T> items)
{
if (collection == null || items == null || !items.Any()) return;
Type type = collection.GetType();
InsertWithoutNotify(collection, items);
type.InvokeMember("OnCollectionChanged", ProtectedMember, null,
collection, new object[] {
// Notify that we've added new items into the collection
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)
});
}
public static void InsertRange<T>(this ObservableCollection<T> collection, int index, IEnumerable<T> items)
{
if (collection == null || items == null || !items.Any()) return;
Type type = collection.GetType();
InsertWithoutNotify(collection, items, index);
type.InvokeMember("OnCollectionChanged", ProtectedMember, null,
collection, new object[] {
// Notify that we've added new items into the collection
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)
});
}
public static void ReplaceCollection<T>(this ObservableCollection<T> collection, IEnumerable<T> items)
{
if (collection == null || items == null || !items.Any()) return;
Type type = collection.GetType();
// Clear the underlaying list items without triggering a change
PropertyInfo itemsProp = type.BaseType.GetProperty("Items", ProtectedProperty);
IList<T> protectedItems = itemsProp.GetValue(collection) as IList<T>;
protectedItems.Clear();
// Perform the actual update
InsertWithoutNotify(collection, items);
type.InvokeMember("OnCollectionChanged", ProtectedMember, null,
collection, new object[] {
// Notify that we have replaced the entire collection
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)
});
}
}
}
示例: 所需步数 0,10,20,30,40,50,60,70,80,90,100 --> 最小值=0,最大值=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())
);