Question

J'ai un TreeView qui est lié à un arbre des instances ViewModel. Le problème est que les données du modèle provient d'un dépôt lent donc j'ai besoin la virtualisation des données. La liste des sous ViewModel en dessous d'un noeud ne doit être chargé lorsque le nœud de l'arborescence parent est élargi et il doit être déchargé quand il est réduit.

Comment cela peut-il être mis en œuvre tout en respectant les principes MVVM? Comment peut-ViewModel être averti qu'il doit charger ou décharger des sous-nœuds? C'est quand un noeud a été développé ou réduit sans knowning quoi que ce soit sur l'existence du TreeView?

Quelque chose me fait sentir que la virtualisation des données ne va pas bien avec MVVM. Depuis la virtualisation des données ViewModel a généralement besoin de connaître beaucoup de choses sur l'état actuel de l'interface utilisateur et ASLO a besoin de contrôler un certain nombre d'aspects dans l'interface utilisateur. Prenons un autre exemple:

A listview avec la virtualisation des données. Le ViewModel aurait besoin de contrôler la longueur de ScrollThumb du ListView car il dépend du nombre d'éléments dans le modèle. De même, lorsque l'utilisateur fait défiler, le ViewModel devrait CONNU à ce poste qu'il défilée à et la taille de la listview est (combien d'éléments sont actuellement) pour être en mesure de charger la partie droite des données du modèle forment le référentiel.

Était-ce utile?

La solution

Le moyen facile de résoudre ce problème est une mise en œuvre « de collection virtualiser » qui maintient les références faibles à ses éléments avec un algorithme pour aller chercher / la création d'éléments. Le code de cette collection est assez complexe, ce avec toutes les interfaces nécessaires et les structures de données pour suivre efficacement les plages de données chargées, mais voici une API partielle pour une classe qui virtualisée basée sur les indices:

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

La structure de données interne est ici un arbre équilibré de plages de données, chacune des données de distance contenant un index de début et un tableau de références faibles.

Cette classe a été conçue pour être sous-classé pour fournir la logique pour charger les données. Voilà comment cela fonctionne:

  • Dans le constructeur de sous-classe, RecordInsertOrDelete est appelée à définir la taille de la collection initiale
  • Lorsqu'un élément est accessible à l'aide IList/ICollection/IEnumerable, l'arbre est utilisé pour trouver l'élément de données. Si trouvé dans l'arbre et il y a une référence faible et la référence faible encore des points à un objet de la vie, cet objet est retourné, sinon il est chargé et renvoyé.
  • Lorsqu'un élément doit être chargé, une plage d'index est calculé en effectuant une recherche avant et en arrière si la structure de données pour le suivant / précédent élément déjà chargé, puis est appelé FetchItems abstraite de sorte que la sous-classe peut charger les éléments.
  • Dans la mise en œuvre de FetchItems de sous-classe, les éléments sont récupérés puis RecordFetchedItems est appelé à mettre à jour l'arbre des gammes avec les nouveaux éléments. Une certaine complexité ici est nécessaire de fusionner les noeuds adjacents pour éviter une trop grande croissance des arbres.
  • Lorsque la sous-classe reçoit une notification des données externes change, il peut appeler RecordInsertOrDelete mettre à jour le suivi des index. Cette mise à jour des index de départ. Pour un insert, cela peut aussi diviser une plage, et pour supprimer cela peut nécessiter une ou plusieurs plages à recréée plus petit. Ce même algorithme est utilisé en interne lorsque les éléments sont ajoutés / supprimés via les interfaces IList et IList<T>.
  • La méthode de Cleanup est appelée dans le contexte de la recherche de façon incrémentielle l'arbre de gammes pour des gammes WeakReferences et entiers qui peuvent être disposés, ainsi que pour les gammes qui sont trop rares (par exemple seulement une WeakReference dans une plage de 1000 emplacements)

Notez que FetchItems est passé une gamme d'articles déchargés de sorte qu'il peut utiliser une heuristique pour charger plusieurs éléments à la fois. Une simple telle heuristique chargerait les 100 articles ou jusqu'à la fin de l'écart actuel, selon la première éventualité.

Avec un VirtualizingCollection, de WPF virtualisation intégrée provoque le chargement de données au moment opportun pour ListBox, ComboBox, etc, aussi longtemps que vous utilisez par exemple. VirtualizingStackPanel au lieu de StackPanel.

Pour une TreeView, il en faut pas de plus: Dans le HierarchicalDataTemplate établi un MultiBinding pour ItemsSource qui se lie à votre ItemsSource réel et aussi IsExpanded sur le parent basé sur un modèle. Le convertisseur de la MultiBinding retourne sa première valeur (la ItemsSource) si la deuxième valeur (la valeur de IsExpanded) est vraie, sinon elle retourne null. Qu'est-ce que cela ne se fait en sorte que lorsque vous l'effondrement d'un noeud dans la TreeView toutes les références au contenu de collecte sont immédiatement abandonnées afin que VirtualizingCollection peut les nettoyer.

Notez que la virtualisation ne doit pas être fait sur la base des indices. Dans un scénario d'arbre, il peut être tout ou rien, et dans un scénario de liste à une estimation peut être utilisé et des plages remplies en tant que de besoin à l'aide d'un mécanisme « start touche » / « touche de fin ». Ceci est utile lorsque les données sous-jacentes peuvent changer et la vue virtualisé doit suivre son emplacement actuel basé sur lequel la clé est en haut de l'écran.

Autres conseils

    <TreeView
        VirtualizingStackPanel.IsVirtualizing = "True"
        VirtualizingStackPanel.VirtualizationMode = "Recycling" 
VirtualizingStackPanel.CleanUpVirtualizedItem="TreeView_CleanUpVirtualizedItem">
            <TreeView.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel />
                </ItemsPanelTemplate>
            </TreeView.ItemsPanel>
        </TreeView>
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top