WPF DataGrid 忽略 SortDescription
Posted
技术标签:
【中文标题】WPF DataGrid 忽略 SortDescription【英文标题】:WPF DataGrid ignores SortDescription 【发布时间】:2012-06-26 00:01:38 【问题描述】:关于 WPF DataGrid(.NET 4.0 中的 System.Windows.Controls.DataGrid)的排序,我遇到了一个奇怪的问题。
它的 ItemsSource 绑定到 datacontext 对象的一个属性:
<DataGrid HeadersVisibility="Column" SelectedIndex="0" MinHeight="30" ItemsSource="Binding FahrtenView" AutoGenerateColumns="False" x:Name="fahrtenDG">
FahrtenView 看起来像这样:
public ICollectionView FahrtenView
get
var view = CollectionViewSource.GetDefaultView(_fahrten);
view.SortDescriptions.Add(new SortDescription("Index", ListSortDirection.Ascending));
return view;
DataGrid 已排序。但是,它仅在第一次分配 DataContext 时才进行排序。之后,更改 DataContext(通过在数据层次结构中选择另一个“父”对象)仍然会导致属性 FahrtenView 被评估(我可以放入一个 BP 并且调试器在那里停止)但是添加的排序描述被完全忽略,因此排序确实不再工作了。
即使在每个 DataContextChange 上调用 fahrtenDG.Items.Refresh() 也无济于事。
我很确定这是对 WPF DataGrid 进行排序时要走的路,不是吗?那么为什么它在第一次被调用时就完美地完成了它的工作,却如此顽固地拒绝工作呢?
有什么想法吗?我将不胜感激。
干杯, 亨德里克
【问题讨论】:
你应该添加你的更新作为答案,然后接受它(如果可以的话) 类似于***.com/questions/9560528/issue-sorting-datagrid & ***.com/questions/6176771/… 【参考方案1】:我从 DataGrid 继承来简要了解它的内容。我发现由于一些神秘的原因,虽然第一次调用 OnItemsSourceChanged,但一切看起来都很好,在 OnItemsSourceChanged 的每个后续调用中,ItemsSource 的 SortDescription 列表集合视图为空。
出于这个原因,我添加了一个在 OnItemsSourceChanged 结束时调用的自定义 SetupSortDescription 事件。现在我在事件处理函数中添加排序描述,这就像一个魅力。
我认为这是 WPF 工具包 DataGrid 中的一个错误。
这是我重写的 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);
【讨论】:
非常感谢您的帖子,亨德里克!仅通过视图模型确实不可能解决此错误 - 必须实现自定义 DataGrid。我使用附加属性而不是事件(下面的代码)进行了小修改。 谢谢!我改进了这一点以使用 MVVM 而不是事件 - 通过为您设置一次的 SortDescriptions 列表提供绑定。请参阅我的其他答案。 谢谢!这让我发疯!我修改了您的代码以满足我的需要。它基本上保留了排序描述,并在它们被吹走时恢复它们。这可能对其他人有所帮助:见下文。【参考方案2】:我对 Hendrik 的回答做了一些改进,以使用 MVVM 而不是事件。
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);
【讨论】:
对我来说效果很好,只要我向数据网格提供CollectionViewSource
而不是 ObservableCollection
。我在控件的资源中定义了我的CollectionViewSource
,并在我绑定到这个新属性的静态类中定义了另一个SortDescription
列表。【参考方案3】:
我使用来自 kat 的 interited DataGrid 来为 WPF DataGrid 创建一个行为。
该行为会保存初始的 SortDescriptions 并将它们应用于 ItemsSource
的每次更改。
您还可以提供IEnumerable<SortDescription>
,这将在每次更改时触发。
行为
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);
带有可选 SortDescriptions 参数的 XAML
<DataGrid ItemsSource="Binding View" >
<i:Interaction.Behaviors>
<commons:DataGridSortBehavior SortDescriptions="Binding SortDescriptions"/>
</i:Interaction.Behaviors>
</DataGrid>
ViewModel ICollectionView 设置
View = CollectionViewSource.GetDefaultView(_collection);
View.SortDescriptions.Add(new SortDescription("Sequence", ListSortDirection.Ascending));
可选:ViewModel 属性,用于提供可更改的 SortDescriptions
public IEnumerable<SortDescription> SortDescriptions
get
return new List<SortDescription> new SortDescription("Sequence", ListSortDirection.Ascending);
【讨论】:
工作愉快。谢谢。【参考方案4】:如果您在同一个集合上调用 CollectionViewSource.GetDefaultView(..),您将返回相同的 collectionview 对象,这可以解释为什么添加相同的 sortdescription 结构不会触发更改。
fahrtenDG.Items.Refresh() 无法工作,因为您没有刷新绑定的集合。
CollectionViewSource.GetDefaultView(_fahrten).Refresh() 应该可以工作 - 我会保留对它的引用。
根据您的解释,我不太明白 datacontext 的变化 - 您是否将其更改为新对象?如果是这样,您的所有绑定都应该重新评估。它是否总是同一个集合,并且您在 listelements 上的 Index 属性会发生变化,这就是您期望发生变化的原因 - 如果是这样,您的列表元素可能需要 INotifyPropertyChanged 实现,因为如果集合没有改变,那么就不需要度假村。
您的 OnItemsSourceChanged(..) 实现看起来像一个 hack :)
【讨论】:
【参考方案5】:我试图通过视图模型解决这个问题——通过在 getter 中重新创建 ICollectionView 并疯狂地调用 DeferRefresh()。但是,我可以确认 Hendrik 的解决方案是唯一可靠的解决方案。我想在下面发布完整的代码,以防它对某人有所帮助。
查看
<controls:SortableDataGrid
ItemsSource="Binding InfoSorted"
PermanentSort="Binding PermanentSort"
CanUserSortColumns="False" />
查看模型
public ObservableCollection<Foo> Info get; private set;
public ICollectionView InfoSorted get; private set;
public IEnumerable<SortDescription> PermanentSort get; private set;
自定义控制
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);
【讨论】:
【参考方案6】:我赞同 Juergen's approach 使用附加行为。但是,由于我在视图模型类中声明 CollectionViewSource 对象时出现了这个问题的版本,因此我发现通过添加事件处理程序SortDescriptions_CollectionChanged
来解决问题更直接,如下面的代码所示。此代码完全在视图模型类中。
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);
【讨论】:
【参考方案7】:谢谢!这让我发疯!我修改了您的代码以满足我的需要。它基本上保留了排序描述,并在它们被吹走时恢复它们。这可能对其他人有所帮助:
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);
【讨论】:
以上是关于WPF DataGrid 忽略 SortDescription的主要内容,如果未能解决你的问题,请参考以下文章