3

我有以下绑定到 DataGrid 的 ObservableCollection:

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);

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

4

3 回答 3

5

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

这是另一个类似的问题: C# WPF Datagrid does not dynamic sort on data update

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

最后,我结合了这两个线程的答案:

  1. ObservableCollection 没有注意到其中的项目何时更改(即使使用 INotifyPropertyChanged)
  2. ObservableCollection 和 Item PropertyChanged

我还添加了“智能”排序,只有在属性更改时才调用 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);          
            }
        }
于 2014-01-20T15:07:33.473 回答
1

我在下面的整个答案都是胡言乱语。正如评论中所指出的,如果您绑定到集合本身,那么您将隐式绑定到默认集合视图。(但是,作为链接注释的评论,Silverlight 是一个例外——除非集合实现,否则不会隐式创建默认集合视图ICollectionViewFactory。)

CollectionViewSource不会修改基础集合。要获得排序,您需要绑定到视图本身,例如:

<DataGrid ItemsSource="{Binding Path=CollectionViewSource.View}">

请注意,虽然原始集合(消息)未受影响,但您的排序视图将通过通知事件更新:

如果源集合实现 INotifyCollectionChanged 接口,则由 CollectionChanged 事件引发的更改将传播到视图。

于 2014-01-17T22:51:53.597 回答
1

在尝试对另一个属性进行排序并注意到它恰好起作用之后,我才发现了问题。结果当我的消息被添加到集合中时,TimeSent 属性被初始化为 MinDate,然后才更新为实际日期。所以它被正确地放置在列表的底部。问题是当 TimeSent 属性被修改时,位置没有得到更新。看起来我对 INotifyPropertyChanged 事件的传播有问题(TimeSent 驻留在 Message 对象内的另一个对象中)。

于 2014-01-17T23:08:06.013 回答