0

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>

Wonko the Sane
  • 10,623
  • 8
  • 67
  • 92
  • UniformGrid does not support virtualizing. – Clemens Mar 25 '19 at 06:14
  • @Clemens - actually, I believe I've used `UniformGrid` as a `VirtualizingPanel` before. Regardless, if I replace `UniformGrid` with the commented out `VirtualizingStackPanel` above, it still does not virtualize. I have looked at the other answers, but none has helped me overcome this issue. – Wonko the Sane Mar 25 '19 at 12:04
  • 1
    I'm curious if anyone can provide an answer that goes beyond what's explained here: https://stackoverflow.com/q/2783845/1136211 – Clemens Mar 25 '19 at 12:32
  • Me too. :) Between previous work that I've done and this example, I cannot find the error of my ways. – Wonko the Sane Mar 25 '19 at 12:33

1 Answers1

2

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

Mike Strobel
  • 25,075
  • 57
  • 69