WPF - Virtualization does not work for items with collapsed visibility

Viewed 30

I have performance problem with my listview. There is a WPF ListView with groupping and virtualisation.

<ListView Name="ListOfEvents"
          SelectionMode="Extended"
          VirtualizingPanel.IsVirtualizing="True"
          VirtualizingPanel.IsVirtualizingWhenGrouping="True"
          VirtualizingPanel.VirtualizationMode="Recycling">
    <ListView.GroupStyle>
        <GroupStyle>
            <GroupStyle.ContainerStyle>
                <Style TargetType="{x:Type GroupItem}">
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate>
                                <Expander IsExpanded="False">
                                    <Expander.Header>
                                        <StackPanel Orientation="Horizontal">

                                            <TextBlock>
                                                <TextBlock.Inlines>
                                                    <Run Text="{Binding Name, Mode=OneWay}" FontWeight="Bold" Foreground="Gray"  />
                                                    <Run Text=":"/>
                                                </TextBlock.Inlines>
                                            </TextBlock>

                                            <TextBlock Text="{Binding ItemCount}" Foreground="Black" FontWeight="Bold"/>

                                        </StackPanel>
                                    </Expander.Header>
                                    <ItemsPresenter/>
                                </Expander>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </GroupStyle.ContainerStyle>
        </GroupStyle>
    </ListView.GroupStyle>

    <ListView.ItemContainerStyle>
        <Style TargetType="{x:Type ListViewItem}">
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsVisible}" Value="False" >
                    <Setter Property="Visibility" Value="Collapsed"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </ListView.ItemContainerStyle>

    <ListView.View>
        <GridView>

            <GridViewColumn Width="100" DisplayMemberBinding="{Binding Name}" >
                <GridViewColumnHeader Content="{x:Static const:Resources.IDS_NAME}" Tag ="name" />
            </GridViewColumn>

            <GridViewColumn Width="100" DisplayMemberBinding="{Binding Status}" >
                <GridViewColumnHeader Content="{x:Static const:Resources.IDS_STATUS}" Tag="status" />
            </GridViewColumn>

        </GridView>
    </ListView.View>
</ListView>

Sometimes I need to display only one item per group using <Style.Triggers>. In such cases items' property IsVisible is changed to false, but when it happens the application freezes due to layout rendering. In profiling I observe following situation:
1 - Ok. All items are visible. Virtualisation works properly: Ok. All items are visible. Virtualisation works properly
2 - Slow. One item per group. VirtualizingStackPanel contains all ListViewItems from group: Slow. One item per group. VirtualizingStackPanel contains all ListViewItems from group
Any suggestions why does it happens? How can I solve it?

1 Answers

Better don't manipulate the visual tree (the displayed item containers in particular) explicitly while virtualizing. The VirtualizingStackPanel tracks and manages the item containers. Setting the Visibility to Collapsed will remove the container from the visual tree resulting in breaking virtualization.

The recommended solution is to filter the collection source instead.
It's almost always more efficient and more convenient to manipulate the data items instead of the item containers.

To accomplish this, first remove the Visibility trigger from the container Style.
Then in your code, where you toggle the model's IsVisible property, you should apply the filter on the ICollectionView:

ICollectionView collectionView = CollectionViewSource.GetDefaultView(this.ItemsSourceCollection);

// Hide all items of each group except the first
foreach (CollectionViewGroup itemGroup in collectionView)
{
  IEnumerable<MyDataItem> groupItemsToHide = itemGroup.Items
    .Cast<MyDataItem>()
    .Skip(1);

  foreach (var groupItem in groupItemsToHide)
  {
    groupItem.IsVisible = false;
  }
}

// Apply a filter to actually hide the items
// without disabling virtualization
collectionView.Filter = item => (item as MyDataItem).IsVisible;
Related