MVVM против виртуализации данных - PullRequest
4 голосов
/ 29 января 2010

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

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

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

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

Ответы [ 2 ]

4 голосов
/ 29 января 2010

Простой способ решить эту проблему - реализация «виртуализирующей коллекции», которая поддерживает слабые ссылки на свои элементы, а также алгоритм выборки / создания элементов. Код для этой коллекции довольно сложный, со всеми необходимыми интерфейсами и структурами данных для эффективного отслеживания диапазонов загружаемых данных, но вот частичный 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) истинно, в противном случае он возвращает ноль. Это делает так, что при свертывании узла в TreeView все ссылки на содержимое коллекции немедленно удаляются, так что VirtualizingCollection может их очистить.

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

0 голосов
/ 19 июля 2015
    <TreeView
        VirtualizingStackPanel.IsVirtualizing = "True"
        VirtualizingStackPanel.VirtualizationMode = "Recycling" 
VirtualizingStackPanel.CleanUpVirtualizedItem="TreeView_CleanUpVirtualizedItem">
            <TreeView.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel />
                </ItemsPanelTemplate>
            </TreeView.ItemsPanel>
        </TreeView>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...