MVVM vs. Data Virtualization

I have a TreeView bound to a ViewModel instance tree. The problem is that the model data comes from a slow repository, so I need data virtualization. The list of helper ViewModel below the node should only be loaded when the view of the parent tree of the node is expanded, and should be unloaded when it is collapsed.

How can this be implemented subject to the principles of MVVM? How can a ViewModel receive a notification that it needs to load or unload subnode? That is, when the node was expanded or compensated without any information about the existence of the tree image?

Something makes me feel like data virtualization is not suitable for MVVM. Since in data virtualization, ViewModel usually needs to know quite a bit about the current state of the user interface, and also needs to control quite a few aspects in the user interface. Take another example:

List with data virtualization. The ViewModel will need to control the scroll length of the ListView, as this depends on the number of elements in the model. Also, when the user scrolls, the ViewModel must know what position it scrolls to and how big the list is (how many elements are currently included) in order to be able to load the necessary part of the model data from the repository.

+4
source share
2 answers

An easy way to solve this problem is to implement "collection virtualization", which supports weak references to its elements along with an algorithm for extracting / creating elements. The code for this collection is quite complex, with all the necessary interfaces and data structures to effectively track the ranges of loaded data, but here is a partial API for a class that is virtualized based on indexes:

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(); } 

The internal data structure is a balanced tree of data ranges with each data range containing an initial index and an array of weak references.

This class is intended to be a subclass to provide the logic for the actual loading of data. Here's how it works:

  • In the subclass constructor, RecordInsertOrDelete is called to set the initial size of the collection.
  • When an element is accessed using IList/ICollection/IEnumerable , the tree is used to search for the data element. If it is found in the tree, and there is a weak link, and the weak link still points to a life object, this object is returned, otherwise it is loaded and returned.
  • When an element needs to be loaded, the range of indices is calculated by searching in the forward and reverse direction by the data structure for the next / previous element already loaded, then the abstract FetchItems is called so that a subclass can load the elements.
  • In a subclass implementation of FetchItems elements are selected and then RecordFetchedItems is RecordFetchedItems to update the range tree with the new elements. Some complexity is needed to merge neighboring nodes to prevent the tree from growing too much.
  • When a subclass receives notification of external data changes, it can call RecordInsertOrDelete to update the index tracking. This updates the starting indexes. For insertion, this can also split a range, and for deletion, this may require less recreation of one or more ranges. The same algorithm is used internally when elements are added / removed via the IList and IList<T> interfaces.
  • The Cleanup method is called in the background to gradually search the range tree for WeakReferences and entire ranges that can be removed, as well as for ranges that are too sparse (for example, only one WeakReference in a range with 1000 slots)

Note that FetchItems is passed a range of uploaded items, so it can use heuristics to load multiple items at once. A simple such heuristic will load the next 100 items or until the end of the current gap, whichever comes first.

With VirtualizingCollection built-in virtualization of WPF will load the data at the appropriate times for ListBox , ComboBox , etc., if you use, for example. VirtualizingStackPanel instead of StackPanel .

For a TreeView , one more step is required: In the HierarchicalDataTemplate set a MultiBinding for the ItemsSource , which is bound to your real ItemsSource , as well as to IsExpanded in the parent template. The converter for MultiBinding returns its first value ( ItemsSource ) if the second value ( IsExpanded value) is true, otherwise it returns null. This makes sure that when you collapse a node in a TreeView all references to the contents of the collection are immediately deleted, so VirtualizingCollection can clear them.

Please note that virtualization should not be based on indexes. In a tree script, this can be โ€œall or nothing,โ€ and in a list script you can use a counted counter, and the ranges are filled as needed using the โ€œstart keyโ€ / โ€œend keyโ€. This is useful when the underlying data can change, and the virtualized view should track the current location based on which key is at the top of the screen.

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

Source: https://habr.com/ru/post/1299754/


All Articles