提高 WPF ObservableCollection 性能

发布于 2024-08-05 01:38:20 字数 927 浏览 6 评论 0 原文

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

Word 2007 自定义屏幕
(来源:psu.edu

我使用 2 个 ObservableCollections 来允许用户选择他们需要的任何项目(灵活性是这里的关键)。主要问题是我有数千 个项目分组在两个列表框中。总而言之,设计效果非常好(有几十个项目),但我的绊脚石是当用户在屏幕冻结时从左到右复制所有可用项目时(在不同线程上运行的时间?)。

看看 ObservableCollection,它缺少 AddRange 方法,并且互联网上有各种可用的实现。我还知道 CollectionChanged 事件是不必要的,因为每个项目都被复制,导致性能严重下降。

将来我很可能必须允许用户从超过 10 000 个项目的组中进行选择,这听起来像是一个坏主意,但这是不可协商的,因为列表框(CollectionViewSource)上的分组效果非常好,但有关闭两个列表框的虚拟化的副作用

当数据绑定到 ObservableCollection 时,加载包含数千个项目的列表框时,我可以做些什么来提高性能?您是否有推荐的 AddRange 类型实现?我唯一的选择是在后台线程上运行它,这似乎很昂贵,因为我没有从数据库加载数据?

At present I have two WPF listboxes imitating the following functionality

Word 2007 customize screen
(source: psu.edu)

I am using 2 ObservableCollections to allow users to select whatever items they require (flexibility is the key here). The main issue is that I have thousands of items that are grouped in both listboxes. All in all the design works really well (with a few dozen items), but my stumbling block is when a user copies all the available items from the left to the right as the screen freezes (time to run on a different thread?).

Looking at ObservableCollection it lacks an AddRange method and there are various implementations available on the internet. I also know the CollectionChanged event is needlessly being fired as each item is copied over draining performance horribly.

It may well be that I have to allow users to choose from groups of over 10 000 items in the future, which sounds like a bad idea, but is non-negotiable as the grouping on the listbox (CollectionViewSource) works really well, but has the side effect of switching off the Virtualising of both the listboxes

What can I do to improve the performance when loading a listbox with thousands of items when databound to an ObservableCollection? Are there any AddRange type implementations that you would recommend? Is the only choice I have here to run this on a background thread which seems expensive because I am not loading data from a database?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

聽兲甴掵 2024-08-12 01:38:20

我已经删除了 CollectionViewSource 和分组,并且项目在 1/2 秒内复制完毕,但是使用分组可能需要长达一分钟的时间,因为虚拟化不适用于分组。

我需要决定是否使用 CollectionViewSource

I have removed the CollectionViewSource and the grouping and the items are copied over in 1/2 a second, but with the grouping on it can take up to a minute because virtualisation does not work with the grouping.

I will need to decide whether to use the CollectionViewSource

逆流 2024-08-12 01:38:20

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

不要想太多(不要接近这个多线程(这会使事情容易出错并且不必要的复杂。仅使用线程进行硬计算/IO),所有这些不同的操作类型将使其很难缓冲。最烦人的部分是,如果您删除或添加 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

I couldn't resist answering this. I don't think you won't need this answer anymore, but maybe somebody else can use it.

Don't think too hard (do not approach this multithreaded (this will make things error-prone and unnecessary complicated. Only use threading for hard calculations/IO), all those different actiontypes will make it very difficult to buffer. The most annoying part is, that if you remove or add 10000 items your application (listboxes) will be very busy with handling the events raised by the ObservableCollection. The event already supports multiple items. So.....

You could buffer the items until it changes the action. So Add actions will be buffered and wil be raised as batch if the 'user' changes action or flushes it.
Haven't test it, but you could do something like this:

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

Have fun!, J3R03N

末骤雨初歇 2024-08-12 01:38:20

您可能可以继承 ObservableCollection (或直接实现 INotifyCollectionChanged)来添加 BeginUpdateEndUpdate 方法。对 BeginUpdateEndUpdate 的调用之间所做的更改将排队,然后组合成一个(或多个,如果有单独的范围)NotifyCollectionChangedEventArgs 对象,该对象将调用 EndUpdate 时传递给 CollectionChanged 事件的处理程序。

You could probably inherit from ObservableCollection<T> (or directly implement INotifyCollectionChanged) to add BeginUpdate and EndUpdate methods. Changes made between calls to BeginUpdate and EndUpdate would be queued, then combined into one (or several if there are separate ranges) NotifyCollectionChangedEventArgs object that would be passed to the handlers of the CollectionChanged event when EndUpdate is called.

三五鸿雁 2024-08-12 01:38:20

您可以在此处找到线程安全的可观察集合。确保您的 Observable 集合线程安全并将其绑定到列表框。

You can find a Thread safe observable collection here. Make your Observable collection thread safe and bind it to listbox.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文