我有一个绑定到ViewModel实例树的TreeView。问题是模型数据来自速度很慢的存储库,所以我需要数据虚拟化。节点下的子ViewModel列表应仅在父树视图节点展开时加载,而在折叠时应卸载。
如何在遵循MVVM原则的同时实现这一点?如何通知ViewModel需要加载或卸载子节点?这是指在不知道树视图存在的情况下展开或折叠节点的情况?
有件事让我觉得MVVM的数据虚拟化不太好。因为在数据虚拟化中,ViewModel通常需要了解相当多关于UI的当前状态,而lot也需要控制UI中的许多方面。再举一个例子:
一个带有数据虚拟化的列表视图。ViewModel需要控制列表视图的滚动缩略图的长度,因为它取决于模型中的项数。此外,当用户滚动时,ViewModel需要知道他滚动到的位置以及列表视图的大小(当前适合多少项)才能从存储库加载正确的Model data部分。
发布于 2010-01-30 03:26:36
解决这个问题的简单方法是使用“虚拟化集合”实现,该实现维护对其项的弱引用,以及用于获取/创建项的算法。这个集合的代码相当复杂,需要所有的接口和数据结构来有效地跟踪加载数据的范围,但下面是一个基于索引虚拟化的类的部分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> interfaces.Cleanup方法,以递增地搜索范围树,以查找WeakReferences和可以处理的整个范围,也可以搜索过于稀疏的范围(例如,在具有1000个插槽的范围中仅有一个WeakReference )请注意,传递给FetchItems的是一系列未加载的项,因此它可以使用启发式一次加载多个项。一个简单的启发式方法是加载下一个100个项目,或者直到当前间隔的末尾,以先到的为准。
有了VirtualizingCollection,只要你使用的是eg,WPF内置的虚拟化就会在适当的时间为ListBox、ComboBox等加载数据。VirtualizingStackPanel而不是StackPanel。
对于TreeView,还需要一个步骤:在HierarchicalDataTemplate中,为ItemsSource设置一个MultiBinding,它绑定到您的实际ItemsSource,也绑定到模板化父对象上的IsExpanded。如果第二个值(ItemsSource值)为真,则MultiBinding的转换器返回第一个值( IsExpanded ),否则返回null。这样做的目的是,当您折叠TreeView中的节点时,对集合内容的所有引用都会立即删除,以便VirtualizingCollection可以清理它们。
请注意,虚拟化不需要基于索引进行。在树形场景中,它可以是全有或全无,在列表场景中,可以使用估计计数,并根据需要使用"start key“/ "end key”机制填充范围。当底层数据可能发生变化,并且虚拟化视图应该根据屏幕顶部的键来跟踪其当前位置时,这一点很有用。
发布于 2015-07-19 09:21:26
请试试这个。
将VirtualizingStackPanel.IsVirtualizing attached属性设置为true并将VirtualizingStackPanel.VirtualizationMode attached属性设置为VirtualizationMode.Recycling以优化其性能的TreeView。
<TreeView VirtualizingStackPanel.IsVirtualizing = "True" VirtualizingStackPanel.VirtualizationMode = "Recycling" VirtualizingStackPanel.CleanUpVirtualizedItem="TreeView_CleanUpVirtualizedItem">
<TreeView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</TreeView.ItemsPanel>
</TreeView>或者这个也是
<TreeView Height="200"
ItemsSource="{Binding Source={StaticResource dataItems}}"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling">
<TreeView.ItemContainerStyle>
<!--Expand each TreeViewItem in the first level and
set its foreground to Green.-->
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True"/>
<Setter Property="Foreground" Value="Green"/>
</Style>
</TreeView.ItemContainerStyle>https://stackoverflow.com/questions/2163408
复制相似问题