首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >MVVM与数据虚拟化

MVVM与数据虚拟化
EN

Stack Overflow用户
提问于 2010-01-30 00:38:36
回答 2查看 2.7K关注 0票数 4

我有一个绑定到ViewModel实例树的TreeView。问题是模型数据来自速度很慢的存储库,所以我需要数据虚拟化。节点下的子ViewModel列表应仅在父树视图节点展开时加载,而在折叠时应卸载。

如何在遵循MVVM原则的同时实现这一点?如何通知ViewModel需要加载或卸载子节点?这是指在不知道树视图存在的情况下展开或折叠节点的情况?

有件事让我觉得MVVM的数据虚拟化不太好。因为在数据虚拟化中,ViewModel通常需要了解相当多关于UI的当前状态,而lot也需要控制UI中的许多方面。再举一个例子:

一个带有数据虚拟化的列表视图。ViewModel需要控制列表视图的滚动缩略图的长度,因为它取决于模型中的项数。此外,当用户滚动时,ViewModel需要知道他滚动到的位置以及列表视图的大小(当前适合多少项)才能从存储库加载正确的Model data部分。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2010-01-30 03:26:36

解决这个问题的简单方法是使用“虚拟化集合”实现,该实现维护对其项的弱引用,以及用于获取/创建项的算法。这个集合的代码相当复杂,需要所有的接口和数据结构来有效地跟踪加载数据的范围,但下面是一个基于索引虚拟化的类的部分API:

代码语言:javascript
复制
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.
  • The Cleanup方法,以递增地搜索范围树,以查找WeakReferences和可以处理的整个范围,也可以搜索过于稀疏的范围(例如,在具有1000个插槽的范围中仅有一个WeakReference )

请注意,传递给FetchItems的是一系列未加载的项,因此它可以使用启发式一次加载多个项。一个简单的启发式方法是加载下一个100个项目,或者直到当前间隔的末尾,以先到的为准。

有了VirtualizingCollection,只要你使用的是eg,WPF内置的虚拟化就会在适当的时间为ListBoxComboBox等加载数据。VirtualizingStackPanel而不是StackPanel

对于TreeView,还需要一个步骤:在HierarchicalDataTemplate中,为ItemsSource设置一个MultiBinding,它绑定到您的实际ItemsSource,也绑定到模板化父对象上的IsExpanded。如果第二个值(ItemsSource值)为真,则MultiBinding的转换器返回第一个值( IsExpanded ),否则返回null。这样做的目的是,当您折叠TreeView中的节点时,对集合内容的所有引用都会立即删除,以便VirtualizingCollection可以清理它们。

请注意,虚拟化不需要基于索引进行。在树形场景中,它可以是全有或全无,在列表场景中,可以使用估计计数,并根据需要使用"start key“/ "end key”机制填充范围。当底层数据可能发生变化,并且虚拟化视图应该根据屏幕顶部的键来跟踪其当前位置时,这一点很有用。

票数 5
EN

Stack Overflow用户

发布于 2015-07-19 09:21:26

请试试这个。

将VirtualizingStackPanel.IsVirtualizing attached属性设置为true并将VirtualizingStackPanel.VirtualizationMode attached属性设置为VirtualizationMode.Recycling以优化其性能的TreeView。

代码语言:javascript
复制
    <TreeView VirtualizingStackPanel.IsVirtualizing = "True" VirtualizingStackPanel.VirtualizationMode = "Recycling" VirtualizingStackPanel.CleanUpVirtualizedItem="TreeView_CleanUpVirtualizedItem">
            <TreeView.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel />
                </ItemsPanelTemplate>
            </TreeView.ItemsPanel>
        </TreeView>

或者这个也是

代码语言:javascript
复制
 <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>
票数 -2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/2163408

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档