wpf - ¿Virtualizando un ItemsControl?
virtualization virtualizingstackpanel (3)
Tengo un ItemsControl
contiene una lista de datos que me gustaría virtualizar, sin embargo, VirtualizingStackPanel.IsVirtualizing="True"
no parece funcionar con ItemsControl
.
¿Es este realmente el caso o hay otra forma de hacer esto de la que no tengo conocimiento?
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 ItemsControl
a un ListBox
, puedo ver que el evento Initialized
solo se ejecuta un puñado de veces (los enormes márgenes son solo para que solo tenga que pasar por algunos registros), sin embargo, como ItemsControl
cada elemento se inicializa.
He intentado establecer ItemsControlPanelTemplate
en VirtualizingStackPanel
pero eso no parece ser de ayuda.
En realidad, hay mucho más que hacer que ItemsPanelTemplate
use VirtualizingStackPanel
. El ControlTemplate
predeterminado para ItemsControl
no tiene un ScrollViewer
, que es la clave para la virtualización. Agregar a la plantilla de control predeterminada para ItemsControl
(utilizando la plantilla de control para ListBox
como plantilla) nos da lo siguiente:
<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>
(Por cierto, una gran herramienta para buscar plantillas de control predeterminadas es Muéstreme la plantilla )
Cosas para notar:
Tienes que configurar ScrollViewer.CanContentScroll="True"
, mira here por qué.
También tenga en cuenta que pongo VirtualizingStackPanel.VirtualizationMode="Recycling"
. Esto reducirá el número de veces que se llama a TextBlock_Initialized, sin embargo, muchos TextBlocks son visibles en la pantalla. Puede leer más sobre la virtualización de la interfaz de usuario here .
EDITAR: Olvidé indicar lo obvio: como solución alternativa, puede simplemente reemplazar ItemsControl
con ListBox
:). Además, consulte esta página Optimización del rendimiento en MSDN y observe que ItemsControl
no se encuentra en la tabla "Controles que implementan características de rendimiento". por eso necesitamos editar la plantilla de control.
Es solo que el ItemsPanel
predeterminado no es un VirtualizingStackPanel
. Tienes que cambiarlo:
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Sobre la base de la respuesta de DavidN, este es 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 desea.