Skip to content

Null Reference Exception in VirtualizingStackPanel.cs #10443

Open
@PrikolotinSergey

Description

@PrikolotinSergey

Description

Crash happens if you put VirtualizingStackPanel inside any VirtualizingPanel, e.g. VirtualizingStackPanel.

VitrualizingStackPanel.zip

Reproduction Steps

Put ItemsControl with VirtualizingStackPanel iside other VirtualizingStackPanel and add new Item to it at runtime.

    <VirtualizingStackPanel>
        <ItemsControl x:Name="ItemsControl">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
        <Button Content="AddItem" Click="OnButtonClick"/>
    </VirtualizingStackPanel>

Code behind is:

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            ItemsControl.ItemsSource = Items;
        }

        private void OnButtonClick(object sender, RoutedEventArgs e) => Items.Insert(0,"Hello World");

        public ObservableCollection<string> Items { get; } = new ();
    }

The problem could be reproduced on .net6-9. I haven't tested earlier versions though.
Crash happens inside VirtualizingStackPanel.cs

protected override bool ShouldItemsChangeAffectLayoutCore(bool areItemChangesLocal, ItemsChangedEventArgs args)
{
    \\...
if (IsScrolling)
{
	flag = !IsExtendedViewportFull();
	if (!flag)
	{
		UpdateExtent(areItemChangesLocal);
	}
}
else
{
	DependencyObject itemsOwnerInternal = ItemsControl.GetItemsOwnerInternal(this);
	if (VisualTreeHelper.GetParent(itemsOwnerInternal) is VirtualizingPanel virtualizingPanel)
	{
		UpdateExtent(areItemChangesLocal);
		IItemContainerGenerator itemContainerGenerator = virtualizingPanel.ItemContainerGenerator;
        
        // Here itemContainerGenerator  is null, but is not checked for null, as a result NullReferenceException is thrown

		int itemIndex = ((ItemContainerGenerator)itemContainerGenerator).IndexFromContainer(itemsOwnerInternal, returnLocalIndex: true);
		ItemsChangedEventArgs args2 = new ItemsChangedEventArgs(NotifyCollectionChangedAction.Reset, itemContainerGenerator.GeneratorPositionFromIndex(itemIndex), 1, 1);
		flag = virtualizingPanel.ShouldItemsChangeAffectLayout(areItemChangesLocal: false, args2);
	}
	else
	{
		flag = true;
	}
}
}

Expected behavior

No Exception is thrown

Actual behavior

Exception is thrown.

Regression?

It seems it worked on .net framework 4.8, but doesn't work for .net6-9.

Known Workarounds

Put non-virtualizing panel in-between ItemsControl and VirtualizingStackPanel:

    <VirtualizingStackPanel>
        <Grid>
            <ItemsControl x:Name="ItemsControl">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <VirtualizingStackPanel/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
            </ItemsControl>
        </Grid>
        <Button Content="AddItem" Click="OnButtonClick"/>
    </VirtualizingStackPanel>

Impact

No response

Configuration

No response

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions