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