¿Virtualizando un control de ítems?

125

Tengo un que ItemsControlcontiene una lista de datos que me gustaría virtualizar, sin embargo VirtualizingStackPanel.IsVirtualizing="True", no parece funcionar con un ItemsControl.

¿Es este realmente el caso o hay otra forma de hacerlo que no conozco?

Para probar he estado usando el siguiente bloque de código:

<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 cambio el ItemsControla a ListBox, puedo ver que el Initializedevento solo se ejecuta un puñado de veces (los enormes márgenes son solo para que solo tenga que revisar algunos registros), sin embargo, a medida ItemsControlque se inicializa cada elemento.

He intentado configurar el ItemsControlPanelTemplatea VirtualizingStackPanelpero eso no parece ayudar.

Rachel
fuente

Respuestas:

219

En realidad, hay mucho más que solo hacer el ItemsPanelTemplateuso VirtualizingStackPanel. El valor predeterminado ControlTemplatepara ItemsControlno tiene un ScrollViewer, que es la clave para la virtualización. Agregar a la plantilla de control predeterminada para ItemsControl(usando la plantilla de control ListBoxcomo plantilla) nos da lo siguiente:

<ItemsControl ItemsSource="{Binding AccountViews.Tables[0]}">
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <TextBlock Initialized="TextBlock_Initialized"
                 Text="{Binding Name}" />
    </DataTemplate>
  </ItemsControl.ItemTemplate>

  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <VirtualizingStackPanel IsVirtualizing="True"
                              VirtualizationMode="Recycling" />
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>

  <ItemsControl.Template>
    <ControlTemplate TargetType="ItemsControl">
      <Border BorderThickness="{TemplateBinding BorderThickness}"
              BorderBrush="{TemplateBinding BorderBrush}"
              Background="{TemplateBinding Background}">
        <ScrollViewer CanContentScroll="True" 
                      Padding="{TemplateBinding Padding}"
                      Focusable="False">
          <ItemsPresenter />
        </ScrollViewer>
      </Border>
    </ControlTemplate>
  </ItemsControl.Template>
</ItemsControl>

(Por cierto, una gran herramienta para mirar las plantillas de control predeterminadas es Show Me The Template )

Cosas a tener en cuenta:

Tienes que configurar ScrollViewer.CanContentScroll="True", mira aquí por qué.

También note que lo pongo VirtualizingStackPanel.VirtualizationMode="Recycling". Esto reducirá la cantidad de veces que TextBlock_Initializedse llama, sin embargo, muchos TextBlocks son visibles en la pantalla. Puede leer más sobre la virtualización de la interfaz de usuario aquí .

EDITAR: Olvidé decir lo obvio: como una solución alternativa, solo puede reemplazar ItemsControlcon ListBox:) Además, consulte esta página Optimización del rendimiento en MSDN y observe que ItemsControlno está en la tabla "Controles que implementan características de rendimiento", por eso Necesitamos editar la plantilla de control.

DavidN
fuente
1
¡Gracias, ese es exactamente el tipo de cosas que estaba buscando! Estaba buscando un tipo diferente de comportamiento de selección que un cuadro de lista y en ese momento pensé que sería más fácil hacerlo con un control de elementos.
Rachel,
Si este control de elementos está anidado aún más, también debe darle una altura. De lo contrario, no se muestra el scrollviewer.
buckley
9
"Observe también que puse VirtualizingStackPanel.VirtualizationMode = Recycling". ¿No se supone que debe estar en la muestra que proporciona?
Buckley
También lo hace la virtualización trabajo cuando envoltura ItemsControlen ScrollViewerinstread añadiendo Scrolla ControlTemplate?
demo
@DavidN ¿Dónde o cómo puedo poner los encabezados de columna en su solución?
Ozkan
37

Basándose en la respuesta de DavidN, aquí hay un estilo que puede usar en un ItemsControl para virtualizarlo:

<!--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>

No me gusta la sugerencia de usar un ListBox ya que permiten la selección de filas donde no necesariamente lo quieres.

Zodman
fuente
-3

Es solo que el valor predeterminado ItemsPanelno es a VirtualizingStackPanel. Necesitas cambiarlo:

<ItemsControl>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>
Abe Heidebrecht
fuente
8
Estoy votando porque la solución está incompleta. Debe usar un visor de desplazamiento en la plantilla para habilitar la virtualización.
Saraf Talukder