An easy way to solve this problem is to implement "collection virtualization", which supports weak references to its elements along with an algorithm for extracting / creating elements. The code for this collection is quite complex, with all the necessary interfaces and data structures to effectively track the ranges of loaded data, but here is a partial API for a class that is virtualized based on indexes:
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(); }
The internal data structure is a balanced tree of data ranges with each data range containing an initial index and an array of weak references.
This class is intended to be a subclass to provide the logic for the actual loading of data. Here's how it works:
- In the subclass constructor,
RecordInsertOrDelete is called to set the initial size of the collection. - When an element is accessed using
IList/ICollection/IEnumerable , the tree is used to search for the data element. If it is found in the tree, and there is a weak link, and the weak link still points to a life object, this object is returned, otherwise it is loaded and returned. - When an element needs to be loaded, the range of indices is calculated by searching in the forward and reverse direction by the data structure for the next / previous element already loaded, then the abstract
FetchItems is called so that a subclass can load the elements. - In a subclass implementation of
FetchItems elements are selected and then RecordFetchedItems is RecordFetchedItems to update the range tree with the new elements. Some complexity is needed to merge neighboring nodes to prevent the tree from growing too much. - When a subclass receives notification of external data changes, it can call
RecordInsertOrDelete to update the index tracking. This updates the starting indexes. For insertion, this can also split a range, and for deletion, this may require less recreation of one or more ranges. The same algorithm is used internally when elements are added / removed via the IList and IList<T> interfaces. - The
Cleanup method is called in the background to gradually search the range tree for WeakReferences and entire ranges that can be removed, as well as for ranges that are too sparse (for example, only one WeakReference in a range with 1000 slots)
Note that FetchItems is passed a range of uploaded items, so it can use heuristics to load multiple items at once. A simple such heuristic will load the next 100 items or until the end of the current gap, whichever comes first.
With VirtualizingCollection built-in virtualization of WPF will load the data at the appropriate times for ListBox , ComboBox , etc., if you use, for example. VirtualizingStackPanel instead of StackPanel .
For a TreeView , one more step is required: In the HierarchicalDataTemplate set a MultiBinding for the ItemsSource , which is bound to your real ItemsSource , as well as to IsExpanded in the parent template. The converter for MultiBinding returns its first value ( ItemsSource ) if the second value ( IsExpanded value) is true, otherwise it returns null. This makes sure that when you collapse a node in a TreeView all references to the contents of the collection are immediately deleted, so VirtualizingCollection can clear them.
Please note that virtualization should not be based on indexes. In a tree script, this can be โall or nothing,โ and in a list script you can use a counted counter, and the ranges are filled as needed using the โstart keyโ / โend keyโ. This is useful when the underlying data can change, and the virtualized view should track the current location based on which key is at the top of the screen.