2010-01-29 3 views
4

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

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

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

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

ответ

4

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

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

-3
<TreeView 
     VirtualizingStackPanel.IsVirtualizing = "True" 
     VirtualizingStackPanel.VirtualizationMode = "Recycling" 
VirtualizingStackPanel.CleanUpVirtualizedItem="TreeView_CleanUpVirtualizedItem"> 
      <TreeView.ItemsPanel> 
       <ItemsPanelTemplate> 
        <VirtualizingStackPanel /> 
       </ItemsPanelTemplate> 
      </TreeView.ItemsPanel> 
     </TreeView> 
+1

Я отклонил этот ответ, потому что его трудно понять. Это действительно нуждается в улучшении, особенно в отношении английского языка. – bitbonk