Wonko the Sane
Wonko the Sane

Reputation: 10813

ItemsControl Not Virtualizing

I cannot get this ItemsControl to virtualize properly. Debugging shows that the collection is initialized quickly, but all the items are being added to the control instead of a subset (I simply put a TracePoint in the TextBox_Initializeevent in theUserControl` that makes up the item).

Note: I have looked at other, similar questions, but have been unable to solve this issue with those answers.

The ViewModel:

public class ImportInformationViewModel : CommandViewModel
{
    public ImportInformationViewModel()
    {

        this.PropertyChanged += ImportInformationViewModel_PropertyChanged;
    }

    private ObservableCollection<SingleTransactionViewModel> mTransactions;
    public ReadOnlyObservableCollection<SingleTransactionViewModel> Transactions
    {
        get
        {
            if (mTransactions == null)
                mTransactions = new ObservableCollection<SingleTransactionViewModel>();

            var filtered = mTransactions.Where(trans => !trans.IgnoreTransaction)
                .OrderBy(trans => trans.DateStamp)
                .ThenBy(trans => trans.TransactionName)
                .ThenBy(trans => trans.TransactionDetail);

            return new ReadOnlyObservableCollection<SingleTransactionViewModel>(new ObservableCollection<SingleTransactionViewModel>(filtered));
        }
    }

    private void ImportInformationViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "TransactionFileName")
        {
            if (File.Exists(mTransactionFileName))
           {
                mTransactions.Clear();
                // Process File (Not Shown)
                mTransactions.Add(new SingleTransactionViewModel()
                {
                    DateStamp = date,
                    TransactionDetail = someText;
                });
            }
        }

        if (e.PropertyName != "Transactions")
            NotifyPropertyChanged("Transactions");
    }
}

SingleTransactionViewModel is just another class that implements INotifyPropertyChanged. Nothing special.

Here is the control containing the ItemsControl

<UserControl x:Class="ImportInformationView">
    <UserControl.Resources>
        <CollectionViewSource x:Key="TransactionsData" Source="{Binding Transactions}">
            <CollectionViewSource.GroupDescriptions>
                <PropertyGroupDescription PropertyName="YearAndMonth" />
            </CollectionViewSource.GroupDescriptions>
        </CollectionViewSource>

        <BooleanToVisibilityConverter x:Key="booleanToVisibility" />
    </UserControl.Resources>
    <ItemsControl ItemsSource="{Binding Source={StaticResource TransactionsData}}"
                        HorizontalAlignment="Stretch"
                        HorizontalContentAlignment="Stretch"
                        ScrollViewer.CanContentScroll="True"
                        VirtualizingStackPanel.IsVirtualizing="True"
                        VirtualizingStackPanel.VirtualizationMode="Recycling"
                        MinHeight="20">
            <ItemsControl.GroupStyle>
                <GroupStyle>
                    <GroupStyle.ContainerStyle>
                        <Style TargetType="{x:Type GroupItem}">
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="{x:Type GroupItem}">
                                        <GroupBox Padding="5" Margin="2,5">
                                            <GroupBox.Header>
                                                <Border Background="Black"
                                                        CornerRadius="4">
                                                    <TextBlock Text="{Binding Name}" />
                                                </Border>
                                            </GroupBox.Header>
                                            <ItemsPresenter />
                                        </GroupBox>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </GroupStyle.ContainerStyle>
                </GroupStyle>
            </ItemsControl.GroupStyle>
            <ItemsControl.Template>
                <ControlTemplate>
                    <ScrollViewer CanContentScroll="True">
                        <ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
                    </ScrollViewer>
                </ControlTemplate>
            </ItemsControl.Template>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <UniformGrid IsItemsHost="True" Columns="2" Grid.IsSharedSizeScope="True" VirtualizingStackPanel.IsVirtualizing="True" />
                    <!--<VirtualizingStackPanel IsItemsHost="True" />-->
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <views:SingleTransactionView Margin="4,6" />
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </UserControl>

And I've boiled the SingleTransactionView down to something very simple:

    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" SharedSizeGroup="ExpanderColumn" />
        <ColumnDefinition Width="*" SharedSizeGroup="TransactionNameColumn" />
        <ColumnDefinition Width="Auto" SharedSizeGroup="DateColumn" />
        <ColumnDefinition Width="Auto" SharedSizeGroup="OptionsColumn" />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>


    <TextBlock Grid.Column="1"
               Grid.Row="0"
               Initialized="TextBlock_Initialized"
               Text="{Binding TransactionName}"
               HorizontalAlignment="Left"
               VerticalAlignment="Center"
               FontSize="14"
               FontWeight="Bold"
               Margin="4"/>        
</Grid>

Upvotes: 0

Views: 1338

Answers (1)

Mike Strobel
Mike Strobel

Reputation: 25623

This is probably due to your use of grouping. By default, enabling grouping effectively switches off virtualization. However, as of .NET 4.5, you should be able to regain this functionality via the VirtualizingPanel.IsVirtualizingWhenGrouping property.

Excerpt from this blog post on WPF enhancements in .NET 4.5:

In WPF 4.0, you lost virtualization when grouping is done on the collection you display. I repeat : Grouping = no virtualization in WPF 4.0. This is still the default behavior of WPF 4.5, but you can turn on the virtualization by using the IsVirtualizingWhenGrouping attached property of the VirtualizingPanel class. When this is done, you benefit of all the already described advantages of virtualization.

Here is how you can enable it via XAML:

<ListBox ItemsSource="{Binding Persons}"
         ItemTemplate="{StaticResource PersonDataTemplate}"
         VirtualizingPanel.IsVirtualizing="True"
         VirtualizingPanel.IsVirtualizingWhenGrouping="True">
    <ListBox.GroupStyle>
        <GroupStyle HeaderTemplate="{StaticResource GroupHeaderTemplate}" />
    </ListBox.GroupStyle>
</ListBox>

Sounds like setting VirtualizingPanel.IsVirtualizingWhenGrouping="True" alongside your other virtualization-related properties should give you the behavior you want.+

Upvotes: 2

Related Questions