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