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