Virtualizando um ItemsControl?

125

Eu tenho um ItemsControlcontendo uma lista de dados que gostaria de virtualizar, no entanto VirtualizingStackPanel.IsVirtualizing="True", parece não funcionar com um ItemsControl.

É realmente esse o caso ou existe outra maneira de fazer isso que eu não conheço?

Para testar, tenho usado o seguinte bloco 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>

Se eu mudar ItemsControlpara a ListBox, posso ver que o Initializedevento ocorre apenas algumas vezes (as margens enormes são apenas para que eu só precise passar por alguns registros), no entanto, à medida que ItemsControlcada item é inicializado.

Eu tentei definir o ItemsControlPanelTemplatecomo a VirtualizingStackPanelmas isso não parece ajudar.

Rachel
fonte

Respostas:

219

Na verdade, há muito mais do que apenas fazer o ItemsPanelTemplateuso VirtualizingStackPanel. O padrão ControlTemplatepara ItemsControlnão possui a ScrollViewer, que é a chave da virtualização. A adição ao modelo de controle padrão para ItemsControl(usando o modelo de controle ListBoxcomo modelo) nos fornece o seguinte:

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

(BTW, uma ótima ferramenta para examinar modelos de controle padrão é Mostrar-me o modelo )

Coisas a serem observadas:

Você tem que definir ScrollViewer.CanContentScroll="True", veja aqui o porquê.

Observe também que eu coloquei VirtualizingStackPanel.VirtualizationMode="Recycling". Isso reduzirá o número de vezes que TextBlock_Initializedé chamado, no entanto, muitos TextBlocks são visíveis na tela. Você pode ler mais sobre virtualização da interface do usuário aqui .

EDIT: Esqueceu de afirmar o óbvio: como uma solução alternativa, você pode simplesmente substituir ItemsControlpor ListBox:) Além disso, confira esta página Otimizando o desempenho no MSDN e observe que ItemsControlnão está na tabela "Controles que implementam recursos de desempenho", e é por isso que precisamos editar o modelo de controle.

DavidN
fonte
1
Obrigado, esse é exatamente o tipo de coisa que eu estava procurando! Eu estava procurando por um tipo de comportamento de seleção diferente de uma caixa de listagem e, na época, achei que seria mais fácil fazer um controle de itens.
Rachel
Se esse controle de itens ainda estiver aninhado, você também deve dar uma altura. Caso contrário, o visualizador de rolagem não será exibido.
Buckley
9
"Observe também que eu coloquei VirtualizingStackPanel.VirtualizationMode = Recycling". Não deveria estar na amostra que você fornece?
Buckley
A virtualização também trabalho quando envoltório ItemsControlem ScrollViewerinstread acrescentando Scrollque ControlTemplate?
demo
@DavidN Onde ou como posso colocar os cabeçalhos das colunas na sua solução?
Ozkan #
37

Com base na resposta de DavidN, aqui está um estilo que você pode usar em um ItemsControl para virtualizá-lo:

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

Não gosto da sugestão de usar um ListBox, pois eles permitem a seleção de linhas nas quais você não deseja necessariamente.

Zodman
fonte
-3

É só que o padrão ItemsPanelnão é a VirtualizingStackPanel. Você precisa alterá-lo:

<ItemsControl>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>
Abe Heidebrecht
fonte
8
Estou votando para baixo, pois a solução está incompleta. Você precisa usar um visualizador de rolagem no modelo para ativar a virtualização.
Saraf Talukder