解決這個問題的簡單方法是使用「虛擬化集合」實現,該實現保持對其項目的弱引用以及用於獲取/創建項目的算法。此集合的代碼相當複雜,所需要的所有的接口和數據結構如何有效地追蹤加載的數據範圍,但這裏是爲虛擬化基於索引的類的部分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
。
對於TreeView
,則需要一個步驟:在HierarchicalDataTemplate
設置ItemsSource
一個MultiBinding
結合到你的真實ItemsSource
同時也IsExpanded
的模板父。如果第二個值(IsExpanded
值)爲真,則MultiBinding
的轉換器返回其第一個值(ItemsSource
),否則返回空值。這樣做的目的是當您摺疊TreeView
中的節點時,所有對集合內容的引用都立即刪除,以便VirtualizingCollection
可以清除它們。
請注意,虛擬化不需要基於索引完成。在樹狀場景中,它可以是全或無,並且在列表場景中,可以使用估計的計數,並且使用「開始鍵」/「結束鍵」機制根據需要填充範圍。當底層數據可能發生變化,並且虛擬化視圖應該根據哪個鍵位於屏幕的頂部來跟蹤其當前位置時,這非常有用。
我倒下了這個答案,因爲它很難理解。它確實需要一些改進,特別是對於英語。 – bitbonk 2015-07-25 13:16:47