Search code examples
c#wpfwpfdatagridcollectionviewsource

WPF DataGrid column header sort arrow disappear after changing the source of its view collection source


I have the following simple code that I can reproduce this problem:

XAML:

<DataGrid ItemsSource="{Binding Source.View}"
          AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Name"
                            Binding="{Binding Name}"
                            SortMemberPath="Name"
                            SortDirection="Ascending"/>
    </DataGrid.Columns>
</DataGrid>

View Model:

private readonly ObservableCollection<Data> _collection1 = new ObservableCollection<Data> {new Data("item 1"), new Data("item 2")};
private readonly ObservableCollection<Data> _collection2 = new ObservableCollection<Data> {new Data("person 1"), new Data("person 2")};

public MainViewModel()
{
    Source.Source = _collection1;

    // Set up a timer that runs 5 seconds.
    Observable.Timer(TimeSpan.FromSeconds(5)).ObserveOn(AsyncOperationManager.SynchronizationContext).Subscribe(_ =>
    {
        // Get existing sort descriptions.
        var existingSortDescription = Source.View.SortDescriptions.ToList();

        // Change source.
        Source.Source = _collection2;

        // This has to be done in order to maintain the sort order.
        existingSortDescription.ForEach(Source.SortDescriptions.Add);
    });
}

public CollectionViewSource Source { get; } = new CollectionViewSource();

private class Data
{
    public Data(string name)
    {
        Name = name;
    }

    public string Name { get; }
}

So what the above code does is, that when the app starts, use _collection1 as the source of the items source of the data grid to begin with.

After 5 seconds, change the items source of the data grid to _collection2.

If you run the above code, the sort direction arrow in the "Name" column header will disappear as soon as the source is changed to _collection2, but the sorting is still correct.

Is this a bug in WPF DataGrid control or I'm missing something here?


Solution

  • The SortDescriptions that you add to the View of the CollectionViewSource in the view model doesn't affect the arrows that you see in the DataGrid control in the view.

    You can programmatically display the arrow for a specific column by setting its SortDirection property. So what you could do is to create a custom DataGrid control that handles this for you (the built-one doesn't as you have already discovered), e.g.:

    public class CustomDataGrid : DataGrid
    {
        protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
        {
            base.OnItemsSourceChanged(oldValue, newValue);
    
            INotifyCollectionChanged oldView = oldValue as INotifyCollectionChanged;
            if (oldView != null)
                oldView.CollectionChanged -= View_CollectionChanged;
    
            INotifyCollectionChanged newView = newValue as INotifyCollectionChanged;
            if (newView != null)
                newView.CollectionChanged += View_CollectionChanged;
        }
    
        private void View_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            ICollectionView view = sender as ICollectionView;
            if (view != null)
            {
                SortDescription sd = view.SortDescriptions.LastOrDefault();
                if (sd != null)
                {
                    DataGridColumn column = Columns.FirstOrDefault(x => x.SortMemberPath == sd.PropertyName);
                    if (column != null)
                    {
                        column.SortDirection = sd.Direction;
                    }
                }
            }
        }
    }
    

    You then simply replace your <DataGrid /> elements with <local:CustomDataGrid /> elements in your XAML.