Wirtualizacja ItemsControl?

125

Mam ItemsControllistę zawierającą dane, które chciałbym zwirtualizować, ale VirtualizingStackPanel.IsVirtualizing="True"nie wydaje się działać z ItemsControl.

Czy tak jest naprawdę, czy jest inny sposób na zrobienie tego, którego nie jestem świadomy?

Aby przetestować, użyłem następującego bloku kodu:

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

Jeśli zmienię na ItemsControla ListBox, widzę, że Initializedzdarzenie odbywa się tylko kilka razy (ogromne marginesy są po prostu tak, że muszę przejść tylko przez kilka rekordów), jednak w miarę ItemsControlinicjalizacji każdej pozycji.

Próbowałem ustawić wartość ItemsControlPanelTemplatea, VirtualizingStackPanelale to nie pomaga.

Rachel
źródło

Odpowiedzi:

219

W rzeczywistości chodzi o znacznie więcej niż tylko ItemsPanelTemplatewykorzystanie VirtualizingStackPanel. Wartość domyślna ControlTemplatefor ItemsControlnie ma znaku ScrollViewer, który jest kluczem do wirtualizacji. Dodanie do domyślnego szablonu kontrolnego dla ItemsControl(użycie szablonu kontrolnego ListBoxjako szablonu) daje nam następujące korzyści:

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

(Przy okazji, świetnym narzędziem do przeglądania domyślnych szablonów kontrolek jest Pokaż szablon )

Rzeczy, na które należy zwrócić uwagę:

Musisz ustawić ScrollViewer.CanContentScroll="True", zobacz tutaj, dlaczego.

Zauważ też, że umieściłem VirtualizingStackPanel.VirtualizationMode="Recycling". Zmniejszy to liczbę TextBlock_Initializedwywołań, niezależnie od tego, ile bloków tekstowych jest widocznych na ekranie. Więcej informacji na temat wirtualizacji interfejsu użytkownika można znaleźć tutaj .

EDIT: Zapomniałem stwierdzić oczywisty: jako alternatywnego rozwiązania, można po prostu zastąpić ItemsControlz ListBox:) Również sprawdzić ten Optymalizacja wydajności na stronie MSDN i zauważ, że ItemsControlnie jest w „formantów, które wdrażają wydajność funkcje” stół, dlatego musimy edytować szablon kontrolny.

DavidN
źródło
1
Dziękuję, właśnie tego szukałem! Szukałem innego sposobu wyboru niż pole listy i wtedy pomyślałem, że najłatwiej będzie to zrobić z kontrolką elementów.
Rachel
Jeśli ten element kontrolny jest dalej zagnieżdżony, należy również nadać mu wysokość. W przeciwnym razie przeglądarka przewijania nie jest wyświetlana.
buckley
9
„Zauważ też, że umieściłem VirtualizingStackPanel.VirtualizationMode = Recycling”. Czy to nie powinno znajdować się w dostarczonej przez Ciebie próbce?
Buckley,
Czy wirtualizacja również praca przy okład ItemsControlna ScrollViewerinstread dodając Scrolldo ControlTemplate?
demo
@DavidN Gdzie lub jak mogę umieścić nagłówki kolumn w Twoim rozwiązaniu?
Ozkan
37

Opierając się na odpowiedzi DavidaN, oto styl, którego możesz użyć w ItemsControl, aby go zwirtualizować:

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

Nie podoba mi się sugestia użycia ListBox, ponieważ pozwalają one na wybór wierszy, w których niekoniecznie chcesz.

Zodman
źródło
-3

Po prostu domyślnym ItemsPanelnie jest plik VirtualizingStackPanel. Musisz to zmienić:

<ItemsControl>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>
Abe Heidebrecht
źródło
8
Głosuję w dół, ponieważ rozwiązanie jest niekompletne. Aby włączyć wirtualizację, musisz użyć przeglądarki scrollviewer w szablonie.
Saraf Talukder