0

I have a List of 774 items. When I set it to Items property (also List) of ViewModel bound to ItemsSource, it takes about 10+ seconds.

I have already tried the answer from Virtualizing an ItemsControl? and it did not work - still 10+ seconds.

This is unmodified code. Note that ItemsControl is inside a ScrollViewer.

XAML:

<Grid d:DataContext="{x:Static local:RulesListDesignModel.Instance}" Background="{StaticResource ForegroundLightBrush}">
        <ScrollViewer VerticalScrollBarVisibility="Auto">
            <ItemsControl ItemsSource="{Binding Items}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <local:RulesListItemControl />
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </ScrollViewer>
    </Grid>

C#

ViewModelApplication.CurrentRulesListViewModel.Items = mList;

This is XAML after modifying the code according to the answer from Virtualizing an ItemsControl? (seems to take a bit more than 10 seconds):

<Grid d:DataContext="{x:Static local:RulesListDesignModel.Instance}" Background="{StaticResource ForegroundLightBrush}">
    <ScrollViewer VerticalScrollBarVisibility="Auto">
        <ItemsControl ItemsSource="{Binding Items}"
                      VirtualizingStackPanel.IsVirtualizing="True"
                      ScrollViewer.CanContentScroll="True">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <local:RulesListItemControl />
                </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>
    </ScrollViewer>
</Grid>
BionicCode
  • 1
  • 4
  • 28
  • 44
Quasimodo
  • 57
  • 1
  • 13
  • 1
    Scroll viewer breaks any virtualization – ingvar Jul 03 '20 at 08:28
  • @ingvar do you have a source for that? I'm curious – Corentin Pane Jul 03 '20 at 08:47
  • @ingvar I've heard of that and actually more agree than disagree, though other answers suggested adding ScrollViewer FOR virtualization. So what's the solution? – Quasimodo Jul 03 '20 at 08:50
  • @ingvar you need a `ScrollViewer` of course. It's the `ScrollViewer` that allows to show e.g. 20 items and hide 1,000. It's the `ScrollViewer` that gives information about visible and hidden items and when hidden items should become visible. UI Virtualization basically means generating a set of items only when scrolling. The only setting that would disable virtualization is when `ScrollViewer.CanContentScroll` is set to `false`. This would set the scroll unit to pixel and the items panel has no easy chance to determine the number of visible items. – BionicCode Jul 03 '20 at 09:51
  • @BionicCode I've tried setting `ScrollViewer.CanContentScroll="True"` on the outer `ScrollViewer` - it's the same 10+ – Quasimodo Jul 03 '20 at 10:05
  • What you are showing here is different from what is shown in the answer to the original question. The important point is not to have an outer ScrollViewer, but only the ScrollViewer in the ControlTemplate of the ItemsControl. An outer ScrollViewer always lets the ItemsControl expand to its full extent, and hence breaks any virtualization. – Clemens Jul 03 '20 at 11:41

1 Answers1

4

You should use a ListBox or ListView, which have their ScrollViewer integrated and UI virtualization enabled by default. There is no need to use the more basic ItemsControl.

You should try the following to use UI virtualization:

<ListBox VirtualizingStackPanel.VirtualizationMode="Recycling" />

Setting VirtualizingStackPanel.VirtualizationMode to VirtualizationMode.Recycling improves the scroll performance.

If you want to stay with the ItemsControl (why would you?) you need to rework the visual tree.

You are currently using two ScrollViewers. One inside the template and one wrapped around the ItemsControl. Note that since ScrollViewer.CanContentScroll defaults to false, the inner ScrollViewer is responsible to disable UI virtualization. Setting CanContentScroll to true is essential, as it will set the scroll unit to item (instead of pixel). The VirtualizingStackPanel needs to know the number of visible items.

You should remove the outer ScrollViewer and your performance should significantly improve:

<ItemsControl ItemsSource="{Binding Items}">
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <local:RulesListItemControl />
    </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>

But more important is to focus on your custom RulesListItemControl. This control is loaded for every item. Complex controls introduces complex initialization. You should try to reduce the visual tree of this control.

Remove every Border that is not needed, replace Label with TextBlock, revisit triggers etc. The goal is to reduce rendering time of each item container.
To do this, you need to override the ControlTemplate of the controls that you use to compose the RulesListItemControl.

BionicCode
  • 1
  • 4
  • 28
  • 44
  • Thank you, sir! Works just instant. Btw not the first time your gave me the answer. Though after all, the user "ingvar" was right because the outer scrollviewer broke virtualization, didn't it? – Quasimodo Jul 03 '20 at 10:37
  • 1
    Nice, I'm happy I could help you. Good to see you again, didn't realized that we have met before, although you name sounded familiar. Yes, he was partially right. When I read his comment it read like _"don't use a `ScrollViewer`"_. From your immediate reply I can tell that you understood it the same way I did. He should have been more precise and telling you that you are using two `ScrollViewers`. We don't know if he realized this or what he meant. – BionicCode Jul 03 '20 at 10:42
  • Well, you are right, I got it as a choice "ScrollViewer or Virtualization" – Quasimodo Jul 03 '20 at 10:54