Virtualisant un ItemsControl?
-
03-10-2019 - |
Question
J'ai un ItemsControl
contenant une liste de données que je voudrais virtualiser, mais VirtualizingStackPanel.IsVirtualizing="True"
ne semble pas fonctionner avec un ItemsControl
.
Est-ce vraiment le cas ou est-il une autre façon de faire ce que je ne suis pas au courant?
Pour tester J'utilise le bloc de code suivant:
<ItemsControl ItemsSource="{Binding Path=AccountViews.Tables[0]}"
VirtualizingStackPanel.IsVirtualizing="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Initialized="TextBlock_Initialized"
Margin="5,50,5,50" Text="{Binding Path=Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Si je change le ItemsControl
à un ListBox
, je peux voir que l'événement Initialized
ne fonctionne que quelques fois (les énormes marges sont juste pour que je ne dispose que de passer par quelques dossiers), mais comme un ItemsControl
chaque élément obtient initialisé.
Je l'ai essayé de placer le ItemsControlPanelTemplate
à un VirtualizingStackPanel
mais cela ne semble pas aider.
La solution
Il est en réalité beaucoup plus à lui que juste faire l'utilisation de ItemsPanelTemplate
VirtualizingStackPanel
. Le ControlTemplate
par défaut pour ItemsControl
ne dispose pas d'un ScrollViewer
, qui est la clé de la virtualisation. Ajout au modèle de contrôle par défaut pour ItemsControl
(en utilisant le modèle de commande pour ListBox
comme modèle) nous donne ce qui suit:
<ItemsControl
VirtualizingStackPanel.IsVirtualizing="True"
ScrollViewer.CanContentScroll="True"
ItemsSource="{Binding Path=AccountViews.Tables[0]}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock
Initialized="TextBlock_Initialized"
Text="{Binding Path=Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Template>
<ControlTemplate>
<Border
BorderThickness="{TemplateBinding Border.BorderThickness}"
Padding="{TemplateBinding Control.Padding}"
BorderBrush="{TemplateBinding Border.BorderBrush}"
Background="{TemplateBinding Panel.Background}"
SnapsToDevicePixels="True">
<ScrollViewer
Padding="{TemplateBinding Control.Padding}"
Focusable="False">
<ItemsPresenter
SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</ScrollViewer>
</Border>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
(BTW, un excellent outil pour regarder les modèles de contrôle par défaut est Show Me Le modèle )
A noter:
Vous devez ScrollViewer.CanContentScroll="True"
ensemble, voir pourquoi.
Notez également que je mets VirtualizingStackPanel.VirtualizationMode="Recycling"
. Cela permettra de réduire le nombre de fois TextBlock_Initialized
est appelé à cependant beaucoup de TextBlocks sont visibles à l'écran. Vous pouvez en savoir plus sur l'interface utilisateur virtualisation ici
.
EDIT: Vous avez oublié de dire l'évidence: comme solution de rechange, vous pouvez simplement remplacer ItemsControl
avec ListBox
:)
En outre, consultez cette Optimisation des performances à la page MSDN et avis que ItemsControl
n'est pas dans les « contrôles qui implémentent des caractéristiques de performances » table, ce qui est la raison pour laquelle nous devons modifier le modèle de contrôle.
Autres conseils
Miser sur la réponse de DavidN, voici un style que vous pouvez utiliser sur un ItemsControl pour virtualiser il:
<!--Virtualised ItemsControl-->
<Style x:Key="ItemsControlVirtualizedStyle" TargetType="ItemsControl">
<Setter Property="VirtualizingStackPanel.IsVirtualizing" Value="True"/>
<Setter Property="ScrollViewer.CanContentScroll" Value="True"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ItemsControl">
<Border
BorderThickness="{TemplateBinding Border.BorderThickness}"
Padding="{TemplateBinding Control.Padding}"
BorderBrush="{TemplateBinding Border.BorderBrush}"
Background="{TemplateBinding Panel.Background}"
SnapsToDevicePixels="True"
>
<ScrollViewer Padding="{TemplateBinding Control.Padding}" Focusable="False">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Je ne aime pas la suggestion d'utiliser un ListBox car ils permettent la sélection de lignes où vous ne voulez pas nécessairement.
Il est juste que la ItemsPanel
par défaut n'est pas un VirtualizingStackPanel
. Vous devez changer:
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>