WPF DataGrid ignores SortDescription

I have a weird problem regarding WPF sorting DataGrid (System.Windows.Controls.DataGrid in .NET 4.0).

The ItemsSource property is bound to the property of the datacontext object:

<DataGrid HeadersVisibility="Column" SelectedIndex="0" MinHeight="30" ItemsSource="{Binding FahrtenView}" AutoGenerateColumns="False" x:Name="fahrtenDG"> 

FahrtenView is as follows:

  public ICollectionView FahrtenView { get { var view = CollectionViewSource.GetDefaultView(_fahrten); view.SortDescriptions.Add(new SortDescription("Index", ListSortDirection.Ascending)); return view; } } 

DataGrid is sorted. However, it is only sorted the first time it has assigned a DataContext. After that, changing the DataContext (by selecting another "parent" object in the data hierarchy) still evaluates the FahrtenView property (I can put BP in and the debugger stop there), but the added sortdescription is completely ignored, so sorting doesn't work anymore.

Even calling fahrtenDG.Items.Refresh () for each DataContextChange element does not help.

I'm sure this is the way to go when it comes to sorting WPF DataGrid, isn't it? So why does he refuse to work so stubbornly after completing his work for the first time when his name is?

Any idea? I would be very grateful.

Cheers, Hendrick

+8
sorting wpf datagrid collectionview
source share
7 answers

I inherited from DataGrid to take a quick look at its guts. I found that for some mysterious reasons, although OnItemsSourceChanged is first called, everything looks fine, every time I call OnItemsSourceChanged, the SortDescription list from the ItemsSource collection view is empty .

For this reason, I added a custom SetupSortDescription event that is fired at the end of OnItemsSourceChanged. Now I am adding sort descriptions to an event handler function that works like a charm.

I see this as a bug in the WPF toolkit DataGrid.

Here is my overridden OnItemsSourceChanged

  protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) { if (SetupSortDescriptions != null && (newValue != null)) SetupSortDescriptions(this, new ValueEventArgs<CollectionView>((CollectionView)newValue)); base.OnItemsSourceChanged(oldValue, newValue); } 
+8
source share

I improved Hendrik a bit to use MVVM, not an event.

  public static readonly DependencyProperty SortDescriptionsProperty = DependencyProperty.Register("SortDescriptions", typeof(List<SortDescription>), typeof(ReSolverDataGrid), new PropertyMetadata(null)); /// <summary> /// Sort descriptions for when grouped LCV is being used. Due to bu*g in WCF this must be set otherwise sort is ignored. /// </summary> /// <remarks> /// IN YOUR XAML, THE ORDER OF BINDINGS IS IMPORTANT! MAKE SURE SortDescriptions IS SET BEFORE ITEMSSOURCE!!! /// </remarks> public List<SortDescription> SortDescriptions { get { return (List<SortDescription>)GetValue(SortDescriptionsProperty); } set { SetValue(SortDescriptionsProperty, value); } } protected override void OnItemsSourceChanged(System.Collections.IEnumerable oldValue, System.Collections.IEnumerable newValue) { //Only do this if the newValue is a listcollectionview - in which case we need to have it re-populated with sort descriptions due to DG bug if (SortDescriptions != null && ((newValue as ListCollectionView) != null)) { var listCollectionView = (ListCollectionView)newValue; listCollectionView.SortDescriptions.AddRange(SortDescriptions); } base.OnItemsSourceChanged(oldValue, newValue); } 
+4
source share

I used kat's processed DataGrid to create Behavior for the WPF DataGrid.

The behavior saves the initial SortDescriptions and applies them every time the ItemsSource changes. You can also provide an IEnumerable<SortDescription> that will call the resort with every change.

Behavior

 public class DataGridSortBehavior : Behavior<DataGrid> { public static readonly DependencyProperty SortDescriptionsProperty = DependencyProperty.Register( "SortDescriptions", typeof (IEnumerable<SortDescription>), typeof (DataGridSortBehavior), new FrameworkPropertyMetadata(null, SortDescriptionsPropertyChanged)); /// <summary> /// Storage for initial SortDescriptions /// </summary> private IEnumerable<SortDescription> _internalSortDescriptions; /// <summary> /// Property for providing a Binding to Custom SortDescriptions /// </summary> public IEnumerable<SortDescription> SortDescriptions { get { return (IEnumerable<SortDescription>) GetValue(SortDescriptionsProperty); } set { SetValue(SortDescriptionsProperty, value); } } protected override void OnAttached() { var dpd = DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof (DataGrid)); if (dpd != null) { dpd.AddValueChanged(AssociatedObject, OnItemsSourceChanged); } } protected override void OnDetaching() { var dpd = DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof (DataGrid)); if (dpd != null) { dpd.RemoveValueChanged(AssociatedObject, OnItemsSourceChanged); } } private static void SortDescriptionsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is DataGridSortBehavior) { ((DataGridSortBehavior) d).OnItemsSourceChanged(d, EventArgs.Empty); } } public void OnItemsSourceChanged(object sender, EventArgs eventArgs) { // save description only on first call, SortDescriptions are always empty after ItemsSourceChanged if (_internalSortDescriptions == null) { // save initial sort descriptions var cv = (AssociatedObject.ItemsSource as ICollectionView); if (cv != null) { _internalSortDescriptions = cv.SortDescriptions.ToList(); } } else { // do not resort first time - DataGrid works as expected this time var sort = SortDescriptions ?? _internalSortDescriptions; if (sort != null) { sort = sort.ToList(); var collectionView = AssociatedObject.ItemsSource as ICollectionView; if (collectionView != null) { using (collectionView.DeferRefresh()) { collectionView.SortDescriptions.Clear(); foreach (var sorter in sort) { collectionView.SortDescriptions.Add(sorter); } } } } } } } 

XAML with optional SortDescriptions parameter

 <DataGrid ItemsSource="{Binding View}" > <i:Interaction.Behaviors> <commons:DataGridSortBehavior SortDescriptions="{Binding SortDescriptions}"/> </i:Interaction.Behaviors> </DataGrid> 

ViewModel Configuring ICollectionView

 View = CollectionViewSource.GetDefaultView(_collection); View.SortDescriptions.Add(new SortDescription("Sequence", ListSortDirection.Ascending)); 

Optional: ViewModel property to provide mutable SortDescriptions

 public IEnumerable<SortDescription> SortDescriptions { get { return new List<SortDescription> {new SortDescription("Sequence", ListSortDirection.Ascending)}; } } 
+3
source share

If you call CollectionViewSource.GetDefaultView (..) in the same collection, you get the same viewview, which might explain why adding the same sortdescription structure does not cause a change.

fahrtenDG.Items.Refresh () may not work because you are not updating the associated collection.

CollectionViewSource.GetDefaultView (_fahrten) .Refresh () should work - I would keep a link to it.

From your explanation, I don’t quite understand the change in the datacontext - are you changing it to a new object? If so, all of your bindings should be reevaluated. Is it always the same collection, and the Index property in the lists of elements changes, and therefore you expect changes - if your list element may need to implement INotifyPropertyChanged, because if the collection does not change, then there is no need for a resort.

Your implementation of OnItemsSourceChanged (..) seems hacked :)

+1
source share

I tried to work around this problem with the view model - by recreating the ICollectionView in the getter and desperately calling DeferRefresh (). However, I can confirm that Hendrick’s solution is the only one that works reliably. I wanted to post the full code below if it helps someone.

VIEW

 <controls:SortableDataGrid ItemsSource="{Binding InfoSorted}" PermanentSort="{Binding PermanentSort}" CanUserSortColumns="False" /> 

VIEWING THE MODEL

 public ObservableCollection<Foo> Info { get; private set; } public ICollectionView InfoSorted { get; private set; } public IEnumerable<SortDescription> PermanentSort { get; private set; } 

CUSTOMS MANAGEMENT

 public class SortableDataGrid : DataGrid { public static readonly DependencyProperty PermanentSortProperty = DependencyProperty.Register( "PermanentSort", typeof(IEnumerable<SortDescription>), typeof(SortableDataGrid), new FrameworkPropertyMetadata(null)); public IEnumerable<SortDescription> PermanentSort { get { return (IEnumerable<SortDescription>)this.GetValue(PermanentSortProperty); } set { this.SetValue(PermanentSortProperty, value); } } protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) { var sort = this.PermanentSort; if (sort != null) { sort = sort.ToList(); var collectionView = newValue as ICollectionView; if (collectionView != null) { using (collectionView.DeferRefresh()) { collectionView.SortDescriptions.Clear(); foreach (SortDescription sorter in sort) { collectionView.SortDescriptions.Add(sorter); } } } } base.OnItemsSourceChanged(oldValue, newValue); } } 
+1
source share

I welcome Jürgen's approach of using attached behavior. However, since my version of this problem arose when I declared the CollectionViewSource object in the view model class, I found a more direct solution to the problem by adding the SortDescriptions_CollectionChanged event SortDescriptions_CollectionChanged , as shown in the code below. This code is completely in the view model class.

 public CollectionViewSource FilteredOptionsView { get { if (_filteredOptionsView == null) { _filteredOptionsView = new CollectionViewSource { Source = Options, IsLiveSortingRequested = true }; SetOptionsViewSorting(_filteredOptionsView); _filteredOptionsView.View.Filter = o => ((ConstantOption)o).Value != null; } return _filteredOptionsView; } } private CollectionViewSource _filteredOptionsView; protected void SetOptionsViewSorting(CollectionViewSource viewSource) { // define the sorting viewSource.SortDescriptions.Add(_optionsViewSortDescription); // subscribe to an event in order to handle a bug caused by the DataGrid that may be // bound to the CollectionViewSource ((INotifyCollectionChanged)viewSource.View.SortDescriptions).CollectionChanged += SortDescriptions_CollectionChanged; } protected static SortDescription _optionsViewSortDescription = new SortDescription("SortIndex", ListSortDirection.Ascending); void SortDescriptions_CollectionChanged(Object sender, NotifyCollectionChangedEventArgs e) { var collection = sender as SortDescriptionCollection; if (collection == null) return; // The SortDescriptions collection should always contain exactly one SortDescription. // However, when DataTemplate containing the DataGrid bound to the ICollectionView // is unloaded, the DataGrid erroneously clears the collection. if (collection.None()) collection.Add(_optionsViewSortDescription); } 
0
source share

Thanks! It drove me crazy! I changed my code to fit my needs. It basically saves type descriptions and restores them when they are reset. This may help others:

 private List<SortDescription> SortDescriptions = null; protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) { if (newValue is CollectionView collectionView) if (SortDescriptions == null) SortDescriptions = new List<SortDescription>(collectionView.SortDescriptions); else foreach (SortDescription sortDescription in SortDescriptions) collectionView.SortDescriptions.Add(sortDescription); base.OnItemsSourceChanged(oldValue, newValue); } 
0
source share

All Articles