ObservableCollection не сортирует вновь добавленные элементы

У меня есть следующая 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.Add(message);

Я делаю что-то неправильно? Я уверен, что смогу обойти эту проблему, обновляя представление каждый раз, когда добавляю элемент, но это просто неправильный способ сделать это (не говоря уже о производительности).

3 ответа

Решение

Так что я провел немного больше исследований, и оказалось, что моя проблема связана с ограничением сетки данных WPF. Он не будет автоматически пересортировать коллекцию при изменении базовых данных. Другими словами, когда вы впервые добавите свой элемент, он будет отсортирован и помещен в правильное место, но если вы измените свойство элемента, он не будет пересортирован. INotifyPropertyChanged не имеет отношения к сортировке обновлений. Он имеет дело только с обновлением отображаемых данных, но не вызывает их сортировку. Это событие CollectionChanged, которое вызывает повторную сортировку, но изменение элемента, который уже находится в коллекции, не вызовет это конкретное событие, и, следовательно, сортировка не будет выполнена.

Вот еще одна похожая проблема: C# WPF Datagrid не выполняет динамическую сортировку при обновлении данных

Решением этого пользователя было вручную вызвать 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);          
            }
        }

Я только что нашел проблему, после попытки отсортировать другое свойство и заметив, что оно работает. Оказывается, когда мои сообщения добавлялись в коллекцию, свойство TimeSent инициализировалось в MinDate и только затем обновлялось до фактической даты. Таким образом, это было должным образом размещено внизу списка. Проблема в том, что позиция не обновлялась при изменении свойства TimeSent. Похоже, у меня проблема с распространением событий INotifyPropertyChanged (TimeSent находится в другом объекте внутри объекта Message).

Весь мой ответ ниже - бред. Как указано в комментариях, если вы привязываетесь к самой коллекции, то вы неявно привязываетесь к представлению коллекции по умолчанию. (Однако, как комментарий к примечаниям к ссылке, Silverlight является исключением - там не создается неявное представление коллекции по умолчанию, если коллекция не реализует ICollectionViewFactory.)

CollectionViewSourceне изменяет основную коллекцию.Чтобы выполнить сортировку, вам нужно привязать само представление, например:

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

Обратите внимание, что, хотя исходная коллекция (Сообщения) не затронута, ваш отсортированный вид будет обновляться через событие уведомления:

Если исходная коллекция реализует интерфейс INotifyCollectionChanged, изменения, вызванные событием CollectionChanged, распространяются на представления.

Другие вопросы по тегам