2010-01-29 11 views
4

J'ai un TreeView qui est lié à une arborescence d'instances ViewModel. Le problème est que les données du modèle proviennent d'un référentiel lent, j'ai donc besoin de la virtualisation des données. La liste de sous-ViewModel sous un nœud ne doit être chargée que lorsque le nœud de vue de l'arbre parent est développé et qu'il doit être déchargé lorsqu'il est réduit. Comment cela peut-il être mis en œuvre tout en respectant les principes MVVM?MVVM vs virtualisation des données

Comment ViewModel peut-il être averti qu'il doit charger ou décharger des sous-nœuds? C'est à ce moment qu'un noeud a été développé ou réduit sans rien savoir de l'existence de treeview? Quelque chose me donne l'impression que la virtualisation des données ne va pas bien avec MVVM. Depuis la virtualisation des données, ViewModel doit généralement en savoir beaucoup sur l'état actuel de l'interface utilisateur et doit également contrôler un grand nombre d'aspects dans l'interface utilisateur. Prenons un autre exemple:

Une liste avec virtualisation de données. ViewModel doit contrôler la longueur du scrollthumb de ListView, car elle dépend du nombre d'éléments dans le modèle. Aussi, lorsque l'utilisateur défile, le ViewModel doit savoir à quelle position il a défilé et quelle est la taille de la listview (combien d'éléments correspondent actuellement) pour pouvoir charger la bonne partie des données du formulaire dans le référentiel.

Répondre

4

La solution la plus simple consiste à utiliser une implémentation de "virtualisation de collection" qui conserve des références faibles à ses éléments, ainsi qu'un algorithme de récupération/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(); 
} 

L'interne La structure de données est ici une arborescence équilibrée de plages de données, chaque plage de données contenant un index de départ et un tableau de références faibles.

Cette classe est conçue pour être sous-classée afin de fournir la logique de chargement effectif des données. Voilà comment cela fonctionne:

  • Dans le constructeur de sous-classe, RecordInsertOrDelete est appelé à 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 qu'il y a une référence faible et que la référence faible pointe toujours vers un objet de vie, cet objet est retourné, sinon il est chargé et retourné.
  • Lorsqu'un élément doit être chargé, une plage d'index est calculée en recherchant en avant et en arrière la structure de données pour l'élément suivant/précédent déjà chargé, puis l'abrégé FetchItems est appelé afin que la sous-classe puisse charger les éléments.
  • Dans l'implémentation de la sous-classe FetchItems, les éléments sont récupérés puis RecordFetchedItems est appelée pour mettre à jour l'arborescence des plages avec les nouveaux éléments. Une certaine complexité est nécessaire pour fusionner les nœuds adjacents afin d'éviter une trop grande croissance des arbres.
  • Lorsque la sous-classe reçoit une notification de modifications de données externes, elle peut appeler RecordInsertOrDelete pour mettre à jour le suivi d'index. Cela met à jour les index de démarrage. Pour une insertion, cela peut également diviser une plage, et pour une suppression, cela peut nécessiter de recréer une ou plusieurs plages. Ce même algorithme est utilisé en interne lorsque des éléments sont ajoutés/supprimés via les interfaces IList et IList<T>.
  • La méthode Cleanup est appelée dans le contexte de la recherche de façon incrémentielle l'arbre de gammes pour WeakReferences et les plages entières 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'éléments déchargés afin qu'il puisse utiliser une heuristique pour charger plusieurs éléments à la fois. Une telle heuristique simple consisterait à charger les 100 éléments suivants ou jusqu'à la fin de l'intervalle actuel, selon la première éventualité.

Avec un VirtualizingCollection, la virtualisation intégrée WPF provoquera 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 TreeView, il en faut pas de plus: Dans le HierarchicalDataTemplate établi un MultiBinding pour ItemsSource qui se lie à votre vrai ItemsSource et aussi IsExpanded sur le parent basé sur un modèle. Le convertisseur pour le MultiBinding renvoie sa première valeur (ItemsSource) si la deuxième valeur (la valeur IsExpanded) est vraie, sinon elle renvoie null. Cela fait en sorte que lorsque vous réduisez un nœud dans le TreeView, toutes les références au contenu de la collection sont immédiatement supprimées afin que VirtualizingCollection puisse les nettoyer.

Notez que la virtualisation n'a pas besoin d'être effectuée en fonction des index. Dans un scénario en arbre, il peut s'agir de tout ou rien, et dans un scénario de liste, un compte estimé peut être utilisé et des plages remplies si nécessaire en utilisant un mécanisme de "clé de départ"/"touche de fin". Ceci est utile lorsque les données sous-jacentes peuvent changer et que la vue virtualisée doit suivre sa position actuelle en fonction de la clé située en haut de l'écran.

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

J'ai downvoted cette réponse parce qu'elle est difficile à comprendre. Il a vraiment besoin d'amélioration, surtout en ce qui concerne la langue anglaise. – bitbonk

Questions connexes