ObservableCollection不对新添加的项目进行排序

时间:2014-01-17 22:43:48

标签: wpf sorting datagrid observablecollection icollectionview

我有以下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);

我做错了吗?我确信每次添加项目时都可以通过刷新视图解决问题,但这似乎是错误的做法(更不用说性能方面)。

3 个答案:

答案 0 :(得分:4)

所以我做了一些调查,结果发现我的问题是由于WPF数据网格的限制。当基础数据发生变化时,它不会自动对集合进行重新排序。换句话说,当您第一次添加项目时,它将被排序并放置在正确的位置,但是如果您更改项目的属性,它将不会被重新排序。 INotifyPropertyChanged与排序更新无关。它仅处理更新显示的数据,但不会触发对其进行排序。这是强制重新排序的CollectionChanged事件,但是修改已经在集合中的项目将不会触发此特定事件,因此不会执行排序。

这是另一个类似的问题: C# WPF Datagrid doesn't dynamically sort on data update

该用户的解决方案是手动调用OnCollectionChanged()。

最后,我结合了这两个主题的答案:

  1. ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged)
  2. ObservableCollection and Item PropertyChanged
  3. 我还添加了'智能'排序,如果属性发生变化,则只调用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对象内的另一个对象中)。