解决这个问题的简单方法是使用“虚拟化集合”实现来维护对其项目的弱引用以及用于获取/创建项目的算法。这个集合的代码相当复杂,需要所有接口和数据结构来有效跟踪加载数据的范围,但这里是基于索引虚拟化的类的部分 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
.
对于 a TreeView
,还需要一个步骤:在HierarchicalDataTemplate
集合中 a MultiBinding
forItemsSource
绑定到您的真实对象ItemsSource
以及IsExpanded
模板化父对象。如果第二个值(值)为真,则的转换器MultiBinding
返回其第一个值(the ) ,否则返回 null。这样做的目的是,当您折叠一个节点时,所有对集合内容的引用都会立即被删除,以便清理它们。ItemsSource
IsExpanded
TreeView
VirtualizingCollection
请注意,虚拟化不需要基于索引来完成。在树状场景中,它可以是全有或全无,而在列表场景中,可以使用估计的计数并根据需要使用“开始键”/“结束键”机制填充范围。当底层数据可能发生变化并且虚拟化视图应根据屏幕顶部的哪个键来跟踪其当前位置时,这很有用。