Вопрос

У меня есть TreeView, привязанный к дереву экземпляров ViewModel.Проблема в том, что данные модели поступают из медленного репозитория, поэтому мне нужна виртуализация данных.Список подчиненных ViewModel под узлом следует загружать только тогда, когда узел представления родительского дерева развернут, и выгружаться при его свертывании.

Как это можно реализовать, придерживаясь принципов MVVM?Как ViewModel может получить уведомление о необходимости загрузки или выгрузки подузлов?То есть, когда узел был расширен или свернут, ничего не зная о существовании древовидного представления?

Что-то заставляет меня чувствовать, что виртуализация данных не сочетается с MVVM.Поскольку при виртуализации данных ViewModel обычно должна знать довольно много о текущем состоянии пользовательского интерфейса, а также контролировать довольно много аспектов пользовательского интерфейса.Возьмем другой пример:

Просмотр списка с виртуализацией данных.ViewModel необходимо будет контролировать длину прокрутки ListView, поскольку она зависит от количества элементов в модели.Кроме того, когда пользователь прокручивает, ViewModel должно будет знать, до какой позиции он прокрутился и насколько велико представление списка (сколько элементов в настоящее время помещается), чтобы иметь возможность загрузить нужную часть данных модели из репозитория.

Это было полезно?

Решение

Самый простой способ решить эту проблему — реализовать «виртуализацию коллекции», которая поддерживает слабые ссылки на ее элементы вместе с алгоритмом выборки/создания элементов.Код для этой коллекции довольно сложен, со всеми необходимыми интерфейсами и структурами данных для эффективного отслеживания диапазонов загруженных данных, но вот частичный API для класса, который виртуализируется на основе индексов:

public class VirtualizingCollection<T>
  : IList<T>, ICollection<T>, IEnumerable<T>,
    IList, ICollection, IEnumerable,
    INotifyPropertyChanged, INotifyCollectionChanged
{
  protected abstract void FetchItems(int requestedIndex, int gapStartIndex, int gapEndIndex);
  protected void RecordFetchedItems(int startIndex, int count, IEnumerable items) ...
  protected void RecordInsertOrDelete(int startIndex, int countPlusOrMinus) ...
  protected virtual void OnCollectionChanged(CollectionChangedEventArgs e) ...
  protected virtual void Cleanup();
}

Внутренняя структура данных здесь представляет собой сбалансированное дерево диапазонов данных, в котором каждый диапазон данных содержит начальный индекс и массив слабых ссылок.

Этот класс предназначен для создания подкласса для обеспечения логики фактической загрузки данных.Вот как это работает:

  • В конструкторе подкласса RecordInsertOrDelete вызывается для установки начального размера коллекции
  • Когда доступ к элементу осуществляется с помощью IList/ICollection/IEnumerable, дерево используется для поиска элемента данных.Если он найден в дереве и существует слабая ссылка, и слабая ссылка все еще указывает на жизненный объект, этот объект возвращается, в противном случае он загружается и возвращается.
  • Когда элемент необходимо загрузить, диапазон индексов вычисляется путем поиска вперед и назад по структуре данных для следующего/предыдущего уже загруженного элемента, а затем абстрактного значения. FetchItems вызывается, чтобы подкласс мог загружать элементы.
  • В подклассе FetchItems реализация, элементы извлекаются, а затем RecordFetchedItems вызывается для обновления дерева диапазонов новыми элементами.Здесь требуется некоторая сложность для объединения соседних узлов, чтобы предотвратить слишком большой рост дерева.
  • Когда подкласс получает уведомление об изменении внешних данных, он может вызвать RecordInsertOrDelete чтобы обновить отслеживание индекса.Это обновление запускает индексы.Для вставки это также может разделить диапазон, а для удаления может потребоваться воссоздание одного или нескольких диапазонов меньшего размера.Этот же алгоритм используется внутри, когда элементы добавляются/удаляются через IList и IList<T> интерфейсы.
  • А Cleanup метод вызывается в фоновом режиме для постепенного поиска в дереве диапазонов WeakReferences и целые диапазоны, которые можно удалить, а также слишком редкие диапазоны (например, только один WeakReference в диапазоне с 1000 слотами)

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

С VirtualizingCollection, встроенная виртуализация WPF вызовет загрузку данных в подходящее время для ListBox, ComboBox, и т. д., если вы используете, например. VirtualizingStackPanel вместо StackPanel.

Для TreeView, требуется еще один шаг:в HierarchicalDataTemplate установить MultiBinding для ItemsSource это связано с твоим реальным ItemsSource а также IsExpanded на шаблонном родителе.Конвертер для MultiBinding возвращает свое первое значение (теперь ItemsSource), если второе значение ( IsExpanded value) имеет значение true, в противном случае возвращается значение null.Это делает так, что когда вы сворачиваете узел в TreeView все ссылки на содержимое коллекции немедленно удаляются, так что VirtualizingCollection можно их почистить.

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

Другие советы

    <TreeView
        VirtualizingStackPanel.IsVirtualizing = "True"
        VirtualizingStackPanel.VirtualizationMode = "Recycling" 
VirtualizingStackPanel.CleanUpVirtualizedItem="TreeView_CleanUpVirtualizedItem">
            <TreeView.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel />
                </ItemsPanelTemplate>
            </TreeView.ItemsPanel>
        </TreeView>
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top