Использование виртуализации данных при привязке к WPF DataGrid и поддержка сортировки - PullRequest
1 голос
/ 19 марта 2019

Я привязываю большую коллекцию (более 250 000 записей) к DataGrid. Чтобы это работало хорошо, он должен использовать как виртуализацию пользовательского интерфейса, так и виртуализацию данных. После некоторых исследований я выяснил, как заставить обе виртуализации работать. Но как только я выполняю сортировку, щелкнув заголовок столбца в DataGrid, он прекращает виртуализацию данных и пытается прочитать весь набор данных в память.

Вместо этого я хочу передать команду сортировки в базовую коллекцию, чтобы база данных выполняла сортировку перед извлечением данных с диска. Есть ли способ сделать это?

1 Ответ

1 голос
/ 19 марта 2019

Я отвечаю на свой вопрос здесь в надежде помочь другим, имеющим дело с этой же проблемой.Информация распространяется по нескольким статьям, и сообщество Stack Overflow очень помогло в ее выяснении.

Во-первых, основы.Виртуализация пользовательского интерфейса означает, что элемент управления (в данном случае DataGrid) создает объекты пользовательского интерфейса только для того, что можно увидеть на экране (плюс еще несколько для включения быстрой прокрутки).Он встроен в DataGrid и включен по умолчанию.Таким образом, вам не нужно ничего делать, чтобы включить его. Подробнее см. В этой статье .

Виртуализация данных означает только чтение соответствующих данных, отображаемых на экране.Остальное осталось в базе данных.Есть много ссылок на виртуализацию данных, но мне было трудно найти подходящую статью. Это от Microsoft .

В моем случае я делаю виртуализацию с произвольным доступом.Суть в том, что моя коллекция должна реализовывать IList и INotifyCollectionChanged.При желании я также могу реализовать IItemsRangeInfo и ISelectionInfo, если они помогут.

Пока все хорошо.Я создал тестовую коллекцию для эмуляции произвольного доступа к данным из базы данных.В этом случае он создавал данные строки алгоритмически из индекса, чтобы я мог тестировать с произвольно большими виртуальными коллекциями и исключать производительность базы данных как фактор в этих тестах.Реализация работ IList и INotifyCollectionChanged.Я могу создать коллекцию с миллиардом записей и производительностью DataGrid с почти мгновенной производительностью.Вы можете захватить полосу прокрутки и мгновенно перейти от начала к концу.

Две подсказки, которые помогают создавать коллекции, предназначенные для виртуализации данных.IList наследуется от IEnumerable.С большой коллекцией с произвольным доступом вам не нужно, чтобы звонившие перечисляли эту коллекцию.Однако DataGrid вызывает Enumerate один раз во время инициализации.Вы можете удовлетворить это, вернув пустую коллекцию.Для этой цели я создал одноэлементный пустой класс коллекции.

Другой метод IList, который вы не хотите вызывать, - это CopyTo.У меня просто есть этот метод бросить InvalidOperationException.

Это все работает.Однако, как только вы щелкнете по заголовку столбца, чтобы выполнить сортировку, элемент управления попытается сделать копию всей коллекции.С миллиардом записей я получаю ошибку нехватки памяти.Похоже, что реализация IBindingList должна исправить это, поскольку она обеспечивает методы сортировки, необходимые для DataGrid.Однако реализация IBindingList полностью отключает виртуализацию данных, в результате чего элемент управления пытается прочитать все данные во время инициализации.

Ответ содержится в документации для CollectionView .Когда элемент управления, такой как DataGrid или ListView, связывается с коллекцией, он использует CollectionView в качестве посредника.Идея состоит в том, что существует общая коллекция (модель в терминах MVVM), и что сортировка и фильтрация реализованы в CollectionView, а не в самой коллекции.Таким образом, если одна и та же коллекция отображается в нескольких элементах управления, сортировка одного не влияет на другие.Различные реализации CollectionView достигают этого путем создания теневой копии связанной коллекции и сортировки тени.Он хорошо работает в небольших коллекциях, но это катастрофа для виртуализации данных.

Код привязки данных выбирает представление в соответствии с интерфейсами, которые демонстрирует связанная коллекция.Коллекция, которая реализует IList, связана с ListCollectionView.Если эта коллекция также реализует INotifyCollectionChanged, то ListCollectionView будет выполнять виртуализацию данных (до тех пор, пока не будет вызвана сортировка или фильтрация).Коллекция, которая реализует IBindingListView, связана с BindingListCollectionView, который не выполняет виртуализацию данных.

Чтобы добавить сортировку в виртуализацию данных, вы должны создать подкласс ListCollectionView, захватить запросы на сортировку, передать их в свой класс коллекции и остановить ListCollectionView от создания теневых копий.Это на удивление легко, хотя мне пришлось обратиться к исходному коду в ListCollectionView , чтобы понять это.Вот код:

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).Следовательно, класс коллекции становится частью представления (или ModelView с использованием терминологии MVVM), и между ними должно быть отношение 1: 1.Общая коллекция (или Модель с использованием терминологии MVVM) является базовой базой данных.Чтобы подчеркнуть это, я экспериментировал с объединением обоих в один класс.Это может быть сделано, но это становится сложным, потому что оба класса реализуют IList.Проще иметь два объекта, каждый со ссылкой на другой.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...