1 year ago

#361024

test-img

ekt

WPF DataGrid throws ArgumentOutOfRangeException when inserting a second row

Scenario is quite simple. A WPF DataGrid whose rows come from an ObservableCollection. Things worth noting are:

  • the DataGrid has a GroupStyle defined
  • the ObservableCollection is initially empty
  • rows are added at the top of the ObservableCollection (ie: with Items.Insert(0, element))

First insert is successful. The second insert crashes with

System.ArgumentOutOfRangeException: 'Index was out of range. Must be non-negative and less than the size of the collection.

The problem disappears if I remove the virtualization of the columns, OR if I remove the GroupStyle OR if I put a fixed size on the columns. Calling CollectionViewSource.GetDefaultView(Items).Refresh() immediately after Items.Insert also resolves.

However, all the above solutions are arbitrary and it is not obvious to me why I shouls do them. I think it is simply a DataGrid bug but I would like to understand if instead there is something fundamentally wrong that I am doing.

Following, a simplified app that reproduces the issue. Click the button twice and you'll get the exception.

MainWindow.xaml

    <Window x:Class="WpfDataGridGroupsBug.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfDataGridGroupsBug"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="600"
        d:DataContext="{d:DesignInstance d:Type=local:MyViewModel}"
        WindowStartupLocation="CenterScreen">

    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="GridGroupContainerStyle.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>

    <StackPanel>
        <DataGrid
            x:Name="theDataGrid"
            Height="280"
            RowHeight="20"
            ItemsSource="{Binding Items}"
            AutoGenerateColumns="False"
            CanUserAddRows="False"
            EnableColumnVirtualization="True"
        >
            <DataGrid.GroupStyle>
                <GroupStyle ContainerStyle="{StaticResource GridGroupContainerStyle}"/>
            </DataGrid.GroupStyle>

            <DataGrid.Columns>
                <DataGridTextColumn Header="Name" MinWidth="100" Width="*" Binding="{Binding Path=SomeValue}"/>
            </DataGrid.Columns>

        </DataGrid>
        <Button Click="AddRows" Height="30" Content="Add Row (click me twice)"/>
    </StackPanel>

</Window>

GridGroupContainerStyle.xaml

<Style x:Key="GridGroupContainerStyle" TargetType="{x:Type GroupItem}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type GroupItem}">
                <Expander IsExpanded="true">
                    <Expander.Header>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding Path=Name}" FontWeight="Bold"/>
                            <TextBlock Text="{Binding Path=ItemCount, StringFormat=' ({0})'}" FontWeight="Bold"/>
                        </StackPanel>
                    </Expander.Header>
                    <ItemsPresenter />
                </Expander>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

MainWindow.xaml.cs

using System.Windows;

namespace WpfDataGridGroupsBug
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            var viewModel = new MyViewModel();
            this.DataContext = viewModel;
        }
    
        private void AddRows(object sender, RoutedEventArgs e)
        {
            if (DataContext is MyViewModel viewModel)
                viewModel.AddRowAtTop();
        }
    }
}

ViewModel.cs

public class MyItemModel
{
    public string SomeValue { get; set; }
}

public class MyViewModel : INotifyPropertyChanged
{
    public ObservableCollection<MyItemModel> Items { get; set; }

    public MyViewModel()
    {
        Items = new ObservableCollection<MyItemModel>();
    }

    public void AddRowAtTop()
    {
        Items.Insert(0, new MyItemModel() { SomeValue = "blah" });
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

callstack

at System.ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource)
at System.Collections.Generic.List`1.System.Collections.IList.get_Item(Int32 index)
at System.Windows.Controls.DataGridCellsPanel.ArrangeOverride(Size arrangeSize)
at System.Windows.FrameworkElement.ArrangeCore(Rect finalRect)
at System.Windows.UIElement.Arrange(Rect finalRect)
at MS.Internal.Helper.ArrangeElementWithSingleChild(UIElement element, Size arrangeSize)
at System.Windows.Controls.ItemsPresenter.ArrangeOverride(Size arrangeSize)
at System.Windows.FrameworkElement.ArrangeCore(Rect finalRect)

c#

wpf

datagrid

observablecollection

0 Answers

Your Answer

Accepted video resources