bit*_*onk 4 wpf mvvm data-virtualization
我有一个绑定到 ViewModel 实例树的 TreeView。问题是模型数据来自一个缓慢的存储库,所以我需要数据虚拟化。节点下的子ViewModel列表只有在父树视图节点展开时才加载,折叠时卸载。
如何在遵守 MVVM 原则的同时实现这一点?ViewModel 如何获得需要加载或卸载子节点的通知?那是当一个节点在不知道树视图存在的情况下展开或折叠时?
有些事情让我觉得数据虚拟化不适合 MVVM。由于在数据虚拟化中,ViewModel 通常需要非常了解 UI 的当前状态,并且还需要控制 UI 中的许多方面。再举一个例子:
具有数据虚拟化的列表视图。ViewModel 需要控制 ListView 滚动拇指的长度,因为它取决于模型中的项目数。此外,当用户滚动时,ViewModel 需要知道他滚动到哪个位置以及列表视图有多大(当前适合多少项目)才能从存储库加载模型数据的正确部分。
解决这个问题的简单方法是使用“虚拟化集合”实现,该实现维护对其项目的弱引用以及用于获取/创建项目的算法。这个集合的代码相当复杂,需要所有接口和有效跟踪加载数据范围的数据结构,但这里是基于索引虚拟化的类的部分 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();
}
Run Code Online (Sandbox Code Playgroud)
这里的内部数据结构是一个平衡的数据范围树,每个数据范围包含一个起始索引和一个弱引用数组。
此类被设计为子类,以提供实际加载数据的逻辑。下面是它的工作原理:
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,ItemsSource它绑定到您的真实对象ItemsSource以及IsExpanded模板化父对象上。如果第二个值(值)为真,则转换器MultiBinding返回其第一个值(ItemsSource该IsExpanded值),否则返回 null。这样做的目的是使当您折叠一个节点时TreeView,对集合内容的所有引用都会立即删除,以便VirtualizingCollection可以清理它们。
请注意,虚拟化不需要基于索引来完成。在树场景中,它可以是全有或全无,而在列表场景中,可以使用估计的计数并根据需要使用“开始键”/“结束键”机制填充范围。当底层数据可能发生变化并且虚拟化视图应根据屏幕顶部的哪个键跟踪其当前位置时,这很有用。