我有以下ObservableCollection绑定到DataGrid:
public ObservableCollection<Message> Messages = new ObservableCollection<Message>;
XAML:
<DataGrid ItemsSource="{Binding Path=Messages}">
我使用默认视图在启动时对其进行排序:
ICollectionView view = CollectionViewSource.GetDefaultView(Messages);
view.SortDescriptions.Add(new SortDescription("TimeSent", ListSortDirection.Descending));
一切正常,但问题是每当我向Messages集合添加一条新消息时,它只会被附加到列表的底部,而不会自动排序。
Messages.Add(message);
我做错了吗?我确信每次添加项目时都可以通过刷新视图解决问题,但这似乎是错误的做法(更不用说性能方面)。
答案 0 :(得分:4)
所以我做了一些调查,结果发现我的问题是由于WPF数据网格的限制。当基础数据发生变化时,它不会自动对集合进行重新排序。换句话说,当您第一次添加项目时,它将被排序并放置在正确的位置,但是如果您更改项目的属性,它将不会被重新排序。 INotifyPropertyChanged与排序更新无关。它仅处理更新显示的数据,但不会触发对其进行排序。这是强制重新排序的CollectionChanged事件,但是修改已经在集合中的项目将不会触发此特定事件,因此不会执行排序。
这是另一个类似的问题: C# WPF Datagrid doesn't dynamically sort on data update
该用户的解决方案是手动调用OnCollectionChanged()。
最后,我结合了这两个主题的答案:
我还添加了'智能'排序,如果属性发生变化,则只调用OnCollectionChanged()是SortDescription中当前使用的值。
public class MessageCollection : ObservableCollection<Message>
{
ICollectionView _view;
public MessageCollection()
{
_view = CollectionViewSource.GetDefaultView(this);
}
public void Sort(string propertyName, ListSortDirection sortDirection)
{
_view.SortDescriptions.Clear();
_view.SortDescriptions.Add(new SortDescription(propertyName, sortDirection));
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
this.AddPropertyChanged(e.NewItems);
break;
case NotifyCollectionChangedAction.Remove:
this.RemovePropertyChanged(e.OldItems);
break;
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Reset:
this.RemovePropertyChanged(e.OldItems);
this.AddPropertyChanged(e.NewItems);
break;
}
base.OnCollectionChanged(e);
}
private void AddPropertyChanged(IEnumerable items)
{
if (items != null)
{
foreach (var obj in items.OfType<INotifyPropertyChanged>())
{
obj.PropertyChanged += OnItemPropertyChanged;
}
}
}
private void RemovePropertyChanged(IEnumerable items)
{
if (items != null)
{
foreach (var obj in items.OfType<INotifyPropertyChanged>())
{
obj.PropertyChanged -= OnItemPropertyChanged;
}
}
}
private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
bool sortedPropertyChanged = false;
foreach (SortDescription sortDescription in _view.SortDescriptions)
{
if (sortDescription.PropertyName == e.PropertyName)
sortedPropertyChanged = true;
}
if (sortedPropertyChanged)
{
NotifyCollectionChangedEventArgs arg = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Replace, sender, sender, this.Items.IndexOf((Message)sender));
OnCollectionChanged(arg);
}
}
答案 1 :(得分:1)
下面我的全部答案都是胡言乱语。正如评论中指出的那样,if you bind to the collection itself, then you are implicitly binding to the default collection view。 (但是,作为链接说明中的注释,Silverlight是一个例外 - 没有隐式创建默认集合视图,除非集合实现ICollectionViewFactory
。 )
CollectionViewSource
不会修改基础集合。要进行排序,您需要绑定到view本身,例如:
<DataGrid ItemsSource="{Binding Path=CollectionViewSource.View}">
请注意,虽然原始集合(消息)未受影响,但your sorted view will get updated通过通知事件:
秒>
如果源集合实现了INotifyCollectionChanged接口,则CollectionChanged事件引发的更改将传播到视图。
答案 2 :(得分:1)
在尝试对另一个属性进行排序并注意到它正好工作之后,我才发现问题。当我的消息被添加到集合时,TimeSent属性被初始化为MinDate,然后才更新到实际日期。所以它被正确地放在列表的底部。问题是,在修改TimeSent属性时,位置没有得到更新。看起来我有传播INotifyPropertyChanged事件的问题(TimeSent驻留在Message对象内的另一个对象中)。