首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >绑定到WPF DataGrid并支持排序时使用数据虚拟化

绑定到WPF DataGrid并支持排序时使用数据虚拟化
EN

Stack Overflow用户
提问于 2019-03-19 16:38:51
回答 1查看 2K关注 0票数 9

我正在将一个大型集合(250,000+记录)绑定到一个DataGrid。为此,它必须同时使用UI虚拟化和数据虚拟化。经过一些研究,我想出了如何让这两种虚拟化都发挥作用。但是,一旦我进行了排序,通过单击DataGrid中的列标题,它就放弃了数据虚拟化,并尝试将整个数据集读入内存。

相反,我希望它将排序命令传递给基础集合,以便数据库在从磁盘检索数据之前执行排序。有办法这样做吗?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2019-03-19 17:41:10

我在这里回答我自己的问题,希望能帮助其他人处理同样的问题。这些信息分散在多篇文章中,Stack溢出社区对理解它非常有帮助。

首先是基本知识。UI虚拟化意味着控件(在本例中为DataGrid)只为屏幕上可以看到的内容创建UI对象(再加上几个以支持快速滚动)。它内置在DataGrid中,默认情况下启用。所以,你不需要做太多的事情来启用它。有关详情,请参阅本文

数据虚拟化意味着只读取屏幕上可见的相应数据。其余的留在数据库中。有很多关于数据虚拟化的引用,但我发现很难找到正确的文章。这是微软公司的

在我的例子中,我正在进行随机访问虚拟化。总结是,我的集合应该实现IList和INotifyCollectionChanged。或者,我也可以实现IItemsRangeInfo和ISelectionInfo,如果它们有帮助的话。

到现在为止还好。我创建了一个测试集合来模拟对数据库中数据的随机访问。在这种情况下,它从索引中算法地创建行数据,这样我就可以使用任意大的虚拟集合进行测试,并消除数据库性能作为这些测试中的一个因素。实现IList和INotifyCollectionChanged是可行的。我可以创建一个拥有10亿条记录的集合,而DataGrid性能则具有近乎瞬时的性能。你可以抓起滚动条,从头到尾瞬间移动。

这两个提示有助于使集合用于数据虚拟化。IList继承自IEnumerable。对于大型随机访问集合,您不希望任何调用者枚举该集合。但是,DataGrid在初始化期间确实调用了一次枚举。您可以通过返回一个空集合来满足这个要求。为此,我创建了一个单例空集合类。

另一个不想被调用的IList方法是CopyTo。我只是让这个方法抛出一个InvalidOperationException。

这一切都有效。但是,一旦单击列标题来执行排序,控件就会尝试复制整个集合。有了10亿条记录,我发现了一个内存不足的错误。实现IBindingList似乎应该修复这个问题,因为它提供了DataGrid需要的排序方法。但是,实现IBindingList完全禁用数据虚拟化,从而导致控件试图在初始化期间读取所有数据。

答案在CollectionView文档中。当控件(如DataGrid或ListView )绑定到集合时,它使用CollectionView作为中介。其思想是有一个共享集合( MVVM术语中的模型),排序和筛选是在CollectionView而不是集合本身中实现的。这样,如果同一个集合出现在多个控件中,则排序一个不会影响其他控件。各种CollectionView实现通过创建绑定集合的影子副本和对阴影进行排序来实现这一点。它在小型集合中运行良好,但对数据虚拟化来说却是一场灾难。

数据绑定代码根据被绑定的集合所显示的接口选择视图。实现IList的集合由ListCollectionView绑定。如果该集合还实现了INotifyCollectionChanged,那么ListCollectionView将执行数据虚拟化(直到调用排序或筛选)。实现IBindingListView的集合由BindingListCollectionView绑定,后者执行而不是执行数据虚拟化。

要将排序添加到数据虚拟化,您必须将ListCollectionView子类、捕获排序请求、将它们传递给您的集合类,并阻止ListCollectionView创建影子副本。这是令人惊讶的简单,虽然我不得不咨询ListCollectionView的源代码来解决它。下面是代码:

代码语言:javascript
运行
复制
class VirtualListCollectionView : ListCollectionView
{
    VirtualCollection m_collection;

    public VirtualListCollectionView(VirtualCollection collection)
        : base(collection)
    {
        m_collection = collection;
    }

    protected override void RefreshOverride()
    {
        m_collection.SetSortInternal(SortDescriptions);

        // Notify listeners that everything has changed
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));

        // The implementation of ListCollectionView saves the current item before updating the search
        // and restores it after updating the search. However, DataGrid, which is the primary client
        // of this view, does not use the current values. So, we simply set it to "beforeFirst"
        SetCurrent(null, -1);
    }
}

关键是重写"RefreshOverride()“。这就是不想要的影子复制的地方。相反,重写将排序要求传递给关联的集合。自定义类上的特殊"SetSortInternal()“方法会使而不是生成INotifyCollectionChanged事件。这很重要,因为事件会导致对RefreshOverride()的递归调用。

接下来,您必须使用自定义的CollectionView类而不是缺省值来进行数据绑定。要做到这一点,有两种方法。一种是自己创建VirtualListCollectionView ( XAML或codebehind),并绑定到视图而不是集合(通过将它分配给DataGrid.ItemsSource)。另一种方法是在集合上实现ICollectionViewFactory,让它创建自己的视图。

在此框架中,CollectionView将排序和筛选委托给底层集合类(IList实现)。因此,集合类成为视图(或使用MVVM术语的ModelView )的一部分,它们之间应该存在1:1的关系。共享集合(或使用MVVM术语的模型)是底层数据库。为了强调这一点,我尝试将两者合并到同一个类中。这是可以完成的,但是很棘手,因为这两个类都实现了IList。有两个对象比较容易,每个对象都引用另一个对象。

票数 13
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/55245962

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档