web-dev-qa-db-fra.com

Virtualiser un ItemsControl?

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.

118
Rachel

Il y a en fait beaucoup plus que simplement faire utiliser ItemsPanelTemplateVirtualizingStackPanel. 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.

203
DavidN

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.

29
Zodman