草庐IT

c# - 支持定期通知的自定义 ObservableCollection<T> 或 BindingList<T>

coder 2024-06-03 原文

总结

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

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

数据的特点如下;

  • 馆藏中约有 5000 件元素
  • 可以在一秒钟内添加一个项目,然后进行 5 次属性更改,然后再将其删除。
  • 项目也可能会保持某种临时状态一段时间,并且应该向用户显示。

我遇到问题的关键要求;

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

我想做什么;

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

If item 1 has a property State which moves from A -> B -> C -> D in the interval I need/want only one 'State' change event to be raised, A->D.

我很感激用户不需要让 UI 每秒更新数千次。如果在 UI 更新之间的 N 秒窗口内添加了一个项目,更改了它的状态并删除了所有项目,那么它永远不会到达 DataGrid。

数据网格

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

The bottleneck in my system is currently in the time taken to re-sort when an item's properties change

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

表达问题的不同方式

Given two BindingList / ObservableCollection instances which were initially identical but the first list has since had a series of additional updates (which you can listen for), generate the minimal set of changes to turn one list into the other.

课外阅读

我需要的是这个 ArrayMonitor 的等价物由 George Tryfonas 编写,但概括为支持添加和删除项目(它们永远不会被移动)。

注意 如果有人能想出更好的摘要,我将非常感谢编辑问题标题的人。

编辑 - 我的解决方案

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

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

MonitoredBindingList.cs

这是我对绑定(bind)列表的尝试,可以轮询更新通知。它可能存在一些错误,因为它最终对我没有用。

它创建一个添加/删除事件队列,并通过列表跟踪更改。 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++;
        }
    }
}

最佳答案

我们在这里讨论两件事:

  1. 集合的变化。这会引发事件 INotifyCollectionChanged.CollectionChanged
  2. 项目属性的变化。这会引发事件 INotifyPropertyChanged.PropertyChanged

界面INotifyCollectionChanged需要由您的自定义集合来实现。界面INotifyPropertyChanged需要由您的项目实现。此外,PropertyChanged事件只会告诉您某个项目的哪个属性发生了更改,但不会告诉您之前的值是多少。
这意味着,您的项目需要有一个类似这样的实现:

  • 有一个每 N 秒运行一次的计时器
  • 创建 HashSet<string>其中包含已更改的所有属性的名称。因为是集合,所以每个属性只能包含一次或零次。
  • 更改属性时,将其名称添加到哈希集中(如果哈希集中不存在)。
  • 当计时器结束时,提高 PropertyChanged哈希集中所有属性的事件,然后将其清除。

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

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

关于c# - 支持定期通知的自定义 ObservableCollection<T> 或 BindingList<T>,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5310190/

有关c# - 支持定期通知的自定义 ObservableCollection<T> 或 BindingList<T>的更多相关文章

  1. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

    我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

  2. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  3. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

    我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

  4. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  5. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  6. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

  7. ruby-on-rails - rspec should have_select ('cars' , :options => ['volvo' , 'saab' ] 不工作 - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion在首页我有:汽车:VolvoSaabMercedesAudistatic_pages_spec.rb中的测试代码:it"shouldhavetherightselect"dovisithome_pathit{shouldhave_select('cars',:options=>['volvo','saab','mercedes','audi'])}end响应是rspec./spec/request

  8. ruby - 在 Ruby 中有条件地定义函数 - 2

    我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin

  9. ruby - 定义方法参数的条件 - 2

    我有一个只接受一个参数的方法:defmy_method(number)end如果使用number调用方法,我该如何引发错误??通常,我如何定义方法参数的条件?比如我想在调用的时候报错:my_method(1) 最佳答案 您可以添加guard在函数的开头,如果参数无效则引发异常。例如:defmy_method(number)failArgumentError,"Inputshouldbegreaterthanorequalto2"ifnumbereputse.messageend#=>Inputshouldbegreaterthano

  10. ruby - 如何在 Grape 中定义哈希数组? - 2

    我使用Ember作为我的前端和GrapeAPI来为我的API提供服务。前端发送类似:{"service"=>{"name"=>"Name","duration"=>"30","user"=>nil,"organization"=>"org","category"=>nil,"description"=>"description","disabled"=>true,"color"=>nil,"availabilities"=>[{"day"=>"Saturday","enabled"=>false,"timeSlots"=>[{"startAt"=>"09:00AM","endAt"=>

随机推荐