Резюме
У меня есть большой быстро меняющийся набор данных, который я хочу привязать к пользовательскому интерфейсу (Datagrid с группировкой).Изменения происходят на двух уровнях:
- Элементы часто добавляются или удаляются из коллекции (500 в секунду в каждую сторону)
- Каждый элемент имеет 4 свойства, которые изменятся до 5раз в жизни
Характеристики данных следующие:
- В коллекции ~ 5000 предметов
- Предмет может, в пределахво-вторых, быть добавленным затем иметь 5 изменений свойств и затем быть удаленным.
- Элемент может также некоторое время оставаться в каком-то промежуточном состоянии и должен отображаться для пользователя.
Ключевое требование, с которым у меня возникают проблемы;
- Пользователь должен иметь возможность сортировать набор данных по любому свойству объекта
Что я хотел бы делать;
- Обновлять интерфейс только каждый раз N секунд
- Поднять только соответствующие NotifyPropertyChangedEvents
Если элемент 1 имеет свойство State, которое перемещается из A -> B -> C ->D в том интервале, в котором я нуждаюсь / хочу, чтобы было вызвано только одно событие изменения 'State', A-> D.
Я ценю, что пользователь не должен обновлять пользовательский интерфейс тысячи развторой.если элемент добавляется, его состояние изменяется и удаляется в течение N секунд между обновлениями пользовательского интерфейса, он никогда не должен попадать в DataGrid.
DataGrid
DataGrid - это компонент, который я использую для отображения данных.В настоящее время я использую XCeed DataGrid, поскольку он обеспечивает динамическую группировку тривиально.Я не вкладываю в это эмоциональные эмоции, но если бы я мог предоставить некоторые параметры динамической группировки (включая свойства, которые часто меняются), было бы неплохо использовать DataGrid.
Узкое место в моей системе в настоящее время находится ввремя, необходимое для повторной сортировки при изменении свойств элемента
Это занимает 98% процессорного времени в YourKit Profiler.
Другой способ сформулировать вопрос
Учитывая два экземпляра BindingList / ObservableCollection, которые были первоначально идентичны, но первый список с тех пор имел ряд дополнительных обновлений (которые вы можете прослушивать), сгенерируйте минимальный набор изменений, чтобы превратить один список вдругое.
Внешнее чтение
Мне нужен эквивалент этого ArrayMonitor Джорджа Трифонаса, но обобщенный для поддержки добавления иудаление элементов (они никогда не будут перемещены).
NB Я был бы очень признателен, кто-то редактирует заголовок квЕсли они могут придумать лучшее резюме.
РЕДАКТИРОВАТЬ - Мое решение
Сетка XCeed связывает ячейки непосредственно с элементами в сетке, тогда какФункциональность сортировки и группировки обеспечивается списками ListChangedEvents, созданными в BindingList.Это немного противоречит интуитивному принципу и исключает приведенный ниже MontioredBindingList, поскольку строки будут обновляться перед группами.
Вместо этого я обертываю сами элементы, перехватывая события Изменения свойства и сохраняя их в HashSet, как предложил Даниэль.Это хорошо работает для меня, я периодически перебираю элементы и прошу их уведомлять о любых изменениях.
MonitoredBindingList.cs
Вот моя попытка создания списка привязоккоторый может быть опрошен для уведомлений об обновлениях.Скорее всего, есть некоторые ошибки, поскольку в конце концов это было бесполезно для меня.
Создает очередь событий Add / Remove и отслеживает изменения через список.Список изменений имеет тот же порядок, что и базовый список, так что после того, как мы уведомим об операциях добавления / удаления, вы можете поднять изменения по отношению к нужному индексу.
/// <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++;
}
}
}