I've got a strange problem here regarding sorting of a WPF DataGrid (System.Windows.Controls.DataGrid in .NET 4.0).
Its ItemsSource is bound to a property of the datacontext object:
<DataGrid HeadersVisibility="Column" SelectedIndex="0" MinHeight="30" ItemsSource="{Binding FahrtenView}" AutoGenerateColumns="False" x:Name="fahrtenDG">
FahrtenView looks like this:
public ICollectionView FahrtenView
{
get
{
var view = CollectionViewSource.GetDefaultView(_fahrten);
view.SortDescriptions.Add(new SortDescription("Index", ListSortDirection.Ascending));
return view;
}
}
The DataGrid gets sorted. However it only gets sorted the very first time it's assigned a DataContext. After that, changing the DataContext (by selecting another "parental" object in a data hierarchy) still causes the property FahrtenView to be evaluated (I can put a BP in and debugger stops there) but the added sortdescription is completely ignored, hence sorting does not work anymore.
Even calling fahrtenDG.Items.Refresh() on every DataContextChange doesn't help.
I'm pretty sure this is the way to go when it comes to sorting a WPF DataGrid, isn't it? So why does it refuse to work so obstinately after doing its job perfectly the very first time it gets called?
Any idea? I'd be very grateful.
Cheers, Hendrik
I used the interited DataGrid from kat to create a Behavior for the WPF DataGrid.
The behavior saves the initial SortDescriptions and applies them on every change of ItemsSource
.
You can also provide a IEnumerable<SortDescription>
which will cause a resort on 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 ICollectionView Setup
View = CollectionViewSource.GetDefaultView(_collection);
View.SortDescriptions.Add(new SortDescription("Sequence", ListSortDirection.Ascending));
Optional: ViewModel Property for providing changable SortDescriptions
public IEnumerable<SortDescription> SortDescriptions
{
get
{
return new List<SortDescription> {new SortDescription("Sequence", ListSortDirection.Ascending)};
}
}
I improved on Hendrik's answer a bit to use MVVM rather than 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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With