增强 WPF ObservableCollection 性能

8

目前我有两个 WPF 列表框,模仿以下功能

Word 2007 customize screen
(来源:psu.edu)

我使用了 2 个 ObservableCollections,以允许用户选择所需的任何项目(灵活性是关键)。主要问题是我有成千上万个分组在两个列表框中。总的来说,设计非常好用(几十个项目),但当用户将所有可用项目从左边复制到右边时,屏幕会冻结(需要在不同线程上运行的时间?)。

查看 ObservableCollection,它缺少 AddRange 方法,而互联网上有各种实现。我也知道每个项目被复制时都会无意中触发 CollectionChanged 事件,严重影响性能。

可能我将来必须允许用户从超过10,000个项目的组中选择,听起来像是一个糟糕的想法,但由于列表框(CollectionViewSource)上的分组非常有效,这是不可协商的,但会关闭两个列表框的虚拟化。
当使用ObservableCollection绑定数据加载包含数千个项目的列表框时,有什么方法可以提高性能?您推荐任何AddRange类型实现吗?我在这里唯一的选择是在后台线程上运行此操作,这似乎很昂贵,因为我不是从数据库加载数据?

请查看此链接:https://dev59.com/j0jSa4cB1Zd3GeqPCAtm - Sauron
4个回答

2

我忍不住回答这个问题。 我认为您可能不再需要这个答案,但是可能有其他人可以使用它。

不要想得太复杂(不要使用多线程(这会使事情容易出错和不必要的复杂性。仅对硬计算/ IO使用线程),所有这些不同的actiontypes都将使缓冲变得非常困难。最烦人的部分是,如果您添加或删除10000个项目,则应用程序(列表框)将忙于处理ObservableCollection引发的事件。该事件已经支持多个项目。 所以.....

您可以缓冲项目,直到它更改操作。 因此,添加操作将被缓冲,并且如果“用户”更改操作或刷新操作,则会被作为批次引发。 尚未测试,但是您可以尝试以下操作:

// Written by JvanLangen
public class BufferedObservableCollection<T> : ObservableCollection<T>
{
    // the last action used
    public NotifyCollectionChangedAction? _lastAction = null;
    // the items to be buffered
    public List<T> _itemBuffer = new List<T>();

    // constructor registeres on the CollectionChanged
    public BufferedObservableCollection()
    {
        base.CollectionChanged += new NotifyCollectionChangedEventHandler(ObservableCollectionUpdate_CollectionChanged);
    }

    // When the collection changes, buffer the actions until the 'user' changes action or flushes it.
    // This will batch add and remove actions.
    private void ObservableCollectionUpdate_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        // if we have a lastaction, check if it is changed and should be flush else only change the lastaction
        if (_lastAction.HasValue)
        {
            if (_lastAction != e.Action)
            {
                Flush();
                _lastAction = e.Action;
            }
        }
        else
            _lastAction = e.Action;

        _itemBuffer.AddRange(e.NewItems.Cast<T>());
    }

    // Raise the new event.
    protected void RaiseCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (this.CollectionChanged != null)
            CollectionChanged(sender, e);
    }

    // Don't forget to flush the list when your ready with your action or else the last actions will not be 'raised'
    public void Flush()
    {
        if (_lastAction.HasValue && (_itemBuffer.Count > 0))
        {
            RaiseCollectionChanged(this, new NotifyCollectionChangedEventArgs(_lastAction.Value, _itemBuffer));
            _itemBuffer.Clear();
            _lastAction = null;
        }
    }

    // new event
    public override event NotifyCollectionChangedEventHandler CollectionChanged;
}

玩得愉快!,J3R03N


这实际上不起作用。当您尝试使用多个项触发集合更改事件时,您将收到NotSupportedException(不支持范围操作)。建议查看其他解决方案,例如:http://binarysculpting.com/2012/04/03/adding-many-entries-to-an-observable-collection-in-a-performance-friendly-way/,或http://peteohanlon.wordpress.com/2008/10/22/bulk-loading-in-observablecollection/,或https://dev59.com/tHRB5IYBdhLWcg3wSVcI。 - Charlie
除了Jeroeon的评论之外,ObservableCollection还有一个OnCollectionChanged方法,您可以重写它。您应该使用它来代替订阅CollectionChanged事件。 - Seanba

2

我已经删除了CollectionViewSource和分组功能,并将项目复制完成只需要半秒钟,但启用分组功能后,由于虚拟化与分组不兼容,可能需要长达一分钟的时间。

我需要决定是否使用CollectionViewSource。


CollectionViewSource在初始绑定时效率很高,但在运行时非常低效。我现在正在使用LINQ在代码后台进行排序/过滤。 - Vault
这怎么算是一个答案? - Stealth Rabbi

1
你可以继承自ObservableCollection<T>(或直接实现INotifyCollectionChanged)来添加BeginUpdateEndUpdate方法。在调用BeginUpdateEndUpdate之间进行的更改将被排队,然后组合成一个(如果有单独的范围,则为多个)NotifyCollectionChangedEventArgs对象,并在调用EndUpdate时传递给CollectionChanged事件的处理程序。

2
据我所知,WPF控件不支持集合的范围更新,并且当它们在一个CollectionChanged事件中接收到多个项目时会抛出异常。 - Tomáš Kafka
1
WTF ?! 如果它们不支持,为什么要提供在事件参数中指定多个项的能力?我已经实现了我的答案中描述的集合,但还没有时间进行实际测试...我刚刚测试了一下,看起来你是对的 :(. 所以我的集合不能用于绑定场景... - Thomas Levesque

1

您可以在这里找到一个线程安全的可观察集合。将您的可观察集合变为线程安全,并将其绑定到列表框中。


这是我正在使用的方法,而且效果非常好。你可以使用BackgroundWorker来填充你的ObservableCollection,并实时查看ListBox被填充的情况。 - japf
这种方法仍然会在UI线程上抛出过多的事件,因为每个添加的项都会引发自己的集合更改事件。这并不能解决问题。 - Jared Harding

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接