Резюме
У меня есть большой быстро меняющийся набор данных, который я хочу привязать к пользовательскому интерфейсу (Datagrid с группировкой). Изменения происходят на двух уровнях;
Характеристики данных следующие:
Я понимаю, что пользователю не нужно обновлять пользовательский интерфейс тысячи раз в секунду. если элемент добавлен, его состояние изменено и удалено в пределах окна N секунд между обновлениями пользовательского интерфейса, он никогда не должен попадать в DataGrid.
DataGrid
DataGrid - это компонент, который я использую для отображения данных. В настоящее время я использую XCeed DataGrid, поскольку он обеспечивает простую динамическую группировку. Я эмоционально не вкладываюсь в это, стандартный DataGrid был бы прекрасен, если бы я мог предоставить некоторые параметры динамической группировки (которые включают часто меняющиеся свойства).
Узким местом в моей системе является в настоящее время требуется время для повторной сортировки при изменении свойств элемента
Это занимает 98% ЦП в YourKit Profiler.
Другой способ сформулировать вопрос
Учитывая два экземпляра BindingList / ObservableCollection которые изначально были идентичны, но первый список с тех пор содержит серию дополнительные обновления (которые вы можете слушать), сгенерировать минимальный набор изменений, чтобы превратить один список в другое.
Внешнее чтение
Мне нужен эквивалент этого ArrayMonitor Джорджа Трифонаса, но обобщенный для поддержки добавления и удаления элементов (они никогда не будут перемещены).
NB I был бы очень признателен, если бы кто-то отредактировал заголовок вопроса, если бы он мог придумать лучшее резюме.
РЕДАКТИРОВАТЬ - Мое решение
Сетка XCeed привязывает ячейки непосредственно к элементам в сетке, тогда как функции сортировки и группировки управляются с помощью ListChangedEvents, поднятых в BindingList. Это немного противоречит интуиции и исключает приведенный ниже список MontioredBindingList, поскольку строки будут обновляться до групп.
Вместо этого я оборачиваю сами элементы, улавливая события изменения свойства и сохраняю их в HashSet, как предложил Даниэль. Это хорошо работает для меня, Я периодически просматриваю элементы и прошу их уведомлять о любых изменениях.
MonitoredBindingList.cs
Вот моя попытка создать список привязки, который можно опросить на предмет уведомлений об обновлениях. Скорее всего, в нем есть какие-то ошибки, так как в итоге он мне не пригодился.
Он создает очередь событий добавления / удаления и отслеживает изменения через список. Список изменений имеет тот же порядок, что и базовый список, поэтому после того, как мы уведомим об операциях добавления / удаления, вы можете поднять изменения по правильному индексу.
///
/// A binding list which allows change events to be polled rather than pushed.
///
[Serializable]
public class MonitoredBindingList : BindingList
{
private readonly object publishingLock = new object();
private readonly Queue addRemoveQueue;
private readonly LinkedList> changeList;
private readonly Dictionary>> changeListDict;
public MonitoredBindingList()
{
this.addRemoveQueue = new Queue();
this.changeList = new LinkedList>();
this.changeListDict = new Dictionary>>();
}
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());
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++;
}
}
}