J'ai un ItemsControl
contenant une liste de données que je voudrais virtualiser, cependant VirtualizingStackPanel.IsVirtualizing="True"
ne semble pas fonctionner avec un ItemsControl
.
Est-ce vraiment le cas ou existe-t-il une autre façon de procéder que je ne connais pas?
Pour tester, j'ai utilisé 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
en ListBox
, je peux voir que l'événement Initialized
ne s'exécute qu'une poignée de fois (les marges énormes sont juste pour que je n'aie qu'à passer par quelques enregistrements), mais en tant que ItemsControl
chaque élément est initialisé.
J'ai essayé de définir ItemsControlPanelTemplate
sur VirtualizingStackPanel
mais cela ne semble pas aider.
Il y a en fait beaucoup plus que simplement faire utiliser ItemsPanelTemplate
VirtualizingStackPanel
. Le ControlTemplate
par défaut pour ItemsControl
n'a pas de ScrollViewer
, qui est la clé de la virtualisation. L'ajout au modèle de contrôle par défaut pour ItemsControl
(en utilisant le modèle de contrôle 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 consulter les modèles de contrôle par défaut est Montrez-moi le modèle )
A noter:
Vous devez définir ScrollViewer.CanContentScroll="True"
, voir ici pour savoir pourquoi.
Notez également que je mets VirtualizingStackPanel.VirtualizationMode="Recycling"
. Cela réduira le nombre de fois TextBlock_Initialized
est appelé mais de nombreux TextBlocks sont visibles à l'écran. Vous pouvez en savoir plus sur la virtualisation de l'interface utilisateur ici .
EDIT: J'ai oublié de dire l'évidence: en tant que solution alternative, vous pouvez simplement remplacer ItemsControl
par ListBox
:) Vérifiez également ceci Optimisation des performances sur la page MSDN et notez que ItemsControl
n'est pas dans le tableau "Contrôles qui implémentent des fonctionnalités de performances", c'est pourquoi nous devons modifier le modèle de contrôle.
En s'appuyant sur la réponse de DavidN, voici un style que vous pouvez utiliser sur un ItemsControl pour le virtualiser:
<!--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 n'aime pas la suggestion d'utiliser un ListBox car ils permettent la sélection de lignes où vous ne le souhaitez pas nécessairement.