2009-12-30 7 views
25

Mon prototype affiche les «documents» contenant les «pages» représentés par des images miniatures. Chaque document peut avoir n'importe quel nombre de pages. Par exemple, il peut y avoir 1000 documents de 5 pages chacun, ou 5 documents de 1000 pages chacun, ou quelque part entre les deux. Les documents ne contiennent pas d'autres documents. Dans mon balisage xaml j'ai un ListBox, dont ItemsTemplate fait référence à un innerItemsTemplate qui a également un ListBox. Je veux les 2 niveaux d'éléments sélectionnés afin que je puisse effectuer diverses opérations sur des documents ou des pages (supprimer, fusionner, déplacer vers un nouvel emplacement, etc.). Le innerItemsTemplate ListBox utilise un WrapPanel comme ItemsPanelTemplate.WPF ListBox avec une ListBox - Virtualisation et défilement de l'interface utilisateur

Pour le scénario où j'ai un grand nombre de documents avec quelques pages chacun (disons, 10000 documents avec 5 pages chacun), le défilement fonctionne très bien grâce à l'interface utilisateur de virtualisation par le VirtualizingStackPanel. Cependant, j'ai des problèmes si j'ai un grand nombre de pages. Un document avec 1000 pages affichera seulement environ 50 à la fois (tout ce qui correspond à l'écran), et quand je défilerai vers le bas, le ListBox externe se déplace vers le document suivant, en sautant les 950 pages ou qui n'étaient pas visibles. Parallèlement à cela, il n'y a pas VirtualzingWrapPanel donc la mémoire de l'application augmente vraiment.

Je me demande si je vais à ce sujet dans le bon sens, en particulier , car il est difficile à expliquer! Je voudrais être en mesure d'afficher 10000 documents avec 1000 pages chacun (ne montrant que ce qui correspond à l'écran), en utilisant la virtualisation de l'interface utilisateur, et aussi le défilement lisse.

Comment puis-je m'assurer que le défilement se déplace dans toutes les pages du document avant d'afficher le document suivant, tout en conservant la virtualisation de l'interface utilisateur? La barre de défilement semble se déplacer uniquement vers le document suivant.

Est-il logique de représenter "documents" et "pages" - avec ma méthode actuelle d'utilisation d'un ListBox dans un ListBox?

J'apprécierais beaucoup vos idées. Merci.

Répondre

24

La réponse est surprenante:

  • Si vous utilisez ItemsControl ou ListBox vous obtiendrez le comportement que vous rencontrez, où Si vous utilisez TreeView à la place, le contrôle défilera doucement pour que vous puissiez faire défiler votre document vers le document suivant, mais il sera toujours capable de virtualiser.

Je pense que la raison pour laquelle l'équipe a choisi WPF ce comportement est que TreeView a souvent des éléments qui sont plus grandes que la zone visible, alors qu'habituellement ListBox es ne le font pas.

Dans tous les cas, il est trivial dans WPF pour faire un regard TreeView et agir comme un ListBox ou ItemsControl en modifiant simplement le ItemContainerStyle. C'est très simple. Vous pouvez rouler le vôtre ou simplement copier sur le modèle approprié à partir du fichier de thème du système.

Alors vous aurez quelque chose comme ceci:

<TreeView ItemsSource="{Binding documents}"> 
    <TreeView.ItemsPanel> 
    <ItemsPanelTemplate> 
     <VirtualizingStackPanel /> 
    </ItemsPanelTemplate> 
    </TreeView.ItemsPanel> 
    <TreeView.ItemContainerStyle> 
    <Style TargetType="{x:Type TreeViewItem}"> 
     <Setter Property="Template"> 
     <Setter.Value> 
      <ControlTemplate TargetType="{x:Type TreeViewItem}"> 
      <ContentPresenter /> <!-- put your desired container style here with a ContentPresenter inside --> 
      </ControlTemplate> 
     </Setter.Value> 
     </Setter> 
    </Style> 
    </TreeView.ItemContainerStyle> 
    <TreeView.ItemTemplate> 
    <DataTemplate TargetType="{x:Type my:Document}"> 
     <Border BorderThickness="2"> <!-- your document frame will be more complicated than this --> 
     <ItemsControl ItemsSource="{Binding pages}"> 
      ... 
     </ItemsControl> 
     </Border> 
    </DataTemplate> 
    </TreeView.ItemTemplate> 
</TreeView> 

Mise en défilement à base de pixels et de style ListBox multiselect à travailler ensemble

Si vous utilisez cette technique pour obtenir le défilement à base de pixels, votre ItemsControl externe qui montre les documents ne peut pas être un ListBox (parce que ListBox n'est pas une sous-classe de TreeView ou TreeViewItem). Ainsi, vous perdez tout le support multisélect de ListBox. Pour autant que je sache, il n'y a aucun moyen d'utiliser ces deux fonctionnalités ensemble sans inclure un peu de votre propre code pour une fonctionnalité ou l'autre.

Si vous avez besoin des deux ensembles de fonctionnalités dans le même contrôle, vous avez essentiellement plusieurs options:

  1. Mettre en oeuvre multi-sélection vous dans une sous-classe de TreeViewItem. Utilisez TreeViewItem au lieu de TreeView pour le contrôle externe, car il permet à plusieurs enfants d'être sélectionnés. Dans le modèle à l'intérieur ItemsContainerStyle: Ajoutez un CheckBox autour du ContentPresenter, le modèle liez le CheckBox à IsSelected et le style CheckBox avec le modèle de contrôle pour obtenir l'apparence que vous voulez. Ajoutez ensuite vos propres gestionnaires d'événements de souris pour gérer Ctrl-Click et Maj-Click pour multiselect.

  2. Implémentez vous-même la virtualisation à défilement de pixels dans une sous-classe de VirtualizingPanel. Ceci est relativement simple, car la plus grande partie de la complexité de VirtualizingStackPanel est liée au défilement non-pixel et au recyclage de conteneur. Dan Crevier's Blog a quelques informations utiles pour comprendre VirtualizingPanel.

+0

Cette approche fonctionne en effet pour moi aussi loin que la virtualisation de l'interface utilisateur. Maintenant, j'ai juste besoin d'avoir le comportement de ListBox pour sélectionner des éléments (pages ou documents dans ce cas). Comment puis-je obtenir des modes de sélection multiples et étendus similaires à ListBox? –

+0

De plus, je mets le ItemsPanelTemplate dans ItemsControl à un WrapPanel, qui ne semble pas envelopper quand je redimensionne l'application - il semble se comporter plus comme un stackPanel. Dans l'ensemble, je pense que la réponse ci-dessus par Ray me fait aller dans la bonne direction. –

+0

Je suis si heureux que je suis tombé sur ce post, il m'a sauvé en me tirant tous mes cheveux. – Bijington

0

Permettez-moi de faire précéder cette réponse d'une question: Est-ce que l'utilisateur doit voir chaque vignette de chaque élément de la liste à tout moment? Si la réponse à cette question est «non», alors il serait peut-être possible de limiter le nombre de pages visibles dans le modèle d'élément interne (étant donné que vous avez indiqué que le défilement fonctionne bien avec, disons, 5 pages) et utiliser un modèle distinct 'article sélectionné' qui est plus grand et affiche toutes les pages de ce document?Billy Hollis explique comment « pop » un élément sélectionné dans une zone de liste sur dnrtv episode 115

+0

pas l'utilisateur n'a pas à voir chaque miniature à l'intérieur de chaque élément de la liste en tout temps - ils ont juste besoin d'être en mesure de faire défiler pour arriver à d'autres articles –

36

Il est possible d'atteindre VirtualizingStackPanels de défilement lisse dans WPF 4.0 sans sacrifier la virtualisation si vous êtes prêt à utiliser la réflexion pour accéder à la fonctionnalité privée du VirtualizingStackPanel.Tout ce que vous avez à faire est de mettre la propriété privée IsPixelBased du VirtualizingStackPanel à true.

Notez que dans .Net 4.5 il n'y a pas besoin de ce bidouillage comme vous pouvez définir VirtualizingPanel.ScrollUnit = "Pixel".

Pour le rendre vraiment facile, voici un code:

public static class PixelBasedScrollingBehavior 
{ 
    public static bool GetIsEnabled(DependencyObject obj) 
    { 
     return (bool)obj.GetValue(IsEnabledProperty); 
    } 

    public static void SetIsEnabled(DependencyObject obj, bool value) 
    { 
     obj.SetValue(IsEnabledProperty, value); 
    } 

    public static readonly DependencyProperty IsEnabledProperty = 
     DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(PixelBasedScrollingBehavior), new UIPropertyMetadata(false, HandleIsEnabledChanged)); 

    private static void HandleIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     var vsp = d as VirtualizingStackPanel; 
     if (vsp == null) 
     { 
      return; 
     } 

     var property = typeof(VirtualizingStackPanel).GetProperty("IsPixelBased", 
                    BindingFlags.NonPublic | BindingFlags.Instance); 

     if (property == null) 
     { 
      throw new InvalidOperationException("Pixel-based scrolling behaviour hack no longer works!"); 
     } 

     if ((bool)e.NewValue == true) 
     { 
      property.SetValue(vsp, true, new object[0]); 
     } 
     else 
     { 
      property.SetValue(vsp, false, new object[0]); 
     } 
    } 
} 

Pour utiliser sur un ListBox, par exemple, vous feriez:

<ListBox> 
    <ListBox.ItemsPanel> 
     <ItemsPanelTemplate> 
     <VirtualizingStackPanel PixelBasedScrollingBehavior.IsEnabled="True"> 
      </VirtualizingStackPanel> 
     </ItemsPanelTemplate> 
    </ListBox.ItemsPanel> 
</ListBox> 
+19

C'était vraiment utile, +1. Pour les futurs visiteurs utilisant .NET 4.5, vous devez définir 'VirtualizingPanel.ScrollUnit =" Pixel "' sur votre 'ListBox' lui-même, pas sur' VirtualizingStackPanel' qui contient le contenu. –

+0

Ok, ça ne marche pas pour moi. Je ne sais pas pourquoi. –

+0

@ViktorLaCroix: avez-vous installé .Net 4.5 sur votre machine? Parce que c'est une mise à jour sur place pour. Net 4.0, je pense que cela brise ce hack. –

4

Cela a fonctionné pour moi. Semble quelques attributs simples fera (4.5 .NET)

<ListBox    
    ItemsSource="{Binding MyItems}" 
    VirtualizingStackPanel.IsVirtualizing="True" 
    VirtualizingStackPanel.ScrollUnit="Pixel"/>