摘要
我有一个大型且不断变化的数据集,希望将其绑定到 UI(带分组的数据网格)。更改有两个层面:
- 集合中频繁添加或删除项目(每个方向每秒500个)
- 每个项目有4个属性,在其生命周期内最多更改5次
数据的特征如下:
- 集合中约有 5000 个项目
- 一个项目在一秒内可能被添加,然后进行 5 次属性更改,然后被删除。
- 一个项目可能会在某个中间状态下保持一段时间,并应向用户显示。
我遇到问题的主要要求如下:
- 用户应能够按对象的任何属性对数据集进行排序
我希望做到以下几点:
- 每隔 N 秒更新 UI
- 只提高相关的 NotifyPropertyChanged 事件
如果项目 1 具有属性 State, 在间隔期间从 A -> B -> C -> D, 我希望仅引发一个 'State' 更改事件, 即 A->D。
我明白用户不需要在每秒更新 UI。如果一个项目在 UI 更新之间的 N 秒窗口内添加、更改状态并被删除,则它不应出现在数据网格中。
数据网格
数据网格是我用来显示数据的组件。我目前正在使用 XCeed DataGrid,因为它可以提供动态分组。如果可以提供一些动态分组选项(包括经常更改的属性),那么普通 DataGrid 将很好。
我的系统中瓶颈在于当项目属性更改时重新排序所需的时间
这占用了 YourKit Profiler 中 98% 的 CPU。
以不同方式表达问题
给定两个 BindingList / ObservableCollection 实例, 它们最初是相同的,但第一个列表此后已经有了一系列的附加更新(您可以监听), 生成将一个列表转换为另一个列表的最小更改集。
外部阅读材料
我需要类似 George Tryfonas 的 ArrayMonitor,但支持添加和删除项目(它们永远不会移动)。
注:如果有人能想到更好的摘要,请XCeed grid将单元格直接绑定到网格中的项,而排序和分组功能则受BindingList上引发的ListChangedEvents驱动。这略微有些反直觉,并且排除了下面的MonitoredBindingList,因为行会在分组之前更新。
这是我的一个绑定列表的尝试,可以轮询更新通知。它可能存在一些错误,因为最终对我没有用。
它创建了一个添加/删除事件队列,并通过列表跟踪更改。ChangeList与底层列表具有相同的顺序,以便在我们通知添加/删除操作后,可以针对正确的索引引发更改。
/// <summary>
/// A binding list which allows change events to be polled rather than pushed.
/// </summary>
[Serializable]
public class MonitoredBindingList<T> : BindingList<T>
{
private readonly object publishingLock = new object();
private readonly Queue<ListChangedEventArgs> addRemoveQueue;
private readonly LinkedList<HashSet<PropertyDescriptor>> changeList;
private readonly Dictionary<int, LinkedListNode<HashSet<PropertyDescriptor>>> changeListDict;
public MonitoredBindingList()
{
this.addRemoveQueue = new Queue<ListChangedEventArgs>();
this.changeList = new LinkedList<HashSet<PropertyDescriptor>>();
this.changeListDict = new Dictionary<int, LinkedListNode<HashSet<PropertyDescriptor>>>();
}
protected override void OnListChanged(ListChangedEventArgs e)
{
lock (publishingLock)
{
switch (e.ListChangedType)
{
case ListChangedType.ItemAdded:
if (e.NewIndex != Count - 1)
throw new ApplicationException("Items may only be added to the end of the list");
// Queue this event for notification
addRemoveQueue.Enqueue(e);
// Add an empty change node for the new entry
changeListDict[e.NewIndex] = changeList.AddLast(new HashSet<PropertyDescriptor>());
break;
case ListChangedType.ItemDeleted:
addRemoveQueue.Enqueue(e);
// Remove all changes for this item
changeList.Remove(changeListDict[e.NewIndex]);
for (int i = e.NewIndex; i < Count; i++)
{
changeListDict[i] = changeListDict[i + 1];
}
if (Count > 0)
changeListDict.Remove(Count);
break;
case ListChangedType.ItemChanged:
changeListDict[e.NewIndex].Value.Add(e.PropertyDescriptor);
break;
default:
base.OnListChanged(e);
break;
}
}
}
public void PublishChanges()
{
lock (publishingLock)
Publish();
}
internal void Publish()
{
while(addRemoveQueue.Count != 0)
{
base.OnListChanged(addRemoveQueue.Dequeue());
}
// The order of the entries in the changeList matches that of the items in 'this'
int i = 0;
foreach (var changesForItem in changeList)
{
foreach (var pd in changesForItem)
{
var lc = new ListChangedEventArgs(ListChangedType.ItemChanged, i, pd);
base.OnListChanged(lc);
}
i++;
}
}
}
INotifyPropertyChanged
传播到数据网格?NotifyCollectionChangedAction.Replace
似乎不太对。 - CityViewIBindingList<T>
而不是INotifyCollectionChanged
,并使其订阅添加到列表中的每个项目的PropertyChanged事件,然后为每个具有更改属性的项目引发ListChanged事件。 - Daniel Hilgarth