【问题标题】:Custom ObservableCollection<T> or BindingList<T> with support for periodic notifications支持定期通知的自定义 ObservableCollection<T> 或 BindingList<T>
【发布时间】:2011-07-15 16:12:57
【问题描述】:

总结

我有一个快速变化的大型数据集,我希望将其绑定到 UI(带分组的数据网格)。变化分为两个层面;

  • 经常从集合中添加或删除项目(单程每秒 500 个)
  • 每个项目都有 4 个属性,在其生命周期内最多会更改 5 次

数据的特点如下;

  • 集合中有大约 5000 个项目
  • 可以在一秒钟内添加一个项目,然后进行 5 次属性更改,然后将其删除。
  • 项目也可能会在某个临时状态下保持一段时间,并应显示给用户。

我遇到问题的关键要求;

  • 用户应该能够按对象上的任何属性对数据集进行排序

我想做什么;

  • 仅每 N 秒更新一次 UI
  • 仅引发相关的 NotifyPropertyChangedEvents

如果项目 1 有一个属性 State 从 A -> B -> C -> D 在 间隔我需要/只想要一个“状态”变化 要引发的事件,A->D。

我很欣赏用户不需要每秒更新数千次 UI。如果添加了一个项目,它的状态发生了变化,并且在 UI 更新之间 N 秒的窗口内全部删除,它不应该命中 DataGrid。

数据网格

DataGrid 是我用来显示数据的组件。我目前正在使用 XCeed DataGrid,因为它可以轻松地提供动态分组。我对它没有感情上的投入,如果我可以提供一些动态分组选项(包括经常更改的属性),股票 DataGrid 会很好。

我系统的瓶颈是 目前正在重新排序 当项目的属性发生变化时

这会占用 YourKit Profiler 中 98% 的 CPU。

问题的不同表述方式

给定两个 BindingList / ObservableCollection 实例 最初是相同的,但 从那以后,第一份名单有一系列 附加更新(您可以 听),生成最小集合 将一个列表变成 其他。

外读

我需要的是与 George Tryfonas 的 ArrayMonitor 等效,但一般化以支持添加和删除项目(它们永远不会被移动)。

请注意,如果有人能想到更好的摘要,我会非常感谢他们编辑问题的标题。

编辑 - 我的解决方案

XCeed 网格将单元格直接绑定到网格中的项目,而排序和分组功能由 BindingList 上引发的 ListChangedEvents 驱动。这有点违反直觉,并排除了下面的 MontioredBindingList,因为行会在组之前更新。

相反,我自己包装项目,捕捉属性更改事件并将它们存储在 HashSet 中,正如 Daniel 建议的那样。这对我来说效果很好,我会定期迭代这些项目并要求他们通知任何更改。

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++;
        }
    }
}

【问题讨论】:

    标签: c# wpf observablecollection large-data-volumes bindinglist


    【解决方案1】:

    我们在这里谈论两件事:

    1. 对集合的更改。这引发了事件INotifyCollectionChanged.CollectionChanged
    2. 项目属性的更改。这引发了事件INotifyPropertyChanged.PropertyChanged

    接口INotifyCollectionChanged需要你的自定义集合实现。接口INotifyPropertyChanged 需要由您的项目实现。此外,PropertyChanged 事件仅告诉您项目上的哪个属性发生了更改,而不是之前的值是什么。
    这意味着,您的项目需要有一个类似这样的实现:

    • 有一个每 N 秒运行一次的计时器
    • 创建一个HashSet&lt;string&gt;,其中包含已更改的所有属性的名称。因为它是一个集合,所以每个属性只能包含一次或零次。
    • 当属性发生变化时,将其名称添加到散列集中(如果它尚未包含在其中)。
    • 当计时器结束时,为哈希集中的所有属性引发PropertyChanged 事件,然后将其清除。

    您的收藏将有类似的实现。然而,这有点困难,因为您需要考虑在计时器事件之间添加和删除的项目。这意味着,当添加一个项目时,您会将其添加到散列集“addItems”中。如果删除了某个项目,则将其添加到“removedItems”哈希集中,如果它尚未在“addItems”中。如果它已经在“ addedItems”中,请将其从那里删除。我想你明白了。

    为了遵守关注点分离和单一职责的原则,最好让您的项目以默认方式实现INotifyPropertyChanged,并创建一个合并事件的包装器。这样做的好处是您的项目不会被不属于那里的代码弄得杂乱无章,并且此包装器可以通用并用于实现 INotifyPropertyChanged 的每个类。
    集合也是如此:您可以为所有实现 INotifyCollectionChanged 的集合创建一个通用包装器,并让包装器合并事件。

    【讨论】:

    • 嗨丹尼尔 - 感谢您如此快速和深入的回复。尽管您已经纠正了我所犯的几个错误,但我一直在努力实现您所描述的。当我完成课程后,我会将其发回此处,以便任何人都可以使用或调整它。不过,我还有一个额外的问题,在我的 CustomObservableCollection 中,当项目更改时我应该引发什么事件?或者我如何将INotifyPropertyChanged 传播到数据网格? NotifyCollectionChangedAction.Replace 似乎不对。
    • @CityView:通知数据网格关于其项目属性的更改不是集合的任务。当您将集合绑定到数据网格时,数据网格会绑定到集合的 CollectionChanged 事件以及显示的每个项目的 PropertyChanged 事件。
    • @Daniel:有道理 - XCeed Grid 不是这种情况,这就是我感到困惑的原因。对于 Exceed,您似乎需要扩展 BindingList。谢谢,我今天会发布我的尝试。
    • @CityView:我明白了。在这种情况下,您的自定义集合最好实现 IBindingList&lt;T&gt; 而不是 INotifyCollectionChanged 并使其订阅添加到列表中的每个项目的 PropertyChanged 事件,然后为具有的每个项目引发 ListChanged 事件已更改的属性。
    • @Daniel:一些吹毛求疵:对于集合实现,当添加一个项目时,需要对称地检查它是否不在已删除的项目中。
    猜你喜欢
    • 2011-09-09
    • 2010-10-02
    • 1970-01-01
    • 1970-01-01
    • 2021-11-04
    • 1970-01-01
    • 1970-01-01
    • 2010-10-03
    • 1970-01-01
    相关资源
    最近更新 更多