2010-01-29 81 views
4

我有一個綁定到ViewModel實例樹的TreeView。問題是模型數據來自緩慢的存儲庫,所以我需要數據虛擬化。應該只在父樹視圖節點展開時加載節點下方的子ViewModel列表,並且在摺疊時應該將其卸載。MVVM vs數據虛擬化

如何在遵守MVVM原則的同時實現這一點? ViewModel如何得到通知它需要加載或卸載子節點?那是什麼時候一個節點被展開或摺疊而不知道樹視圖的存在?

有些東西讓我覺得數據虛擬化不適合MVVM。由於在數據虛擬化過程中ViewModel通常需要知道很多關於UI的當前狀態,並且需要在UI中控制相當多的方面。再舉一個例子:

帶數據虛擬化的列表視圖。 ViewModel需要控制ListView的scrollthumb的長度,因爲它取決於模型中的項目數量。同樣,當用戶滾動時,ViewModel需要知道他滾動到的位置以及列表視圖有多大(目前適合的項目數)以便能夠從存儲庫加載模型數據的正確部分。

回答

4

解決這個問題的簡單方法是使用「虛擬化集合」實現,該實現保持對其項目的弱引用以及用於獲取/創建項目的算法。此集合的代碼相當複雜,所需要的所有的接口和數據結構如何有效地追蹤加載的數據範圍,但這裏是爲虛擬化基於索引的類的部分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來更新索引跟蹤。這會更新開始索引。對於插入,這也可能會分割一個範圍,對於刪除,可能需要重新創建一個或多個範圍。當通過接口IListIList<T>添加/刪除項目時,內部使用相同的算法。
  • Cleanup方法被稱爲在後臺遞增搜索範圍的樹WeakReferences並且可設置整個範圍內,並且還用於過於稀疏的範圍(例如,僅一個WeakReference與1000個時隙的範圍)

請注意,FetchItems傳遞了一系列未加載的項目,因此它可以使用啓發式一次加載多個項目。一個簡單的啓發式方法就是加載下100個項目,或者直到目前的差距結束,以先到者爲準。

使用VirtualizingCollection,只要您使用的是WPF的內置虛擬化,就會在適當的時候導致數據在ListBox,ComboBox等等的加載。 VirtualizingStackPanel而不是StackPanel

對於TreeView,則需要一個步驟:在HierarchicalDataTemplate設置ItemsSource一個MultiBinding結合到你的真實ItemsSource同時也IsExpanded的模板父。如果第二個值(IsExpanded值)爲真,則MultiBinding的轉換器返回其第一個值(ItemsSource),否則返回空值。這樣做的目的是當您摺疊TreeView中的節點時,所有對集合內容的引用都立即刪除,以便VirtualizingCollection可以清除它們。

請注意,虛擬化不需要基於索引完成。在樹狀場景中,它可以是全或無,並且在列表場景中,可以使用估計的計數,並且使用「開始鍵」/「結束鍵」機制根據需要填充範圍。當底層數據可能發生變化,並且虛擬化視圖應該根據哪個鍵位於屏幕的頂部來跟蹤其當前位置時,這非常有用。

-3
<TreeView 
     VirtualizingStackPanel.IsVirtualizing = "True" 
     VirtualizingStackPanel.VirtualizationMode = "Recycling" 
VirtualizingStackPanel.CleanUpVirtualizedItem="TreeView_CleanUpVirtualizedItem"> 
      <TreeView.ItemsPanel> 
       <ItemsPanelTemplate> 
        <VirtualizingStackPanel /> 
       </ItemsPanelTemplate> 
      </TreeView.ItemsPanel> 
     </TreeView> 
+1

我倒下了這個答案,因爲它很難理解。它確實需要一些改進,特別是對於英語。 – bitbonk 2015-07-25 13:16:47