Search code examples
c#xamlbindingdatagrid

Why does WPF's DataGrid behave differently when re-initialized with identical code?


I have a UserControl with the following Xaml representing a simple DataGrid with buttons for testing:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>

    <DataGrid x:Name="TestGrid" AutoGenerateColumns="False">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Id" Binding="{Binding Path=Id}"/>
            <DataGridTextColumn Header="Name" Binding="{Binding Path=Name}"/>
        </DataGrid.Columns>
    </DataGrid>

    <StackPanel  Grid.Row="1"  Orientation="Horizontal">
        <Button  Content="Init View" Click="InitViewButton_Click"/>
        <Button  Content="Add" Click="AddButton_Click"/>
        <Button  Content="Change Ids" Click="ChangeIdButton_Click"/>
        <Button  Content="Filter X" Click="FilterButton_Click"/>
    </StackPanel>
</Grid>

And (in code-behind) following code:

public ListCollectionView TestView {get; set;}

public ObservableCollection<TestItem> TestItems {get; set;} 

private void init()
{
    var result = new ObservableCollection<TestItem>()
    {
        new TestItem() { Id = 1, Name = "First A" },
        new TestItem() { Id = 2, Name = "Second A" },
        new TestItem() { Id = 3, Name = "Third A" },

    };

    TestItems = result;

    var view = new ListCollectionView(TestItems);
    view.SortDescriptions.Add(new SortDescription(nameof(TestItem.Id), ListSortDirection.Descending));
    view.IsLiveSorting = true;
    view.Filter = o => (o as TestItem)?.Name.Contains("A") ?? false;
    TestView = view;
}

private void InitViewButton_Click(object sender, RoutedEventArgs e)
{
    init();
    TestGrid.ItemsSource = TestView;
    TestGrid.Items.Refresh();
}

private void AddButton_Click(object sender, RoutedEventArgs e)
{
    var item = new TestItem() { Id = 99, Name = "New AX" };

    TestItems.Add(item);
}

private void ChangeIdButton_Click(object sender, RoutedEventArgs e)
{
    foreach (var testItem in TestItems)
    {
        testItem.Id = Random.Shared.Next(1000);
    }
}

private void FilterButton_Click(object sender, RoutedEventArgs e)
{
    TestView.Filter = o => (o as TestItem)?.Name.Contains("X") ?? false;
}

After clicking on "Init View" the DataGrid is populated as expected and the other buttons work as expected, e.g. when changing the Ids the rows are resorted.

If I then click on "Init View" again, everything is ok, except for re-sorting, i.e. changing the Ids does not resort the rows - the displayed items are updated correctly, though.

How can this be?

Side note: despite not having set IsLiveFiltering, filtering works as expected, e.g. if I remove the "A" from a Name the item is removed from the DataGrid.


Solution

  • It looks like somehow the SortDescriptions are being cleared after setting the DataGrid ItemsSource to the new ListCollectionView (after the first time).

    If you add the SortDescription after TestGrid.ItemsSource = TestView, it works:

    private void init()
    {
        var result = new ObservableCollection<TestItem>()
        {
            new TestItem() { Id = 1, Name = "First A" },
            new TestItem() { Id = 2, Name = "Second A" },
            new TestItem() { Id = 3, Name = "Third A" },
        };
    
        TestItems = result;
    
        var view = new ListCollectionView(TestItems);
    
        view.IsLiveSorting = true;
        view.Filter = o => (o as TestItem)?.Name.Contains("A") ?? false;
        TestView = view;
    }
    
    private void InitViewButton_Click(object sender, RoutedEventArgs e)
    {
        init();
        TestGrid.ItemsSource = TestView;
        TestView.SortDescriptions.Add(new SortDescription(nameof(TestItem.Id), ListSortDirection.Descending));
        //TestGrid.Items.Refresh();
    }